代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 D7JrGaF{
hf0(!C*
9H/R@i[E
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 qtozMa
_$5DK%M}
cz/cY:o)
C;K+ITlJ
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 PnInsf%;
j BBl{
c s*E9
C=@4U}
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 B["+7\c<~
SZ9DT
$fT5Vc]B4
vYb4&VV
一、常见错误1# :多次拷贝字符串 {FKr^)g
"aCB}
oe2*$\?.
adIrrK
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 %=9o'Y,4
+y8Y@e}>
:{iH(ae;
a^@.C5
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: d=%NFCIV
u/6if9B
M9BEG6E9
OG?7(
UJ
String s = new String ("Text here"); ,52 IR[I<T
M!Ao!D[
Ao}<a1f
gN:F5 0
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: 3~Fag1Hp
r(uo-/7z
tlUh8os
/KF@Un_Ow
String temp = "Text here"; E
z}1Xse
String s = new String (temp); DC*MB:c#U
4c 8{AZ
*8?2+)5"
=Q<7[
但是这段代码包含额外的String,并非完全必要。更好的代码为: em3+V
mHW%:a\L
df@N V Ld
7:pc%Ksq
String s = "Text here"; I8)D
ud5}jyJ
b/
\EN)
5G\OINxy
二、常见错误2#: 没有克隆(clone)返回的对象 u%:`r*r
z{
V;bi;
^O@eyP
K=JDl-#!
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: b)KEB9w
dt"/4wCO
,m?V3xvq
$ MH;v_'a
import java.awt.Dimension; tyW}=xs
/***Example class.The x and y values should never*be negative.*/ /tj]^QspS
public class Example{ OHBCanZZ,
private Dimension d = new Dimension (0, 0); dX)aD
$m
public Example (){ } VED~v#.c
\UZGXk
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ GLecBF+>F
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ $RY-yKmi
if (height < 0 || width < 0) 5wx~QV=Hh
throw new IllegalArgumentException(); ;6pB7N
d.height = height; I]W7FZ=o
d.width = width; YAT@xZs-
} 83R s1}*
J+IItO4%
public synchronized Dimension getValues(){ 7\98E&
// Ooops! Breaks encapsulation dGOFSH
return d; L5`k3ap|
} $%DoLpE>
} dL%*;
-F `GZ
wMR,r@}
<dP\vLH_
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: zX*5yNd
Ro9:kEG$
ANBuX6q
oOND]>
Example ex = new Example(); X[dfms;H
Dimension d = ex.getValues(); (c)/&~aE
d.height = -5; JYw?
d.width = -10; DmuQE~DV
IKP_%R8.
JRz)A4P
>Z_;ZMu)
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 1T|")D
,v})
4w ,L
G) KI{D
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 Yh1nXkA!V
2! ,ndLA
SF;\*]["f
E3j`e>Yz
更好的方式是让getValues()返回拷贝: ]vf0 f,F
fK=0?]s}I
MnFrQC
zuN(~>YH
public synchronized Dimension getValues(){ ._tEDY/1m
return new Dimension (d.x, d.y); | 8mWR=9fs
} 2_ u+&7
WcSvw
}(u:K}8
D6@ c|O{Q
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 sjLMM_'
.-HM{6J
+%9Re5R
!Ltx2CB2]
三、常见错误3#:不必要的克隆 Tf5m
YCk
}G{"Mp4
In?+
w=_^n]`R
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: "!Ph
b(|&e
i9Bh<j>:J
sS{Co8EJn
/*** Example class.The value should never * be negative.*/ x#SE%j?
public class Example{ =PA?6Bm
private Integer i = new Integer (0); ~Sb)i f
public Example (){ } m,kYE9{
fWA#n
/*** Set x. x must be nonnegative* or an exception will be thrown*/ ,\
1X\
public synchronized void setValues (int x) throws IllegalArgumentException{ C;:=r:bth
if (x < 0) hfIP
throw new IllegalArgumentException(); *FEJ5x
i = new Integer (x); _XP}fx7$C
} q)?!]|pZ
q M_c-^F
public synchronized Integer getValue(){ 1ED7.#g
// We can’t clone Integers so we makea copy this way. _y6iR&&x
return new Integer (i.intValue()); hC"'cUrcN
} {06-h %qr
} 8 0nu^_
V0A> +
qm/>\4eLt
zx8@4?bK
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 h`9 & :zr
|;|r[aU
V /\Y(Mxc
/8]K}yvR
方法getValue()应该被写为: FsOJmWZ
`"ks0@^U
7&P70DO
M]Vi]s
public synchronized Integer getValue(){ Ppl :_Of
// ’i’ is immutable, so it is safe to return it instead of a copy. p9G+la~;VM
return i; V[%IU'{:
} go=xx.WJ
)d3C1Pd>
'$p`3Oqi
C=Fu1Hpb
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: m#[c]v{
hunlKIg
aUa+]H[
U9PI#TX
&O
?Boolean C[Q4OAFG
?Byte v~AshmP
?Character eV(.\Lj
?Class rIB./,
?Double )O*h79t^Q
?Float '-f` 5 X
?Integer OB=bRLd.IR
?Long J80&npsO
?Short G2n.NW#d4
?String %LHt{:9.
?大部分的Exception的子类 kc$W"J@
,H?e23G
wO!>kc<
so.}WU
四、常见错误4# :自编代码来拷贝数组 dj] O
IY!.j5q8
U3]/ NV*
Lfa&JKd
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: 1xkk5\3]
L?a4>uVY
P^Og(F8;
M/F<W!
public class Example{ =7kn1G.(
private int[] copy; 46l*ui_
/*** Save a copy of ’data’. ’data’ cannot be null.*/ G]xN#O;
public void saveCopy (int[] data){ 8(NS;?
copy = new int[data.length]; kAUL7_>6X
for (int i = 0; i < copy.length; ++i) ,=@WE>ip
copy = data; 32j#kJ W
} 1=>b\"P#E
} .3XSF$;
iV@\v0k
$Sm iN'7;
[zP}G?(
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: ZV5IZ&V!
.;bU["fn)
sy(bL_%
(yEU9R$I"
void saveCopy (int[] data){ 1z,P"?Q
try{ ZX+0{E8a
copy = (int[])data.clone(); bFA
lC
}catch (CloneNotSupportedException e){ n
sN n>{
// Can’t get here. h{~GzrL*
} 8<6@O
} ei]Q<vT6
&R<K>i
mH<|.7~0
hf)RPG&
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: 6khm@}}
Y!(w. G
7<8'7<X
1]8Hpd
static int[] cloneArray (int[] data){ -@'RYY=
try{ `i~J0#P
return(int[])data.clone(); ^h`rA"F\
}catch(CloneNotSupportedException e){ 7D8 pb0`;J
// Can’t get here. 5Ktll~+:#
} "x:-#2+h
} WdJeh:h
.o<9[d"
mxc^IRj
I.2>d_^<
这样的话,我们的saveCopy看起来就更简洁了: 0(uba3z
0@&;JMh6<
rb>2l3g*
8-O:e
void saveCopy (int[] data){ `Y '-2Fv
copy = cloneArray ( data); 8l?@ o
} Cq\{\!6[
-HFyNk]>
Us>n`Lj@
BSEP*#s
五、常见错误5#:拷贝错误的数据 `;Fs
; ?,'jI*1
G1,u{d-_
d5W=?
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: PXDJ[Oj7(0
&b19s=Z,
<j_
:|ytw=3>
import java.awt.Dimension; {]y!2r
/*** Example class. The height and width values should never * be H Mfhe[A?
negative. */ URyY^+s
public class Example{ !-B|x0fs
static final public int TOTAL_VALUES = 10; -K5u5l}
private Dimension[] d = new Dimension[TOTAL_VALUES]; o-AAx#@
public Example (){ } aQ1n1OBr
O;#0Yg
/*** Set height and width. Both height and width must be nonnegative * or an exception will be thrown. */ <$nMqUu0
public synchronized void setValues (int index, int height, int width) throws IllegalArgumentException{ lYrW"(2
if (height < 0 || width < 0) Q>/[*(.Wd
throw new IllegalArgumentException(); Hn?v/3
if (d[index] == null) s:sk`~2<gd
d[index] = new Dimension(); ?)/H8n
d[index].height = height; ;q2e[ y
d[index].width = width; _^w^tfH]
} 8qq'q"g
public synchronized Dimension[] getValues() `]l[p+DO
throws CloneNotSupportedException{ f87lm*wZ
return (Dimension[])d.clone(); L?hWH0^3
} kc"SUiy/
} F[oTc^dr
tp +H]H3
}?%5Ae7l,
z
Q11dLjs
这儿的问题在于getValues()方法仅仅克隆了数组,而没有克隆数组中包含的Dimension对象,因此,虽然调用者无法改变内部的数组使其元素指向不同的Dimension对象,但是调用者却可以改变内部的数组元素(也就是Dimension对象)的内容。方法getValues()的更好版本为: 0hju@&