代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 4ONou&T
6+Y^A})(F-
)`4g, W
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 C>A*L4c]F
JQ[~N-
mbZS J
RD$"ft]Vc
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 !awsQ!e|
!yfQ^a_O
c)7i%RF'
7aV(tMzd
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 9rd7l6$R"
i&%/]Nq
6wmMg i_m
tB,1+I=
一、常见错误1# :多次拷贝字符串 t%B ,ATW
yv2&K=rZp
[6$n
t9Sog~:'
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。
Z>O2
t7(#Cuv-
dHAI4Yf4U
\nX5$[
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: m4 :|
0\Q/$#3
Z*M]AvO+#
Fq-AvU
String s = new String ("Text here"); McXid~
IM^K]$q$47
A3;}C+K
jTDaW8@L
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: 0Ud.u
2#^@awJ ?
m\XgvpvrP
['G@`e*\
String temp = "Text here"; hxedQvW
String s = new String (temp); l9zkx'xt.-
9:]w|lE:D
ZQ0R3=52r
)S,Rx
但是这段代码包含额外的String,并非完全必要。更好的代码为: _a?(JzLw5
|3h-F5V)
YhZmyYamE
\["'%8[:gR
String s = "Text here"; 'f?=ks<
b!pG&7P
Hxw 7Q?F
j$he5^GC
二、常见错误2#: 没有克隆(clone)返回的对象 ;QiSz=DyA
k9'`<82Y
^xpiNP!?a
_xyq25/
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: Zeeixg-1<
npJyVh47
3Dm`8Xt
7M#irCX
import java.awt.Dimension; $v6`5;#u
/***Example class.The x and y values should never*be negative.*/ [Ju5O[o
public class Example{ o-m9}pV
private Dimension d = new Dimension (0, 0); N
N1(f
public Example (){ } V1 H3}
5d4/}o}%"
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ {FrcpcrQa
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ %]iDhXLr
if (height < 0 || width < 0) g aq"+@fH
throw new IllegalArgumentException(); Q96"^Hd
d.height = height; ?FRuuAS
d.width = width; ;:Yz7<>Y,
} t& *K
kt0ma/QpP
public synchronized Dimension getValues(){ :B(vk3;U!
// Ooops! Breaks encapsulation \'BA}v
&/
return d; "SV#e4C.
} 0+vt LDq@P
} _tJm0z!
=MsQ=:ZV
pSzO)j
z|^+uL
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: P`HDQ/^O
-D4"uoN.
;ye5HlH}.
[s"e?Qee
Example ex = new Example(); 9?IvSv}z
Dimension d = ex.getValues(); %:DH_0
d.height = -5; S%sD#0l
d.width = -10; |P>Yf0
n@`:"j%s_
OX
r%b
v{T%`WuPRf
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 k'(eQ5R3L
FVgE^_
/3!c
;(
DC-tBbQkk
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。
'Pm.b}p<
CBVL/pxy
#ox&=MY
RdirEH*H
更好的方式是让getValues()返回拷贝: 8vK$]e36
3Aqw)B'"_
C=sEgtEI
k,kr7'Q
public synchronized Dimension getValues(){ EJz?GM
return new Dimension (d.x, d.y); T|L_+(M{
} 9r efv
k\NwH?ppu
mbS`+)1=l
FS1>
J%P
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 ]8c%)%Vi
JSAbh\Mq6
hbOyrjanx
NhgzU+)+
三、常见错误3#:不必要的克隆 TGxmc37?
,*r}23
z87_/(nu
u5 1%~
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: qTA,rr#p0
/M3UK
:Nt_LsH
\mIm}+!H
/*** Example class.The value should never * be negative.*/ L6ifT`;T
public class Example{ z^etH/]Sy
private Integer i = new Integer (0); xeGl}q|
public Example (){ } (z:DTe
YWXY4*G
/*** Set x. x must be nonnegative* or an exception will be thrown*/ AB1.l
hR
public synchronized void setValues (int x) throws IllegalArgumentException{ *\M$pUS{
if (x < 0) Ul`~d
!3zH
throw new IllegalArgumentException(); P#ro;3S3y
i = new Integer (x); K4[XP]\jr
} ;GjZvo
: =J^ "c
public synchronized Integer getValue(){ D J:N
// We can’t clone Integers so we makea copy this way.
el"XD"*
return new Integer (i.intValue()); Hx|<NS0}_
} yltzf
#%
} |_A DG
8do7`mN
P>wDr`*
/KCJ)0UU
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 fEMz%CwH
?cH,!2
H({Y
z/Kjz$l!
方法getValue()应该被写为: L4x08 e
3SMb#ce*o
itpljh
A{QXzoWkg0
public synchronized Integer getValue(){ 9wB}EDZ
// ’i’ is immutable, so it is safe to return it instead of a copy. uHNh|ew21
return i; [Up0<`Q{I_
} Z6F^p8O-
D rMG{Yiu
}iZ>Gm'5
s&