代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 |!z2oO
mS=r(3#
_cqy`p@"
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 }6zbT-i
%FkLQ+v/<
Xh3;
qojXrSb"y
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 w; TkkDH
NC23Z0y
oh8L`=>&a
PBqy F
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 +",S2Qmo
$K}.
+`vVO
a=}*mF[ug
wGKo.lt
一、常见错误1# :多次拷贝字符串 +=@ ^i'
'"YYj$>
'
7v~j=Z>
'VnwG
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 Ggm` ~fS
T.&7sbE_
XJ\hd,R
3fS}:!sQ
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: mX# "+X|
6Z:YT&,f
Y>6.t"?Q^
$n=lsDnhQ
String s = new String ("Text here"); {")\0|2\x
GlYly5F
3Kq`<B~%
\ {|ImCH
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: x-m/SI]_N
_2Py\+$
OKue" p
sRRI3y@
String temp = "Text here"; |H)cuZ
String s = new String (temp); _GaJXWMbk
m\0cE1fir
o
\L!(hm
kT oOIx
但是这段代码包含额外的String,并非完全必要。更好的代码为: b Y8GA
M?&zY
"c
Buc_9Kzw<+
19u =W(
String s = "Text here"; UPh=+s #Q
4iX-( ir,
je%M AgW`
P~7.sM
二、常见错误2#: 没有克隆(clone)返回的对象 H[&@}v,L
j~av\SCU*
VV3}]GjC
QTJu7^O9
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: JJk#,AP
`u'dh{,gE
D_D,t8_Y
/XpSe<3
import java.awt.Dimension; C3 ;[e0.1b
/***Example class.The x and y values should never*be negative.*/ d,#.E@Po
public class Example{ GrI&?=S^
private Dimension d = new Dimension (0, 0); ocA]M=3~k
public Example (){ } wT_^'i*@I
o#hI5
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ KX+ey8@[
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ C7"HQQ
if (height < 0 || width < 0) ?-~I<f]_
throw new IllegalArgumentException(); D guB
d.height = height; !q/5yEJ>h
d.width = width;
M[P^]J@
} POd/+e9d
M}FWBs'*|
public synchronized Dimension getValues(){ 05e>\}{0
// Ooops! Breaks encapsulation Wr%7~y*K
return d; I48VNX
} ,@CfVQz
} 4('JwZw\!
k=n
"+
9C}qVoNu
{U @3yB
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: &"S/Lt
?l6jG
^X?D#\
szq+@2:
Example ex = new Example(); 3oBC
Dimension d = ex.getValues(); /&s}<BMHU
d.height = -5; Y`li> .\
d.width = -10; MOZu.NmO
otriif@+Z
Jq1 n0O
>{&A%b4JF
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 mnQ'X-q3iO
4F#%f#"
`iYc<N`
:t$A8+A+0
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 'EX4.h
a5
tY_5Pz(@
h_ ZX/k
;h=S7M9.
更好的方式是让getValues()返回拷贝: (_8#YyW#
sBjXE>_#)
0X"\ a'M_
I,P!@
public synchronized Dimension getValues(){ &YX6"S_B
return new Dimension (d.x, d.y); zixEMi[8
} L#j/0IHD
dr]&kqm
&HF]\`RNr
h|W%4|]R)
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 TVkcDS
2VYvO=KA
UKs$W`
gGbI3^r#
三、常见错误3#:不必要的克隆 PrnrXl
S
SMO*({/
.ZX2^)`XD
Auac>')&Q
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: #93}E
Y
9k`~x1Y)
K`(#K#n
^KH%mSX>
/*** Example class.The value should never * be negative.*/ u4"r>e6_B
public class Example{ <Jwo?[a
private Integer i = new Integer (0); L8P36]>
public Example (){ } bXvbddu)}
Z<]VTo
/*** Set x. x must be nonnegative* or an exception will be thrown*/ BjZ>hhs!*
public synchronized void setValues (int x) throws IllegalArgumentException{ fv?45f
if (x < 0) R}k69-1vL
throw new IllegalArgumentException(); pt})JMm
i = new Integer (x); ,y.3Fe
} F6&P ~H
p7 [(z
public synchronized Integer getValue(){ (j N]OE^
// We can’t clone Integers so we makea copy this way. Wem?{kx0
return new Integer (i.intValue()); 3+ asP&n
} iS-K
~qa
} /0\QL+^!
HD00J]y_
4*8&[b
dq1TRFu
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 j+0.=#{??
,%8$D-4#_
fI}c 71b`
%!wq:~B1
方法getValue()应该被写为: &;U|7l~vl
gz\j('~-D
8p,>y(o
XGk}e4;_
public synchronized Integer getValue(){ 3 2y[
// ’i’ is immutable, so it is safe to return it instead of a copy. Zd XKI{b
return i; nKu(XgFv
} %8<2>
;MZbL)
1.dX)^\
ZbyG*5iq
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: >w2f8tW`PP
yk#rd~2Z0
.+uVgSN
j4vB`Gr]
?Boolean S)Mby
?Byte Ij,Yuo
?Character *h])mqhB
?Class ?o>6S
EGW
?Double k(9s+0qe
?Float [oJ& J>U'
?Integer JU2P%3
?Long )<Ob
?Short |VYr=hjo
?String d'q&Lq
?大部分的Exception的子类 `\e'K56W6
8J^d7uC
+7^w9G
i&pMF O
四、常见错误4# :自编代码来拷贝数组 Ej5^Y ?-6
#:I^&~:
N.vG]%1"
d3(+ztmG!
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: w'XSb.\)_m
x{j+}'9
T7s+9CE
2_I+mQ
public class Example{ ,i;9[4QMX
private int[] copy; }Fz!6F2w
/*** Save a copy of ’data’. ’data’ cannot be null.*/ vcV!K^M-
public void saveCopy (int[] data){ *NF&Y
copy = new int[data.length]; <L%HG
for (int i = 0; i < copy.length; ++i) lXw;|dGF
copy = data; vhX-Qk t}
} /O_0=MLp
} +> ^[W~[2
)2toL5 Q
*.,8,e8Vq
flPZlL
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: DbQBVy
fGG
9zB6
hsz$S:am
x@Sra@
void saveCopy (int[] data){ Cl{{H]QngX
try{ +O,V6XRr
copy = (int[])data.clone(); Ho>p ^p
}catch (CloneNotSupportedException e){ QdirE4W
// Can’t get here. p>!1S
} (\tq<h0
} FfjC
M7?
WEps.]s
}il%AAI9}r
tRkrV]K
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: zK,~ 37)\
"wF*O"WQo
C\J@fpH(t`
G1A$PR
static int[] cloneArray (int[] data){ Dn: Yi8=
try{ KZi+j#7O
return(int[])data.clone(); Fr<Pe&d