代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 nBd;d}LD
+9t@eHJT1
fsu'W]f
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 ]v#Q\Q8>
uzOZxW[e
tfO
_b5g
9ZwhCsO
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 Ru/3>n
i*3'O:Gq
a[!':-R`s
a"EXR-+8
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 MWB?V?qPSC
{v(3[7
8@!SM
ouujd~b+
一、常见错误1# :多次拷贝字符串 G8@%)$A
F -m1GG0s
pdM|dGq^
|"arVde
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 (Xx
@_
zT@vji%Y
mYZH]oo
D*b>
l_
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: xJ4T7 )*
iVA_a8}
Wjp<(aY[
{az8*MR=X
String s = new String ("Text here"); ~dv
C$
5}f$O
1K!7FiqY
.d;/6HD[y
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: kC)dia{$
Xo
P]PR`cQ
lw7wvZD
3=z'Ih`
String temp = "Text here"; ,%u\2M
String s = new String (temp); |yS4um(w
@E1N9 S?>
,MdCeA%`
v+Hu=RZE
但是这段代码包含额外的String,并非完全必要。更好的代码为: r*$KF!-dg
f?)qZPM
=^6]N~*,D
/IgTmXxxj
String s = "Text here"; ~&g:7f|X
Zscmc;G
%"o4IYV#
Mb-C DPT
二、常见错误2#: 没有克隆(clone)返回的对象 tUzuel*
&_ber ad
#+XKfumLk
f"/NY6
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: I;=}@]9
p0b&CrALx
uu HWN|
tP`,Egf"g
import java.awt.Dimension; >LLFe~9`g
/***Example class.The x and y values should never*be negative.*/ h)sc-e
public class Example{ s:M:Ff
private Dimension d = new Dimension (0, 0); VXC_Y
public Example (){ } *<J**FhcMu
]^dXB0
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ ?(F~9V
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ Ltc>@
if (height < 0 || width < 0) RP6QS )|
throw new IllegalArgumentException(); q0Fy$e]u
d.height = height; WKP=[o^
d.width = width; Fm:Ri$iT
} P'zA=Rd&~>
97Whn*
public synchronized Dimension getValues(){ k9a-\UIMet
// Ooops! Breaks encapsulation VEJ Tw
return d; TJ#<wIiX
} e<q;` H
} %ePInpb
th !Gc
RE*;nSVFt
bjbm"~
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: w}+jfO9
d^4!=^HN
8g$pfHt|e
233jT@Z
Example ex = new Example(); uV{cvq$jy
Dimension d = ex.getValues(); y/E%W/3
d.height = -5; q^EG'\<^
d.width = -10; /1Ndir^c
RxcX\:
s(-$|f+s
h%Bp%Y9
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 0D=6-P?^W
F@[l&`7
(|<}q-wO
G3m+E;o1
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 zGA#7W2?0
1Z|q0-Dw0
h
~v8Q_6
L -<!,CASW
更好的方式是让getValues()返回拷贝: ZxY%x/K
Ee^2stc-
[WuN?H
-:Yx1Y3
[
public synchronized Dimension getValues(){ </Ja@%
return new Dimension (d.x, d.y); |G }qY5_
} 5Q
=o.wf
QrDI$p7;'
*$Bx#0J8
qo/`9%^E?
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 iU5M_M$G
L`3x0u2
b@"#A8M
1)w^.8f
三、常见错误3#:不必要的克隆 /U+0T>(HS
Zg_ fec~6q
0.qnbDw_
ZDMS:w.'T
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: AfB,`l`k
s&TPG0W
RX \%R
Igrr"NuDZ
/*** Example class.The value should never * be negative.*/ 2XNO*zbve
public class Example{ a/^ojn
private Integer i = new Integer (0); 3P N<J
public Example (){ } Bz!SZpW(M
8\P!47'q
/*** Set x. x must be nonnegative* or an exception will be thrown*/ y38x^fuYJ~
public synchronized void setValues (int x) throws IllegalArgumentException{ J4"?D9T3G
if (x < 0) &C6Z-bS"
throw new IllegalArgumentException(); R0HzNk
i = new Integer (x); )T&ZiHIJ3
} gd#+N]C_
E.45s? r
public synchronized Integer getValue(){ `r+zNJ@q
// We can’t clone Integers so we makea copy this way. 4zzJ5,S 1
return new Integer (i.intValue()); gLy1*k4
} Z^wogIAV
} Lk#8G>U
"V'<dn
e#BxlC
EIug)S~
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 sYE|
v)1@Ew=Y%
h&}z@
7wKT:~~oS3
方法getValue()应该被写为: VN]70LFz*i
L.X"wIs^
8Mg wXH
SI\
O>a9{
public synchronized Integer getValue(){ 21_sg f?
// ’i’ is immutable, so it is safe to return it instead of a copy. &!N9.e:-]
return i; POB6#x
} m?GBvL$
NpI "XQ
!-B$WAV
B:oE&Ahh{
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: r^zra|]
;iq H:wO
{ 0?^ $R8j
ON [F
?Boolean #l 7(WG
?Byte sYa;vg4[
?Character <Ukeq0
?Class [+;>u|
?Double Zm x[:-
?Float x<d2/[(}mT
?Integer C@b-)In
?Long W<Ri(g-
?Short VRE[vM'
?String v-(dh5e`
H
?大部分的Exception的子类 PJ-g.0q
uSQRI9/ir2
@;;3B
iewwL7
四、常见错误4# :自编代码来拷贝数组 pmfL}Dn
\&BT#8ELG
c'md)nD2M
0fE?(0pBj
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: !KC4[;Y
yi.GD~69
SR>(GQ,m0;
9w!PA-) L
public class Example{ i4Da 'Uk
private int[] copy; E\1e8Wyh
/*** Save a copy of ’data’. ’data’ cannot be null.*/ 1 EL#T&
public void saveCopy (int[] data){ 4LXC;gZ
copy = new int[data.length]; fD3>g{
for (int i = 0; i < copy.length; ++i) F81Kxcs
copy = data; U5:5$T,C
} =j^>sg]
} 2=,O)g
Fe1^9ja
1$_|h@
=C#22xqQ.
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: 5Sz&j
Lrjp
z"\<GmvB
k5g vo
void saveCopy (int[] data){ j4%\'xj:
try{ -[}Ah NYK
copy = (int[])data.clone(); &iO53I^r/
}catch (CloneNotSupportedException e){ zD@RW<M
// Can’t get here. NjFlV(XT}
} o)WzZ,\F^J
} C23Gp3_0/
AGhr(\j
`D $ "K1u
Y>2oU`ly,
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: QCJf
VXPsYR&
P" aw--f(
D4jZh+_|S
static int[] cloneArray (int[] data){ lw`$(,
try{ ]u|5ZCv0
return(int[])data.clone(); {VE1c'E"V?
}catch(CloneNotSupportedException e){ +<Y1`kV)
// Can’t get here. &8HJ4Vj2
} +8}8b_bgH
} *RD<*l
`3^%ft~l
3[UaK`/1C
7*eIs2aY
这样的话,我们的saveCopy看起来就更简洁了: _ |G') 9
LS/ZZAN u
Bo4iX,zu
AzMX~cd
void saveCopy (int[] data){ RDxvN:v
copy = cloneArray ( data); ?$@E}t8g\
} D\Fu4Eg
t vp kc;
Dc9Fb^]QOG
W~& QcSWqD
五、常见错误5#:拷贝错误的数据 [{PmU~RMYf
Iuve~ugO
3Vk<hBw2
xzw2~(lo
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: 0zpA<"S
b"(bT6XO!
I:UN2`*#
\Icd>>)*
import java.awt.Dimension; !DBaC%TGC
/*** Example class. The height and width values should never * be GLA4O)
negative. */ ~p { fl?
public class Example{ /Py`a1
static final public int TOTAL_VALUES = 10; :M$8<03>F
private Dimension[] d = new Dimension[TOTAL_VALUES]; 3oC^"723
public Example (){ } }F-,PSH
Ml
TOsHb+Uv
/*** Set height and width. Both height and width must be nonnegative * or an exception will be thrown. */ m!WDXt
public synchronized void setValues (int index, int height, int width) throws IllegalArgumentException{ 8bX?HeYrr
if (height < 0 || width < 0) PEMuIYm$
throw new IllegalArgumentException(); T,uJO<
if (d[index] == null) ]t-B-(D
d[index] = new Dimension(); 72\o6{BiC
d[index].height = height; & &:ZY4`
d[index].width = width; 7&2CLh
} _]M:
public synchronized Dimension[] getValues() k&= iye(
throws CloneNotSupportedException{ G(hzW%P
return (Dimension[])d.clone(); (,['6k<
} iza.' Mm~
} FTh/1"a
VrKFpFd
YR.f`-<Z
Mb+CtI_'
这儿的问题在于getValues()方法仅仅克隆了数组,而没有克隆数组中包含的Dimension对象,因此,虽然调用者无法改变内部的数组使其元素指向不同的Dimension对象,但是调用者却可以改变内部的数组元素(也就是Dimension对象)的内容。方法getValues()的更好版本为: uDMyO<\
SJO^.[
2 W Wr./q
@rlL'|&X*
public synchronized Dimension[] getValues() throws CloneNotSupportedException{ \GCT3$
Dimension[] copy = (Dimension[])d.clone(); 72sBx3 ;
for (int i = 0; i < copy.length; ++i){ Q oWjC
// NOTE: Dimension isn’t cloneable. 4EFP*7X
if (d != null) ;'oi7b
copy = new Dimension (d.height, d.width); 84c[ Z
} 7jPn6uz>w
return copy; y*j8OA.S
} 78O5$?b;#
;f[@zo><r
H8$";T(I
|"Fm<