代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 Ek6g?rj_
|d*a~T0
J.M&Vj:
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 D3P/: 4
N0vECk
>TBXT+
h8(>$A-
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 ACZK]~Y'N*
9n;6zVV%`
W g6H~x
Ot:}Ncq^\O
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 VO=Ibu&X
N2duhI6
<'SS IMr
2l^_OrE!
一、常见错误1# :多次拷贝字符串 #d-zH:uq
_*Z3,*~"X
5r~jo7
P R3Arfle
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 \]5I atli
vlE]RB
LyWY\K a
"* F`,I3
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: OHsA]7S
+jF2{"
IM/xBP
m|c[C\)By
String s = new String ("Text here"); HG?+b
@p
WN5VL
rVl 8?uy
mTxqcQc:7
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: Ddpcov
o.0tD
&0-oi Y
W0~G`A(:;
String temp = "Text here"; L/C~l3
String s = new String (temp); seBmhe5qR
!|H,g wqU
,1N|lyV
?Y,^Moc:
但是这段代码包含额外的String,并非完全必要。更好的代码为: f5Gn!xF
*URT-+'
-9,~b9$
Rk3
bZvj3
String s = "Text here"; sg2% BkTI
;(Ug]U%3_
A`Y^qXFb`
Hcu!bOQ
二、常见错误2#: 没有克隆(clone)返回的对象 ~o"=4q`>
?dCwo;~
'kcR:5B
kex V~Q
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: uMZ~[Sz
E(|A"=\
DlE, aYB
Z,E$4Z
import java.awt.Dimension; 8TeOh1\
/***Example class.The x and y values should never*be negative.*/ cVL|kYVWT
public class Example{ i:0v6d
private Dimension d = new Dimension (0, 0); aPEI_P+Ls
public Example (){ } 'A7!@hVy
D48e30
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ jL"V0M]c
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ KxX [8
if (height < 0 || width < 0) jo<xrn\
throw new IllegalArgumentException(); {&IB[Y6
d.height = height; ^py=]7[I
d.width = width; >U{iof<
} {jB>]7
%0C [v7\
public synchronized Dimension getValues(){ $5JeN{B
// Ooops! Breaks encapsulation B\c_GX Uw
return d; z0<E3t
} s"`~Xnf
} .s*N1
U?h
7b>_vtrt
g&xj(SMj-$
& mO n]
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: (6ga*5<
`5Kg[nB:
9,INyEyAL
:kR>wX
Example ex = new Example(); lK=Is
v+
Dimension d = ex.getValues(); Za{O9Qc?D|
d.height = -5; xX%ppD7
d.width = -10; >S7t
.T9$O]:o
rwLKY.J]
*HR
pbe2
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 y2_rm
3)EJws!
<!Cjq,Sk7
Pl:4`oY3
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 6^LXctW.
Q`9c/vPU
\
T#|<=
^^"zjl*^
更好的方式是让getValues()返回拷贝: p6<JpW5@_
j J{F0o
x*vD^1"'P
&<e18L7a
public synchronized Dimension getValues(){ F}u'A,Hc
return new Dimension (d.x, d.y); P!+Gwm{
} (2#Xa,pb
}9"''Z
"Wg,]$IvU
l{dsm1#W~
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 ; R+>}6
OK80-/8HI
Sxq@W8W
w&L~+Z<
三、常见错误3#:不必要的克隆 1- s(v)cxh
wH o}wp
IrL%0&*hS
D-BT`@~l
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: F9o7=5WAb
\lQ3j8U
@.-S(MNR
oM!&S'M/
/*** Example class.The value should never * be negative.*/ c},pu[nL
public class Example{ dHIk3j-!
private Integer i = new Integer (0); */M`KPW
public Example (){ } /Ot3[B
F
`o9GLxM}
/*** Set x. x must be nonnegative* or an exception will be thrown*/ wvq4 P
public synchronized void setValues (int x) throws IllegalArgumentException{ #Q!Xz2z2
if (x < 0) &NBH'Rt
throw new IllegalArgumentException(); \+V"JIStUj
i = new Integer (x); ar<8wq<4G
} PN 93.G(W
x\G%
public synchronized Integer getValue(){ Bn]=T
// We can’t clone Integers so we makea copy this way. ^ 4<