代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 V:t{mu5j
pm9sI4S
[OPF3W3z
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 -1hCi!
_J2?B?S/j
Z6M
qcAJ3j
{|0YcL
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 9*~";{O.Oa
*yHz#u'
R4 b!?}d
jq#`cay!
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 DGTE#?'(
7'8G,|&:*
74NL)|M
./zzuKO8XK
一、常见错误1# :多次拷贝字符串 L)<~0GcP
M%$ITE
h'GOO(
uwi.Sg11
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 4Q1R:Ra
X]2x0
,*9gy$
zgGJ<=G.
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: YADXXQ"
xEq? [M
O` !XW8
ml)\R L
String s = new String ("Text here"); sUQ
Q/F6
,*\s
TtWzjt
o:*$G~. k
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: V@y&n1?6
(+xT5 2
jUZ$vyT
X,lhVT
|
String temp = "Text here"; t+pA9^$[`
String s = new String (temp); `WMU'ezF
Z;tWV%F5
\R-'<kN.*
JSylQ201
但是这段代码包含额外的String,并非完全必要。更好的代码为: {md5G$*%
MLiaCG;
hhWy-fP#
p Djt\R<f
String s = "Text here"; y\CxdTs
-s)h
?D
wSM(!:on5
?I+$KjE+
二、常见错误2#: 没有克隆(clone)返回的对象 8$ RiFD,
0"GLgj:9
$Fi1Bv)
+BhJske
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: S{)K_x
<gFisc/#r
&Cm]*$?
"&`>+Yw
import java.awt.Dimension; m;1/+qs0
/***Example class.The x and y values should never*be negative.*/ 9s7TLT k
public class Example{ 6Z=Qs=q
private Dimension d = new Dimension (0, 0); e_l|32#/
public Example (){ } (!efaj
E{^W-
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ a3A3mBw
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ e7-IqQA{3C
if (height < 0 || width < 0) v>mK~0.$
throw new IllegalArgumentException(); u"wWekB
d.height = height; %h,&N D
d.width = width; (F3R!n
} @A`j Wao
c/j+aj0.v
public synchronized Dimension getValues(){ 6kAGOjO
// Ooops! Breaks encapsulation @w(|d<5l:L
return d; 1*6xFn
} z6,E}Y
} H?ug-7k/
'.gi@Sr5
pp{p4Z
DvLwX1(l
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: +7AH|v8
bI(8Um6m
<$Sl%DoS
2AMb-&po&f
Example ex = new Example(); QctzIC#;k
Dimension d = ex.getValues(); 35x]'
d.height = -5; n0EW
U,1
d.width = -10; 1_;{1O+B
*(5T?p[7
~4twI*f
C9""sVs
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 G;[O~N3n.
~6O~Fth
R[*n3
wB
!g)rp`?
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 r1}1lJ>7H
h qhX
<9:~u]ixt
i\DU<lD5VN
更好的方式是让getValues()返回拷贝: L`wr~E2u
Br{(sL0e
P*U^,Jh<
IGlyx'\_
public synchronized Dimension getValues(){ Y" rODk1
return new Dimension (d.x, d.y); ZSD7%gE<D
} oQ*LP{M
a0 PU&o1EF
\[)SK`cwd
.yD
6$!6
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 l]Ym)QP
5j0 Ib>\
!h<O c!9
}s6Veosl
三、常见错误3#:不必要的克隆 1A#/70Mo
OQKc_z'"
wa`c3PQGu
>p;&AaXkoG
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: ;KEie@Ry
f|F=)tJO
JY;u<xl
|B'4wF>
/*** Example class.The value should never * be negative.*/ SXvflr] =m
public class Example{ -XK;B--c
private Integer i = new Integer (0); (plT/0=^t
public Example (){ } EAxdF
u
WB<MU:.Vc
/*** Set x. x must be nonnegative* or an exception will be thrown*/ yx*<c#Uf
public synchronized void setValues (int x) throws IllegalArgumentException{ ty4R2LnC
if (x < 0) ro3%VA=V
throw new IllegalArgumentException(); #N~1Ye
i = new Integer (x); nG{o$v_|
} 5~im.XfiVx
Q00v(6V46
public synchronized Integer getValue(){ QP%Hwt]+
// We can’t clone Integers so we makea copy this way. oe3=QE
return new Integer (i.intValue()); bu $u@:q 6
} Zg>]!^X8
} J~oxqw}
2dHsM'ze
o1*P|.`
3 p?nQ
O)L
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 \DBEs02
fOdqr
^Pu:&:ki
$d4&H/u^
方法getValue()应该被写为: ,`k6@4
/(u? k%Q
=K|#5p`
]l +<-
public synchronized Integer getValue(){ N^PkSf[)h5
// ’i’ is immutable, so it is safe to return it instead of a copy. @$;8k }
return i; CF\wR;6k
} ;_|4c7
jt9- v-
U}k@%m,
oR,zr
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: _iEnS4$A8
;volBfv
}; M@JMu,
rwio>4=
?Boolean L%<]gJtrO
?Byte ZJF+./vN
?Character `g)
?Class Tr|PR t
?Double euRKYGW
?Float GRVF/hPn
?Integer W\5 -Yg(@
?Long mpVD;)?JmM
?Short %;= ?r*]
?String 3;wiwN'
?大部分的Exception的子类 wPu.hVz
v ;Q*0%~
fR+{gazk
n
l?V#;
四、常见错误4# :自编代码来拷贝数组 A"s?;hv\fS
gu~R4@3
B.;@i;7L
x*=m'IM[
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: @uN+]e+3
"USzk7=&.
%6Vb1?x
VlSM/y5
public class Example{ jvD_{r
private int[] copy; z 0zB&}
/*** Save a copy of ’data’. ’data’ cannot be null.*/ i_l{#*t
public void saveCopy (int[] data){ Gm9
copy = new int[data.length]; #h
U4gX,
for (int i = 0; i < copy.length; ++i) 8O60pB;4
copy = data; oSf`F1;)HQ
} *PB /I4>{
} ],~[ ^0
-1NR]#P'
$<C",&
iQT0%WaHl
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: 2Ub-ufkU
Li0+%ijM
l{ql'm
98^7pa
void saveCopy (int[] data){ @]8flb
)T
try{ _3wK: T{:
copy = (int[])data.clone(); b`j9}tZ
}catch (CloneNotSupportedException e){ T<b*=i
// Can’t get here. yJO Jw o^
} $cwmfF2C
} Kng=v~)N'
o"z;k3(i$7
S')DAx
hA1B C3
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: 6#K.n&=*
{<gX~./]c
5L~lF8
IMMsOl
static int[] cloneArray (int[] data){ &2[Xu4*
try{ L:mE)Xq2
return(int[])data.clone(); N#)Klq87z
}catch(CloneNotSupportedException e){ 3O1Lv2)_
// Can’t get here. 2EN}"Du]mj
} U:eX^LE7
} <SOG?Lh~
I@O9bxR?
*8;<w~
Mqk|H~l5c
这样的话,我们的saveCopy看起来就更简洁了: 9 BU#THDm
Eyk:pnKJb
e Y^zs0
-%P}LaC<
void saveCopy (int[] data){ <exyd6iI
copy = cloneArray ( data); >SziRm>Y7
} 9=/4}!.
\ Ucv<S
cXf/
'+j;g
五、常见错误5#:拷贝错误的数据 llh
+r?
|M
t2
uTPAf^|
:pz@'J
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: i O? f&u
`,/5skeJ
?$tD
L]"$dF
import java.awt.Dimension; qdKqc,R1{
/*** Example class. The height and width values should never * be 3XQe? 2:<
negative. */ 5 $$Cav
public class Example{ "AKr;|m
static final public int TOTAL_VALUES = 10; \v<S:cTf
private Dimension[] d = new Dimension[TOTAL_VALUES]; AcH!KbYf
public Example (){ } G/fBeK$.
uV@'898%5
/*** Set height and width. Both height and width must be nonnegative * or an exception will be thrown. */ >=:mtcph
public synchronized void setValues (int index, int height, int width) throws IllegalArgumentException{ M6qNh`+HO
if (height < 0 || width < 0) F1B/cd
throw new IllegalArgumentException(); Q*1'k%7
if (d[index] == null) 8\:>;XG6f
d[index] = new Dimension(); 7t}s5}Z 4
d[index].height = height; k{b|w')
d[index].width = width; ?1Vx)j>|
} T"C.>G'[B
public synchronized Dimension[] getValues() gGBRfq>
throws CloneNotSupportedException{ aK|
return (Dimension[])d.clone(); 5!$sQ@#}D
} +opym!\
} O7LJ-M
-b8SaLak
)D'#>!Y
be]/ROP>H
这儿的问题在于getValues()方法仅仅克隆了数组,而没有克隆数组中包含的Dimension对象,因此,虽然调用者无法改变内部的数组使其元素指向不同的Dimension对象,但是调用者却可以改变内部的数组元素(也就是Dimension对象)的内容。方法getValues()的更好版本为: 3&{6+ A
'W54 T
F s=x+8'M
vkR~nIp
public synchronized Dimension[] getValues() throws CloneNotSupportedException{ !Y7$cU &
Dimension[] copy = (Dimension[])d.clone(); y!R9)=/M
for (int i = 0; i < copy.length; ++i){ qxHn+O!h
// NOTE: Dimension isn’t cloneable. kRb JK
if (d != null) p}/D{|xO
copy = new Dimension (d.height, d.width); aUc#,t;Qd
} O\Z!7UQ$
return copy; /n>vPJvz
} QkD]9#Id&
*14:^neoI
-O=xgvh"
T<Qa`|5>
在克隆原子类型数据的多维数组的时候,也会犯类似的错误。原子类型包括int,float等。简单的克隆int型的一维数组是正确的,如下所示: v''J@ F7
{YrA[9
i!3*)-a\~`
oAB:H\
public void store (int[] data) throws CloneNotSupportedException{ Le bc@,
this.data = (int[])data.clone(); r)Zk- !1
// OK `/N={
} t:P]bp^#
.H qJ)OH
[P ;fv
C0Fd<