代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 brdmz}
n(L\||#+
R
+WP0&d'
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 ^Nmg07_R
Up,vD)tG
mL_j4=ER@
C@bm
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 uU:CR>=AKW
?_Q/}@`
Hrz f'a|^
kn>$lTHQ
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 ^",ACWF4Sk
|j VM&R2s
82]vkU
k5C@>J
一、常见错误1# :多次拷贝字符串 ~Q>_uw}g#
.F(i/)vaq|
^1L>l9F
])Qs {hs~s
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 |"9 #bU
i}o[- S4
]@0NO;bK>F
:P@rkT3Q t
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: 4y5UkU9|
)JNSZB
Ldl5zc
y!!E\b=
String s = new String ("Text here"); V`7FKL@"
^pe{b9c
+{L<? "
Pw;!uag
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: TM|)Ljm
jMN[J|us51
,i,q!M{-
v0ES;
String temp = "Text here"; [w&$| h:;
String s = new String (temp); +C(/Lyo}
EB_NK
d R]Q$CJ
o`q_wdy?
但是这段代码包含额外的String,并非完全必要。更好的代码为: YcN!T"wJ@
<1.A=_
M
0#YX=vjX7
i6 (a@KRY
String s = "Text here"; A6pjRxg
Wf&W^Q
;XDz)`c
-M1YE
二、常见错误2#: 没有克隆(clone)返回的对象 Gsc\/4Wx
3:f[gV9K
~~6^Sh60g
ryc& n5
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: &09U@uc$
n9\]S7]52
m/@<c'i
^_%kE%I
import java.awt.Dimension; N?m0USu*
/***Example class.The x and y values should never*be negative.*/ A?oXqb
public class Example{ ?"KC-u|
private Dimension d = new Dimension (0, 0); j$7Xs"
public Example (){ } 5 XA=G
q9^
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ vc]cNz:mQ
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ \^V`ds*.
if (height < 0 || width < 0) fI7j):h;
throw new IllegalArgumentException(); Z-B%'/.
d.height = height; B=@ jW z"
d.width = width; ;kJu$U
} 3J=Y9 }
BnM4T~reOF
public synchronized Dimension getValues(){ Go&D[#
// Ooops! Breaks encapsulation i"j(b|?e
return d; 2PR^:h2
} 0ilCS[`b
} 0K-*WQ*#9
8p!*?RRme[
&
``d
U5]pi+r
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: t
nS+5F
_7D _72
4TwQO$C
cFagz* !
Example ex = new Example(); TbehR:B5g
Dimension d = ex.getValues(); )!Bd6-
d.height = -5; D5an\gE
d.width = -10; X{g%kf,D=
gLSA!#[h
$y?k[Y-~
=]>NDWqpHN
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 =9LC<2
f):~8_0b
R4<lln:[
z1!6%W_.
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 oy<J6
2 /y}a#s
oR*=|B
K$
v"Uk
更好的方式是让getValues()返回拷贝: vLO&Lpv
/"ymZI!k\
F#{gfh
(Bo bB]~a
public synchronized Dimension getValues(){ i%#$*
return new Dimension (d.x, d.y); =_[Z W
} ntP|\E
-~4+w
SjdZyJa
F.)!3YE
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 J@9}`y=K
~^vC,]hU
-K[782Q
111D3
三、常见错误3#:不必要的克隆 $A}QY5`+~S
!eJCM`cp
,5|d3dJS
#'hLb
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: a9~"3y
:h:@o h_=
(XH2Sy
IB|]fzy
/*** Example class.The value should never * be negative.*/ A7P`lJgv
public class Example{ {5%/ T,
private Integer i = new Integer (0); +^6}
public Example (){ } n$2 RCQ
\nqo%5XL
/*** Set x. x must be nonnegative* or an exception will be thrown*/ Vdn.)ir~P
public synchronized void setValues (int x) throws IllegalArgumentException{ +[m8c){
if (x < 0) XV'fW~j\
throw new IllegalArgumentException(); yW.COWL=)
i = new Integer (x); L<(VG{)Z
} P.sgRsL
k:#6^!b1
public synchronized Integer getValue(){ l
oqvi
// We can’t clone Integers so we makea copy this way. Gowp
<9 F
return new Integer (i.intValue()); a-n4:QT
} iS@\ =CK
} |)W!jC&k
B(l-}|m_
Oe1 t\
0>!/rR7
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 S)%_we LW7
ad!(z[F'Y
Qrz4}0
#X.+
方法getValue()应该被写为: tiLu75vj
'Zk<l#"}
eSl-9
^
3z{S}~
public synchronized Integer getValue(){ 4x'AC%&Qi
// ’i’ is immutable, so it is safe to return it instead of a copy. M+sj}
return i; bO49GEUT _
} 0zqj0
&WZP2Q|
MY-.t-3
a%hGZCI
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: >Csbjf6
^Y^"'"
c!&Qj
s0{
NsK>
?Boolean !W1eUY
?Byte GH'O!}
?Character {TZE/A3D,
?Class u9![6$R
?Double Y~oT)wTU
?Float Rq7p29w
?Integer W81o"TR|pt
?Long .R5/8VuHF
?Short NcL
=zo<
?String lVeH+"M?
?大部分的Exception的子类 ~SVQ;U)-
/aUFc '5
Z|^MGyn
CKTrZxR"
四、常见错误4# :自编代码来拷贝数组 qmmv7==
Q?;C4n4]l
L2Ux9_S
GYgWf1$8_D
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: da*9(!OV
lSc,AOXp
|l90g|isJ
Sa]mm/G
public class Example{ &]nd!N
private int[] copy; oA3d^%(c
/*** Save a copy of ’data’. ’data’ cannot be null.*/ Mr6E/7g%
public void saveCopy (int[] data){ C<he4n.
copy = new int[data.length]; K[?R[
for (int i = 0; i < copy.length; ++i) KCXw n
copy = data; R!{7OkC
} f]}}yBte`
} ' yNPhI
J>v$2?w`w
.]Ybp2`"U
v#=ayWgk
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: n0.8)=;2
rrQ0qg
X^in};&d
e?)yb^7K
void saveCopy (int[] data){
nhfwOS
try{ F7uhuqA]N
copy = (int[])data.clone(); P`sN&Y~m
}catch (CloneNotSupportedException e){ gStY8Z!k
// Can’t get here. 1hNEkpL^a
} ?1m ,SK
} /v&`!nKu
Am7| /
hCLk#_
TczXHT}G
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: GUCM4jVT^
d]k='
zXgkcq)
#D:RhqjK
static int[] cloneArray (int[] data){ |!re8|JV_
try{ \|!gPc%s
return(int[])data.clone(); S 1ibw \'
}catch(CloneNotSupportedException e){ ,iOZ|
// Can’t get here. 'aPCb`^;w
} gY\mXM*^
} {gIEZ{
UQdyv(jXq
Bi_J5 If
9&(.x8d,a
这样的话,我们的saveCopy看起来就更简洁了: 3^H/LWx`{]
,%= '>A
aa=b<Cd
!@yQK<0
void saveCopy (int[] data){ 4H7Oh*P\j
copy = cloneArray ( data); IuWX*b`v
} 7h1"^}M&
M;@Ex`+?i
|
W?[,|e
i -V0Lm/
五、常见错误5#:拷贝错误的数据 akrCs&Kka5
hE5G!@1F
3d U#Ueu
N('3oy#8
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: 0sabh`iQ^
cV(H<"I
]84YvpfW
7`+UB>8
import java.awt.Dimension; <