代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 Oga/
4DY\QvW5
ql,k 5.l
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。
l);M(<
4%L`~J4 wr
$$7Mq*a>
p!5oz2RK
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 1eue.iuQ
' b41#/-
rEwEdyK
O>]I!n`!!A
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 ETk4I"
A&%vog]O
N"d
M+
QkbXm[K.Z
一、常见错误1# :多次拷贝字符串 uan%j]|q%
aewVq@ngq!
0k"n;:KM8
qcau(#I9.
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 )xgOl*D
K=|x"6\
e1$T%?(&[
GSzb
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: 7:7i}`O
E^kB|; Ki
\"!Fw)wj
,PH ;j_
String s = new String ("Text here"); OwXw9
&AR@5M u
S<do.{|p[
1<y(8C6
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: y[M<x5
=7{n 2
WGwpryaya
v x qsK
String temp = "Text here"; eXo7_#
String s = new String (temp); d:08@~#
UI S\t^pJD
fFu+P<?"
w1q-bIU
但是这段代码包含额外的String,并非完全必要。更好的代码为: %M"rc4Xd
V$U#'G>m
[(Z{5gK
I8*_\Ez
String s = "Text here"; cXM4+pa=%
mS)|i+5
nf#;]FijB
8nzDLFxp_
二、常见错误2#: 没有克隆(clone)返回的对象 m-V_J`9"
>bQ'*!
a,<l_#'
l":\@rm`
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: M<h2+0(il
fmqHWu*wG
z%ZAN-
TmI~P+5w
import java.awt.Dimension; \F`%vZrKR
/***Example class.The x and y values should never*be negative.*/ VK>ZH^-
public class Example{ QD6<sw@]P
private Dimension d = new Dimension (0, 0); klkshlk d
public Example (){ } h-)tWJ c
*F:f\9
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ SUv(MA&
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ XcN"orAo
if (height < 0 || width < 0) ft |W
throw new IllegalArgumentException(); alr'If@7
d.height = height; .gZ1}2GF=
d.width = width; )4h4ql W
} mn5y]:;`
Rr>nka)U
public synchronized Dimension getValues(){ < cNJrer
// Ooops! Breaks encapsulation Uwj|To&QR
return d; Y!!w*G9b
} :SBB3G)|
} h=<x%sie
,x (?7ZW>
W(~7e?fO
862e
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: )96tBA%u
d v8q&_
=;HC7TUM&
/6Bm
<k%
Example ex = new Example(); BqoGHg4iq
Dimension d = ex.getValues(); PBkTI2 v
d.height = -5; i
n$~(+
d.width = -10; pNt,RRoR
"rHcsuSEw
5?] Dn k.o
=Oyn<
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 a!?JVhD&
0Y|"Bo9k
},eV?eGj
t,D7X1W
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 f2*e&+LjTP
Pk2=*{:W
Y6+/_$N4|
QOT|6)Yb
更好的方式是让getValues()返回拷贝: &/+LY_r'<I
V -X*e
\mp2LICQg
}BFX7X
public synchronized Dimension getValues(){ 7+'&(^c
return new Dimension (d.x, d.y); $[S)A0O
} gUa-6@
2!kb?
!xD$U/%c
h#:_GNuF
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 ?^}
z
Ef)v("'w
zWO!z=
kleE\8_
三、常见错误3#:不必要的克隆 |K.J@zW
s~i73Qk/
n{*A<-vL
{JGXdp:SB
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: #[odjSb
$j(laD#AR
]H {g/C{j
QgF2f/;!
/*** Example class.The value should never * be negative.*/ #MyF 1E
public class Example{ $cSmub ZK
private Integer i = new Integer (0); }uFV\1
public Example (){ } }5b,u6
KA/~q"N
/*** Set x. x must be nonnegative* or an exception will be thrown*/ SJ7-lben3
public synchronized void setValues (int x) throws IllegalArgumentException{ +,q#'wSQG
if (x < 0) RKb{QAK!v
throw new IllegalArgumentException(); ->9waXRDz)
i = new Integer (x); tO}Y=kZa{
} NG+%H1!$_
}q?*13iy(
public synchronized Integer getValue(){ >1*Dg?/=S
// We can’t clone Integers so we makea copy this way. ^ }k qAmr
return new Integer (i.intValue()); M&SY2\\TB
} 2Q;g|*]
} KFhnv`a.0
z(dDX%k@
Nu,t,&B
,R$U(,>_0
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 =v !'?
GeFu_7u!|
U-.A+#<IT9
hd>_K*oH
方法getValue()应该被写为: /A82~
TQL_K8k@_
P;bOtT --
p!/ *(TT
public synchronized Integer getValue(){ .VA'W16
// ’i’ is immutable, so it is safe to return it instead of a copy. Nm{J=`
return i; -Pp =)_O
} ecdM+kP
iezY+`x4
?mbI6fYv
nd)`G$gL
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: jBr3Ay@<
M<K}H8?
:G4)edwe
2{A/Fbk
?Boolean l\6.f_
?Byte /St d6B*
?Character (.~,I+Cz'
?Class ,<O|#`?"@G
?Double CyKupJ.Fq
?Float W@t{pXwLv
?Integer OI)U c .
?Long 1SG^g*mf
?Short zbZN-j#
?String g0M/Sv
?大部分的Exception的子类 V8947h|&
i Qa=4'9;
H=@S+4_bK
-(VX+XHW
四、常见错误4# :自编代码来拷贝数组 jmr1e).];
+5N09$f;R
RD6`b_]o
83pXj=k<
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: |IZFWZd
rodr@
4<A+Tf
xlm:erP
public class Example{ gE$@:j
private int[] copy; AcIw;
c:
/*** Save a copy of ’data’. ’data’ cannot be null.*/ K*aGz8N
public void saveCopy (int[] data){ JQ<9~J
copy = new int[data.length]; 4mci@1K#^
for (int i = 0; i < copy.length; ++i) ."h>I @MH
copy = data; `{+aJ0<S
} >U62vX"
} X8~gLdv8
I,7n-G_'
PS/00F/Ak
FQBAt0
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: [J6q(}f
4*?JU
v
^~DClZ
0#!Z1:Y
void saveCopy (int[] data){ QN8.FiiD
try{ WV,j
<x9w
copy = (int[])data.clone(); Ixr#zt$T-G
}catch (CloneNotSupportedException e){ icXeB_&cS
// Can’t get here. Lb0B m R%0
} F2C v,&'
} Yg!xlrxA
c.Do b?5
K)nn;j=
j9O"!9$vQ
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: e"]DIy4s
tS
sDW!!M
#RTiWD[o
_Bq [c
static int[] cloneArray (int[] data){ D`@*udn=
try{ lk%W2N5
return(int[])data.clone(); /F_(&H!m
}catch(CloneNotSupportedException e){ 1J[|Ow
// Can’t get here. TU O*w
} ]oE:p
} *v0}S5^/"
h%!N!\
YnwP\Arfq
i4\m/&of3y
这样的话,我们的saveCopy看起来就更简洁了: [8rl{~9E
x>MY_?a
Y5\=5r/
hC2_Yr>N%
void saveCopy (int[] data){ RrRE$g
copy = cloneArray ( data); )" H r3
} nhI1`l&
UO8./%'
t(\P8J
~,O}wT6q
五、常见错误5#:拷贝错误的数据 t'DYT"3
rRd8W}B
wf/DLAC
hG
qZB
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: '/Ag3R
~/1eF7
j[&C6l+wH
|<w
Z;d
import java.awt.Dimension; 4<l&cP
/*** Example class. The height and width values should never * be p WLFJH}N
negative. */ {aYCrk1
public class Example{ /+{1;}AT
static final public int TOTAL_VALUES = 10; O
K2|/y
private Dimension[] d = new Dimension[TOTAL_VALUES]; +EP=uV9t
public Example (){ } >
@n?W"
zR6^rq*
/*** Set height and width. Both height and width must be nonnegative * or an exception will be thrown. */ %#-'|~
public synchronized void setValues (int index, int height, int width) throws IllegalArgumentException{ kz?m `~1
if (height < 0 || width < 0) FX:'38-fk
throw new IllegalArgumentException(); X.hVMX2B
if (d[index] == null) K0z@gWGE
d[index] = new Dimension(); mFeoeI,Jv
d[index].height = height; P'p5-l UK
d[index].width = width; #hP&;HZ2>"
} [cvtF(,
public synchronized Dimension[] getValues() &+-]!^2o
throws CloneNotSupportedException{ @DK;i_i
return (Dimension[])d.clone(); Ilv
_.
}
>TQnCG=
} "%fvA;
=d`/BDD
q3$;lLsb;j
wwh)B92Y5
这儿的问题在于getValues()方法仅仅克隆了数组,而没有克隆数组中包含的Dimension对象,因此,虽然调用者无法改变内部的数组使其元素指向不同的Dimension对象,但是调用者却可以改变内部的数组元素(也就是Dimension对象)的内容。方法getValues()的更好版本为: ]q|^?C
,`;Dre
O*y@4AR"S
RlnJlY/
public synchronized Dimension[] getValues() throws CloneNotSupportedException{ ?j-;;NNf
Dimension[] copy = (Dimension[])d.clone(); E-XFW]I
for (int i = 0; i < copy.length; ++i){ #vBS7ba
// NOTE: Dimension isn’t cloneable. UJ1Ecob
if (d != null) _.G p}0a
copy = new Dimension (d.height, d.width); q+}Er*r
} BHEZ<K[U
return copy; o7WK"E!pF'
} b.sRB1
eK'ztqQ
m-)yQM8
i0e aBG]I
在克隆原子类型数据的多维数组的时候,也会犯类似的错误。原子类型包括int,float等。简单的克隆int型的一维数组是正确的,如下所示: 0F|DD8tHR
q'4qSu
&a];"2
0Rze9od]$
public void store (int[] data) throws CloneNotSupportedException{ l1wYN,rv
this.data = (int[])data.clone(); >5+]~[S
// OK s^Wh!:>r/
} ^VAvQ(b!:i
M~#%
[?iU
7n*[r*$
of>"qrdZ
拷贝int型的二维数组更复杂些。Java没有int型的二维数组,因此一个int型的二维数组实际上是一个这样的一维数组:它的类型为int[]。简单的克隆int[][]型的数组会犯与上面例子中getValues()方法第一版本同样的错误,因此应该避免这么做。下面的例子演示了在克隆int型二维数组时错误的和正确的做法: RmcQGQ
K^fH:pV
-+w^"RBV
XVNJ3/
public void wrongStore (int[][] data) throws CloneNotSupportedException{ GO=3<Q{;
this.data = (int[][])data.clone(); // Not OK! 5Sfz0
} KD)+&69
public void rightStore (int[][] data){ N0 F|r8xS
// OK!
|jwN8@
this.data = (int[][])data.clone(); p.J+~s4G
for (int i = 0; i < data.length; ++i){ <4QOjW
if (data != null) :KL5A1{
this.data = (int[])data.clone(); K%/:V
} Z$&