代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 okh0_4
"o<D;lO
5F03y`@ u
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 FLEg0/m0
6NSO >/E
r'PE5xqF
}{#7Z8
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 <tU
:U<ea]
C &FN#B
0O^r.&{j>
]nHe$x!2]
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 e
mC\i
/J8o_EV
q4zSS #]A
lk~dgky@
一、常见错误1# :多次拷贝字符串 q"l>`KCG`
6i^0T
~Cu lFxu
?9,YVylg
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 jUZ[`f;
|y'b217t
>]C<j4
FcY$k%;'Q
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: l [x%I
;\q<zO@x
ew/KZE
L&rO6
String s = new String ("Text here"); -
Ra\^uz
M Yu?&}%^
WY3_7k8u
%!D_q~"H
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: &F9OZMK=
{\F2*P
V9gVn?O0
@eA %(C
String temp = "Text here"; mnQal>0~
String s = new String (temp); )m)h/_
JJ)y2
O} (E(v
|#!eMJ&0
但是这段代码包含额外的String,并非完全必要。更好的代码为: kS[Dy$AB/2
\(wn@/yP'
1.uUMW
|\rSa^:5
String s = "Text here"; /;[}=JL<Q
EGr|BLl
9k*^\@\\x
=nw,*q +
二、常见错误2#: 没有克隆(clone)返回的对象 m*OLoZVy
"@aq@mY@
55(J&q
`s#sE.=o
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: ]9dx3<2_I
t4C<#nfo
vRq=m8
[`cdlx?Eh
import java.awt.Dimension; fc["
/***Example class.The x and y values should never*be negative.*/ #R5we3&p
public class Example{ ttTI#Fr2
private Dimension d = new Dimension (0, 0); `\nON
public Example (){ } 6zELe.tq
b"`ru~]
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ {_?T:`
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ qAnA=/k`
if (height < 0 || width < 0) 7j4ej|Fjo
throw new IllegalArgumentException(); jM{(8aUG
d.height = height; ^n6)YX
d.width = width; |C&%S"*+D
} U#OWUZ
BYkVg2D(
public synchronized Dimension getValues(){ m
j'"Z75
// Ooops! Breaks encapsulation
#_?426Wfs
return d; EKV+?jj$
} ce 7Yr*ZB
} n.=e)*
-ryDsq
Tyg$`\#
/h1dm,
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: Fc34Y0_A
ppPG+[ cz
^=aml
bS_y_9K
Example ex = new Example(); uEc0/a :.
Dimension d = ex.getValues(); ^aGZJiyJ
d.height = -5; 3P%w-qT!N
d.width = -10; )Ix-5084
@>qx:jx(-S
D|u^8\'.
'-$))AdD
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 V[BY/<z)A
GlXA-p<
x*5 Ch~<k
c+}!yH$
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 H9oXZSm
qAS70XjOF
'7S!6kd?
u|]mcZ,ZW
更好的方式是让getValues()返回拷贝: ]
P:NnKgK
J3]qg.B%z
Td["l!-fe
+ 1E?He:iQ
public synchronized Dimension getValues(){ L:|X/c9r[
return new Dimension (d.x, d.y); EqNz L*E
} uzzWZ9Tv
yv6Zo0s<J
mq|A8>g
7/5NaUmPTt
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 U.zRIhA]
]%cHm4#m3
zN?$Sxttx
,v$2'm)V
三、常见错误3#:不必要的克隆 ~#HH;q_7m
k;"R y8[k
/8P4%[\
SdjUhR+o
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: Z`SWZ<
1B9Fb.i
'$2oSd
Q2_WH)J 3
/*** Example class.The value should never * be negative.*/ wHBHkz
public class Example{ CrRQPgl+u
private Integer i = new Integer (0); uMiD*6,$<
public Example (){ } $ uz1
c5T~0 'n
/*** Set x. x must be nonnegative* or an exception will be thrown*/ ShEaL&'J
public synchronized void setValues (int x) throws IllegalArgumentException{ CYTuj>Ww
if (x < 0) !:g>CDA
throw new IllegalArgumentException(); Y:tW]
i = new Integer (x); Allt]P>
} MHpL$g=5_
%~~z9 6(
public synchronized Integer getValue(){ n6}E4Eno
// We can’t clone Integers so we makea copy this way. l1+w2rd1
return new Integer (i.intValue()); rC1qGzg\a
} zezofW]a
} a `[?,W:q
|2t7G9[n
VrAXOUJw6
0,"n-5Im
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 Hm.&f2|(
IDiUn!6Q
gr[ "A
"FLD%3l
方法getValue()应该被写为: .NNcc4+
HiS,q0
9 :K
#um1?V
public synchronized Integer getValue(){ 4cErk)F4
// ’i’ is immutable, so it is safe to return it instead of a copy. Yq)YS]
return i; m&8U4uHN
} [#,X$O>
r+V(1<`2X
?}1JL6mF{
l7D4`i<F
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: j"D0nG,
Mi%1+
mhJOR'2
k?|F0e_
?Boolean k #,Gfs
?Byte L8?Z!0D/h
?Character w/^0tZ~
?Class SS45<!iy
?Double &Gy'AUz-
?Float kERaY9L\
?Integer Rrs`h `'-
?Long r=P$iG'&
?Short 9`gGsC
?String !7,K9/"
?大部分的Exception的子类 @6I[{{>X
%DND&0`
2'O!~8U
yaYIgG
四、常见错误4# :自编代码来拷贝数组
J7
*G/F
UtGd/\:
n/-p;#R
2Xj-A\Oh~
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: qu#@F\gX
,G!_ SZ
,<
)/45
eqUn8<<s
public class Example{ Z>MJ0J76]
private int[] copy;
5Ky9P z
/*** Save a copy of ’data’. ’data’ cannot be null.*/ e G*s1uQl
public void saveCopy (int[] data){ EDa08+Y
copy = new int[data.length]; aP6%OI
for (int i = 0; i < copy.length; ++i) ;t@ 3Go
copy = data; Vp{RX8?.
} 1 [~|
} 1vR#FE?
JG+g88
]5)&36
"|l
oSf@
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: ).O2_<&?F
wJ]$'c3
ezq
q@t9
N:gstp
void saveCopy (int[] data){ )/N Xh'
try{ xdTzG4
copy = (int[])data.clone(); M'!!EQo
}catch (CloneNotSupportedException e){ hcp'+:
// Can’t get here. ,n,7.m.D
} ;uWIl
} m(7_ZiL=
~V$5 m j
dv4r\ R^
(m =u;L"o
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: $Bwvw)(%
;KjMZ(Iil1
pQgOT0f
/wCxf5q0
static int[] cloneArray (int[] data){ ['N#aDh.?
try{ UXdC<(vK
return(int[])data.clone(); ,9l!fT?iH
}catch(CloneNotSupportedException e){ '$L= sH5
// Can’t get here. YWBP'Mo
} BKP!+V/
} px(1Ppb9
|#khwH
bl=*3qB
MgK(gL/&[
这样的话,我们的saveCopy看起来就更简洁了: [#@p{[ ?r
HjF'~n
NYV0<z@M2M
GL0' :LsZ
void saveCopy (int[] data){ *.;}OX^X
copy = cloneArray ( data); Y @ ,e
} D<L{Z[
h|/*yTuN.y
VT~
^:-]
qI%9MI;BV
五、常见错误5#:拷贝错误的数据 QX~72X=(
xyJgHbml
<wGTs6
#(Yb
lY
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: qP .VK?jF|
);.<Yf{c
H&K)q5~
:w`i
import java.awt.Dimension; kU9AfAe
/*** Example class. The height and width values should never * be `9"jHw`D
negative. */ M+&eh*:z:
public class Example{ Mud\Q["
static final public int TOTAL_VALUES = 10; (S93 %ii
private Dimension[] d = new Dimension[TOTAL_VALUES]; Z YO/'YW
public Example (){ } P*^UU\x'4I
GMp'KEQQ
/*** Set height and width. Both height and width must be nonnegative * or an exception will be thrown. */ ^~kFC/tQ
public synchronized void setValues (int index, int height, int width) throws IllegalArgumentException{ "@<g'T0
if (height < 0 || width < 0) /)<7$
throw new IllegalArgumentException(); ~sja^
if (d[index] == null) @md^mss
d[index] = new Dimension(); sVl:EVv
d[index].height = height; 'A@Oia1;{
d[index].width = width; 9mtC"M<
} o>k-~v7
public synchronized Dimension[] getValues() { dxyBDK
throws CloneNotSupportedException{ Hn2Q1lF-ip
return (Dimension[])d.clone(); 9Qm{\
} '
xq5tRg>
} `];[T=
9(Xch2tpO!
9!OCilG
.;sPG
这儿的问题在于getValues()方法仅仅克隆了数组,而没有克隆数组中包含的Dimension对象,因此,虽然调用者无法改变内部的数组使其元素指向不同的Dimension对象,但是调用者却可以改变内部的数组元素(也就是Dimension对象)的内容。方法getValues()的更好版本为: hdDI%3vk3
a+Qj[pS
pDS4_u
gGz_t,=
public synchronized Dimension[] getValues() throws CloneNotSupportedException{ \Hx#p`B%
Dimension[] copy = (Dimension[])d.clone(); ZFw743G
for (int i = 0; i < copy.length; ++i){ g<jK^\eW
// NOTE: Dimension isn’t cloneable. -Y,Ibq
if (d != null) $fpDABf
copy = new Dimension (d.height, d.width); '`VO@a
} ;iI2K/ 3
return copy; s5|)4Zac
} {y%O_-C'r
,UJPLj^
wx7>0[ zE
KD<`-b)7<
在克隆原子类型数据的多维数组的时候,也会犯类似的错误。原子类型包括int,float等。简单的克隆int型的一维数组是正确的,如下所示: JZ0+VB-3U
!LSWg:Ev+
#z5?Y2t7~^
$f-pLF+x
public void store (int[] data) throws CloneNotSupportedException{ e/~<\
this.data = (int[])data.clone(); wA+4:CF@
// OK VFp)`+8
} ^*>no=A
[9Hm][|Ph
fH{$LjH(
xg!\C@$
拷贝int型的二维数组更复杂些。Java没有int型的二维数组,因此一个int型的二维数组实际上是一个这样的一维数组:它的类型为int[]。简单的克隆int[][]型的数组会犯与上面例子中getValues()方法第一版本同样的错误,因此应该避免这么做。下面的例子演示了在克隆int型二维数组时错误的和正确的做法: VH*(>^OfF
5 `mVe0uI
ag4^y&
6m<9^NT
public void wrongStore (int[][] data) throws CloneNotSupportedException{ T[K?A+l
this.data = (int[][])data.clone(); // Not OK! q:eAL'OkM
} J\},o|WI
public void rightStore (int[][] data){ ({62GWnn_
// OK! 4p g(QeR
this.data = (int[][])data.clone(); !: [`
V!{
for (int i = 0; i < data.length; ++i){ 4y)1*V U:
if (data != null) eh=bClk
this.data = (int[])data.clone(); nr%^:u
} q "vT]=Y}:
} h v+i{Z9!]
blS4AQ?b^
A}}t86T
O$ oN1
M#IR=|P]
六、常见错误6#:检查new 操作的结果是否为null ?AH<y/i<Y
J)~=b_'<
g4932_tC
%[;KO&Ga