代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 31}6dg8?n
86i =N_
@RC_Ie=#)
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 eNNK;xXe#
cG<?AR?wDT
A[oRi}=
<MYD`,$yu
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 xc!"?&\*
Bn.8wMB
T|u)5ww%
8ViDh
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 @M1U)JoQ
QAR<.zXvP
0wx`y$~R
>7n(*M
一、常见错误1# :多次拷贝字符串 0@
-LV:jU
^71sIf;+
ZjzQv)gZ
:G!Kaa,r
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 [mm5?23g
gw H6r3=y(
51~:t[N|
]8RcZn
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: <+6)E@Y
0j*8|{|
P63f0F-G
BUtXHD
String s = new String ("Text here"); !Ed';yfz\(
[u<1DR
k?_Miqr
!a
/
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: km *$;Nli
y'(;!5w
_ W$4Qn+f
]86U-`p
String temp = "Text here"; YYhRdU/g
String s = new String (temp); 3o z]
>]Y`-*vw&
_KKG^
u<
|W?x6]~.R
但是这段代码包含额外的String,并非完全必要。更好的代码为: -\>Xtix^-c
+YP,LDJ!v
zE<}_nA
5)0R:
String s = "Text here"; 90Q}9T\
p 5P<3(
y6$5meh.T
'y@0P5[se
二、常见错误2#: 没有克隆(clone)返回的对象 }E*#VA0/nY
2nk}'HBe
}y'KS:Jb
OD{Rh(Id
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: H$Q_K<V
i: 1V\q%
7,Nd[
oL*7
o: qB#8X
import java.awt.Dimension; <wa}A!fu
/***Example class.The x and y values should never*be negative.*/ +[:}<^p?cG
public class Example{ eQA89 :j,
private Dimension d = new Dimension (0, 0); ^IY1^x
public Example (){ } uYF_sf
H~fZA)W 4Y
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ +tl&Jjdm
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ 5ZUqCl(PX)
if (height < 0 || width < 0) ^,@Rd\q
throw new IllegalArgumentException(); 'xhX\?mD
d.height = height; 't2"CPZ
d.width = width; UfXqcyY(
} YaDr6)
6*Rz}RQ
public synchronized Dimension getValues(){ Gw$U0 HA[,
// Ooops! Breaks encapsulation cW%F%:b
return d; qa2QS._m
} +!CG'qyN>
} u<:RSg
aOETms w
(Jy7
=T!iM2
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: Kb#py6
vQ$ FMKz7
vA*!82
{O[a+r.n
Example ex = new Example(); gM '_1zs
U
Dimension d = ex.getValues(); )L<NW{
d.height = -5; C5$1K'X@
d.width = -10; [g`P(?
>iDV8y
Y7{IF X
ez@`&cJ7
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 $<OX\f%
KQ9~\No]
X3P~z8_
M| :wC
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 AQw1,tGV
oYG9i=lZ
_8v8qT}O~4
kl,I.2-
更好的方式是让getValues()返回拷贝: ~xerZQgc
v[E*K@6f
&!SdO<agZ
E3@G^Y
public synchronized Dimension getValues(){ -W38#_y/\
return new Dimension (d.x, d.y); `q@5d&d`j
} rVB,[4N
U-&dn%Sq
UzTFT:\
mnh>gl!l
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 &mXJL3iN
gi\2bzWkbX
XHKiz2Pc1
<=[,_P6|
三、常见错误3#:不必要的克隆 V3r1|{Z(
VRV*\*~$
6[b'60CuZL
FOV%\=Hl
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: pBl'SQccp
dCc"Qr[k
o
b;]
s\O4D*8
/*** Example class.The value should never * be negative.*/ s&&8~
)H
public class Example{ q#s:2#=
private Integer i = new Integer (0); $mgamWNE8w
public Example (){ } 4gD;X NrV
WX~:Y,l+u
/*** Set x. x must be nonnegative* or an exception will be thrown*/ m{#?fR=9
public synchronized void setValues (int x) throws IllegalArgumentException{ Bk)E]Fk|
if (x < 0) :)JIKP%$\)
throw new IllegalArgumentException(); -nK\+bTL}
i = new Integer (x); )T0%<(J
} B8Vhl:p
?`T0zpC
public synchronized Integer getValue(){ 3(o}ulp
// We can’t clone Integers so we makea copy this way. k]>1@t
return new Integer (i.intValue()); g}@W9'!
} dlv1liSXL5
} Q'
b@5o
&JUHm_wd&S
6&9}M Oc
E^s<5BC;
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 t@(:S6d
yV.E+~y
CU`yi.)T{
]9A@iA
方法getValue()应该被写为: SHow~wxw
vQH6CB"
C\`*_t
|(eRv?Qy@
public synchronized Integer getValue(){ U3t$h
// ’i’ is immutable, so it is safe to return it instead of a copy. ] S0tK
return i; ioW&0?,Ym
} Z:(Zy
]nIH0k3y
;9Sb/
Qr.SPNUFK
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: y/vGt_^;3<
xcHuH-}
3aY^6&
L$zB^lSM
?Boolean w|,BTM:e
?Byte cM?i _m
?Character F=g+R~F
?Class n9H4~[JiC
?Double ITssBB9
?Float w. c]
?Integer F`Ld
WA
?Long 90Sp(
?Short 0FAe5
BE7
?String 9 $&$Fe
?大部分的Exception的子类 -bP_jIZF;g
uN;]Fv@Z
Ss~yy0
k>.n[`>$6|
四、常见错误4# :自编代码来拷贝数组 hU|TP3*
bC h
Pd8zdzf{
Cs2F/M'
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: dbsD\\,2%N
huat,zLS
^'G,sZ6'Nh
3:g~@PB
public class Example{ *Y]()#?Gr
private int[] copy; UFl+|wf
/*** Save a copy of ’data’. ’data’ cannot be null.*/ B:]%Iu|
public void saveCopy (int[] data){ nj4G8/U-q
copy = new int[data.length]; `#3FvP@&
for (int i = 0; i < copy.length; ++i) zgn~UC6&
copy = data;
wa%;'M&
} s&)>gE\
} N<e72x
)|N_Q}
X3zpU7`Av+
<|>7?#s2=
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: !mIr_d2"
B,(zp#&yB
xgq
`l#
?}ly`Js
void saveCopy (int[] data){ EQ%,IK/
try{ IBm"VCg{Ew
copy = (int[])data.clone(); a+=.(g
}catch (CloneNotSupportedException e){ 6F:<c
// Can’t get here. 6d{&1-@>
}
3PUyua'
} J.Fy0W@+k4
O.z\
VI2f
c`O(||UZT
s ;2ih)[
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: ,)35Vi;.
|w+N(wcJ
TjY-C m
?2nF1>1
static int[] cloneArray (int[] data){ T=,A p a
try{ kK~,?l
return(int[])data.clone(); 2>*b.$g
}catch(CloneNotSupportedException e){ []l2
`fS#
// Can’t get here. T*{nf
} 0BrAgv"3a_
} >_|$7m.?n[
<44A*ux
I%M"I0FV
gZ@z}CIw'
这样的话,我们的saveCopy看起来就更简洁了: P$#{a2
u3vM !
Ohn?>qQ
QWI)Y:<K/
void saveCopy (int[] data){ Pr'Ij
copy = cloneArray ( data); D~b_nFD
} SIZZFihcYh
F[)5A5+:Y
:^rt8>~
0s!';g Q
五、常见错误5#:拷贝错误的数据 3eERY[
y$y!{R@
*!^l
ZpF
{RC&Ub>
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: MqjdW
N|e#&
<O0.q.
^v5<* uf%m
import java.awt.Dimension; '.{_
7U
/*** Example class. The height and width values should never * be -dS@l'$
negative. */ Q<>b3X>O
public class Example{ Z=dM7 Lj*
static final public int TOTAL_VALUES = 10; vYg>^!Q
private Dimension[] d = new Dimension[TOTAL_VALUES]; yJ4ZB/ZQ
public Example (){ } $X,dQ]M
&embAqW:
/*** Set height and width. Both height and width must be nonnegative * or an exception will be thrown. */ ^X;p8uBo
public synchronized void setValues (int index, int height, int width) throws IllegalArgumentException{ b\S~uFq6
if (height < 0 || width < 0) w:+&i|H >
throw new IllegalArgumentException(); hgK
4;R
if (d[index] == null) N/78Ub
d[index] = new Dimension(); JbAmud,
d[index].height = height; mXs.@u/
d[index].width = width; .: k6Kg
} ^^B~v<uK
public synchronized Dimension[] getValues() H[RX~Xk2E
throws CloneNotSupportedException{ -B&
Nou
return (Dimension[])d.clone(); {fJCj152.
} @{"?fqo
} Bo$dIn2_
mKsJ[)#.
ejc>
t:"3MiM=c
这儿的问题在于getValues()方法仅仅克隆了数组,而没有克隆数组中包含的Dimension对象,因此,虽然调用者无法改变内部的数组使其元素指向不同的Dimension对象,但是调用者却可以改变内部的数组元素(也就是Dimension对象)的内容。方法getValues()的更好版本为: (H8JV1J
@`qB[<t8:<
=Z ql6D
i5aY{3!
public synchronized Dimension[] getValues() throws CloneNotSupportedException{ O(6j:XD
Dimension[] copy = (Dimension[])d.clone(); k [LV^oEg
for (int i = 0; i < copy.length; ++i){ c \;_jg
// NOTE: Dimension isn’t cloneable. iK=QP+^VN
if (d != null) 6Yl+IP];i
copy = new Dimension (d.height, d.width); adPd}rt;
} &M: