代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 I3(d<+M
oL-2qtv
N 9LgU)-Jt
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 u okc:D
4x=(Zw_X
~KPv7WfG
4-^[%&>}
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 0[Eb .2I
ykmv'a$-4
v@n_F
E
oe}l
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 Z9[+'ZWt
||Y<f *
~=cmM
z_&P?+"Df
一、常见错误1# :多次拷贝字符串 S-c ^eLzQ
}`_(<H
2 hq\n<
cP rwW6
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 vFhz!P~
e.8$ga{
7u|B ](FS
wk @,wOt
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: [_.n$p-
24B<[lSK
iKAusWj
3i=Iu0
String s = new String ("Text here"); ].m qxf
o35fifM`
=J/ FJb
[Y/:@t"2y
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: zk}{ dG^M:
3+U]?7t
G%:GeW
eV2mMSY
String temp = "Text here"; =w%O a<
String s = new String (temp); ej^3YNh&
Md&WJ
};L
eB]R3j{
:_HF j.JW
但是这段代码包含额外的String,并非完全必要。更好的代码为: 7lA:)a_!]
`hUHel;6
k ;KdW P
r\qz5G *6
String s = "Text here"; fk{0d
m4m<nnM
DQ80B)<O
N+g@8Q2s;5
二、常见错误2#: 没有克隆(clone)返回的对象 ~ap2m
6q/?-Qcy
:dwt1>
."6[:MF
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: lr3mE
d%ME@6K)
nc?B6IV
lm0N5(XP
import java.awt.Dimension; c$h9/H=~
/***Example class.The x and y values should never*be negative.*/ h"W8N+e\
public class Example{ 5zB~4 u
private Dimension d = new Dimension (0, 0); -t-tn22
public Example (){ } qTmD'2
{C3Y7<
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ L%h/OD
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ DuF"*R~et
if (height < 0 || width < 0) {hdPhL
throw new IllegalArgumentException(); ~Xv=9@,h
d.height = height; `dW]4>`O
d.width = width; m%r/O&g
} eJ@~o{,?>
GbZ;#^S
public synchronized Dimension getValues(){ 4N>>+]MWc
// Ooops! Breaks encapsulation K8[DZ)rO;Z
return d; 1hmc,c
} )!W45"l-3M
} z`3( ,V
l67Jl"v
`/IKdO*!S
q|(W-h+
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: (<c7<_-H
=|U@
TzG]WsY_
LKF/u` 0dP
Example ex = new Example(); ^J/)6/TMXm
Dimension d = ex.getValues(); =o 7}]k7
d.height = -5; 4P8*k[.
d.width = -10; RHY4P4B<v>
9
c3E+
AMCyj`Ur
nt
:N!suP3
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 T)iW`vZg8
S4o$t-9l
tkKJh !Q7
uGP(R=H
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 _aS;!6b8W
zJN7<sv
BlC<`2S
xL
"!~dN
更好的方式是让getValues()返回拷贝: >SmV74[s2
, H
kj1x
zj{s}*
Yl^mAS[w&
public synchronized Dimension getValues(){ Z;DCI-Wg
return new Dimension (d.x, d.y); dJk9@u
} ,!QV>=
;0%OB*lcgE
LlYTv%I
2I'~2o
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 gzn^#3 b
a2@c%i
K7)kS
!36]ud&
三、常见错误3#:不必要的克隆 \Y|*Nee}XP
P:xT0gtt
R^&q-M=O[
8Cx^0
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: 1Y j~fb(
YK#fa2ng
Dl\`
b1?xeG#
/*** Example class.The value should never * be negative.*/ |V,<+BEi
public class Example{ *f+: <=i
private Integer i = new Integer (0); /bRg?Q
public Example (){ } Xl-e !
E,[xUz"
/*** Set x. x must be nonnegative* or an exception will be thrown*/ J$ut_N):N
public synchronized void setValues (int x) throws IllegalArgumentException{ *ZCn8m:-+
if (x < 0) _2ef LjXQ
throw new IllegalArgumentException(); ~mz%E
i = new Integer (x); @mQ:7-,~
} P ,mN >
Gu0 ,)jy\
public synchronized Integer getValue(){ #
TkR
// We can’t clone Integers so we makea copy this way. ",qU,0
return new Integer (i.intValue()); :D:DnVZ-[@
} f>$``.O
} Wd,a?31|
2tQ`/!m>v$
$&I'o
-7qIToO.
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 fz_nsVD
ZI>km?w
V*[b}Xew
[\z/Lbn
,.
方法getValue()应该被写为: UK[v6".^h
J5M+FwZq
?\=/$Gt
`CE^2
public synchronized Integer getValue(){ J>vMo@
// ’i’ is immutable, so it is safe to return it instead of a copy. <'U]`Lp
return i; Qx3eLfm
} \%jVg\4'
,\)a_@@k
+>f<EPGn
Q9F)
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: W&Y"K)`
(=x"Y{%
p<Z3tD;Z
)u:Q)
%$t
?Boolean #o`Ny4sq/
?Byte (]2H7X:b
?Character >pL2*O^{9
?Class q>!L6h5]t
?Double lEjwgk {
?Float /! ajsn
?Integer F'RUel_%
?Long z`@^5_
?Short 7E$&2U^Js
?String iP@6hG`:
?大部分的Exception的子类 pL1i|O
hf6f.Z
<=K qcHb
6 ,ANNj
四、常见错误4# :自编代码来拷贝数组 _u0$,Y?&