代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 7G93,dJ
aeP[+ I9
cpZc9;@IC
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 S%mfs!E>
Ug%_@t/?
jQh^WmN
5[gh|I;D
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 !EBY@ Y1
0Scm?l3
0g=`DSC<(
E167=BD9<
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 e3[:D5
T~xwo
3
hKBc0
oxz{ ejd{
一、常见错误1# :多次拷贝字符串 kc$)^E7
r"{<%e
pyZ9OA!PD
o[\HOe~;
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 p9qKLJ*.C
$m| V :/
d8o53a]
-db75=
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: M+P$/Wk
^%>kO,
X~9j$3lUBR
=L-I-e97@
String s = new String ("Text here"); F<&!b2)ML
,
YW|n:X
;xYNX
s!+
pL|
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: ?]O7Ao
e}yX_Z'P<
Vw{*P2v)
,IHb+ K
String temp = "Text here"; 0?DC00O
String s = new String (temp); 'LE"#2Hu
';B#Gx
3ec`Wa
iw9Q18:I}
但是这段代码包含额外的String,并非完全必要。更好的代码为: 5F"|E-;
=aG xg57
-yAQ
Q \hY7Xq'
String s = "Text here"; s)J(/
p0:kz l4$
OO) ~HV4\
]0V}D,V($
二、常见错误2#: 没有克隆(clone)返回的对象 'jg3
U7@AC}.+
:[l\@>H1tX
'tgKe!-@
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: .="bzgC3A
9!',b>C6
!YL..fb
u+m,b76
import java.awt.Dimension; NpP')m!`}
/***Example class.The x and y values should never*be negative.*/ <UP
m=Hb
public class Example{ "SxLN
8.:
private Dimension d = new Dimension (0, 0); K5>p89mZ
public Example (){ } 2}6%qgnT-
Dim>
7Wbh
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ "r4AY
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ N2r/ho}8
if (height < 0 || width < 0) uN*KHE+h
throw new IllegalArgumentException(); op2Of<{h
d.height = height; F9"w6;hh
d.width = width; Ex amD">T
} _gj&$zP
;*TIM%6#
public synchronized Dimension getValues(){ 1/+C5Bp*
// Ooops! Breaks encapsulation {$D,?V@%_
return d; >SF Uy\3
} =ac_,]z
} &F
*'B|n
82{ Vc
B(g_Gm<
Q#I"_G&{
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: %M
F;`; 1
K7knK
4S"\~><
\W5O&G-C
Example ex = new Example(); `3H4Ajzcc
Dimension d = ex.getValues(); } p
FQRSOZ
d.height = -5; C@ZK~Y_g
d.width = -10; 7w:ef0S
.~A*=
$,=6[T!z+e
SvM6iZ]
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 !%+2Yifna
jd]s<C3o
t.8 GT&p
2"P99$"
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 P9Yy9_a|x
8
;d$54
b
vy2Q g
Y`7~Am/r;&
更好的方式是让getValues()返回拷贝: -Xu.1S
z<sg0K8z63
*K!|@h{60
{e[%;W%c&
public synchronized Dimension getValues(){ +y7;81ND
return new Dimension (d.x, d.y); .;y#
} }jt?|dl1
6=4wp?
El_wdbbT
H&1[nU{?>
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 Hgeg@RP
Q
O RGD
XZ&KR.C,
+d+@u)6
三、常见错误3#:不必要的克隆 gTgMqvt
F>tQn4
Nk=JBIsKv
X'. qYsS
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: D0k
8^
e0@6Pd
n55Pv3}C
3~,d+P
/*** Example class.The value should never * be negative.*/ h~&gIub
public class Example{ mK+IEZV<3
private Integer i = new Integer (0); {FRAv(,\
public Example (){ } 2"|2a@
p.ANVA@:
/*** Set x. x must be nonnegative* or an exception will be thrown*/ B\J^=W+`
public synchronized void setValues (int x) throws IllegalArgumentException{ 9TF f8'?d
if (x < 0) GRb*EeT
throw new IllegalArgumentException(); T2}FYVj?!g
i = new Integer (x); q)H1pwxD
} u p.Q>28r
.)}@J5P)
public synchronized Integer getValue(){ /V3=KY`_J
// We can’t clone Integers so we makea copy this way. Y2xL>F
return new Integer (i.intValue()); A(?\>X
9g
} #-pc}Y|<
} 7g
R@$(1Z
4&8Gr0C
.s#;s'>g
1h6^>()^
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 >fH=DOz$&
D:k3"
E"S
Fk(JSiU
j1_@qns{
方法getValue()应该被写为: |mdi]TL
D9`0Dr}/2
kb[P\cRa
iA8U Yd3Q
public synchronized Integer getValue(){ ~m|Mg9-
// ’i’ is immutable, so it is safe to return it instead of a copy. KIR'$ 6pn~
return i; f;/QJ
} [V4 {c@
/Q,{?';~
}2K $^uR
c/B'jPt
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: 66^ycZCH
&1+X\c+tb
TKk-;Y=N
qwIa?!8o
?Boolean [((;+B
?Byte wApMzZ(X2y
?Character i)#s.6.D>
?Class LL|7rS|o
?Double ; 7N
Z<k
?Float AuR$g7z
?Integer C3G)'\yL
?Long {R/C0-Q^^
?Short V([~r,
?String kdb(I@6
?大部分的Exception的子类 mv5n4mav
yLsz8j-QJ
mxb06u_
n}s~+USZX
四、常见错误4# :自编代码来拷贝数组 h" H2z1$
k}KC/d9.z
"t^URp3
hJzxbr
<
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: %0? M?Jf
e</$ s
,gL9?Wz
oI^4pwn h
public class Example{ VCtH%v#S;.
private int[] copy; p{PE@KO:
/*** Save a copy of ’data’. ’data’ cannot be null.*/ -s9P8W
public void saveCopy (int[] data){ 7}*6#KRG
copy = new int[data.length]; WM)-J^)BJ
for (int i = 0; i < copy.length; ++i) 9;?UvOI;
copy = data; XUuu-wm:}
} 97K[(KE
} K|DWu8
88c<:fK
Rq[ M29
Q,&