代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 6<9}>Wkf
!x:{"
OFBEJacy
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 }.pqV
X{d
PhPe7^
cs7^#/3<
2$MoKOx8$
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 bIlNA )g
&uF~t
|!c
1KY0hAx
5
1N/XEk
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 0y t36Du
omGzyuPF
Qv`: E
S?6-I,]h
一、常见错误1# :多次拷贝字符串 s)fahc(@E
Q@W!6]*\
=)G]\W)m
Caz5q|Oo
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 d#XgO5eyO
<.Pt%Kg^BS
$P#x>#+[A
IN@o9pUjV
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: h-|IZ}F7
v%c/eAF
7M
_
mR Vh
zRd.!Rv
String s = new String ("Text here"); R?;mu^B
k6J&4?xZ
"dG N0i
cWG%>.`5r
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: mQ<4(qd)
.p.(
\5Fo
)hl7)~S<
10h;N[
String temp = "Text here"; 8V}|(b#
String s = new String (temp); ;N(L,
rM^2yr7H
9-V'U\}L
/t`,7y3T
但是这段代码包含额外的String,并非完全必要。更好的代码为: sp:4b$zX
',xUU{5?
`)5WA{z
UGd\`*Cj
String s = "Text here"; 4`)r1D!U
c-5AI{%bl6
-.y1]4
Zj-U^6^L
二、常见错误2#: 没有克隆(clone)返回的对象 1x=x,lcL
7V8k =
ZgG~xl\My
*l4[`7|
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: j{r@>g;3
?>U=bA
N&p0Emg
(&Jo.
<
import java.awt.Dimension; (CRx'R
/***Example class.The x and y values should never*be negative.*/
j5Da53c#^
public class Example{ 4_iA<}>|
private Dimension d = new Dimension (0, 0); 1<1+nGO
public Example (){ } AX$r,KmE
q?Csm\Y
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ fz`)CWo:
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ d5>&,
{o7N
if (height < 0 || width < 0) 1KrJS(.
throw new IllegalArgumentException(); 8#lq:
d.height = height; hrq% { !Z
d.width = width; m7y[Y
} ;5L^)Nyd
:H3/+/x
public synchronized Dimension getValues(){ i0$*):b
// Ooops! Breaks encapsulation Q.$Rhjb
return d; jc )7FE
} W>i%sHH6
} zG<<MR/<
tuIZYp8tIN
lya},_WCq
p&x!m}!
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: /+JnEFf
ha$1vi}b
6 5dMv*{
{&>rKCi
Example ex = new Example(); 2b"DkJj'
Dimension d = ex.getValues(); ,8Po
_[
d.height = -5; .l_Nf9=
d.width = -10; p*,T~(A6
RC[Sa wA
3: WEODV2
,lA@C2c
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 OqIXFX"
eK
l;T
3m!tb)
7`;f<QNo
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 iLZY6?_^
Ms,MXJtH
dt:$:,"
nOL.%
更好的方式是让getValues()返回拷贝: r9&m^,U
_3@5@1[s
x1#>"z7
Nz.X$zUmY
public synchronized Dimension getValues(){ Rr%x;-
return new Dimension (d.x, d.y); m!Z<\2OP
} O 1z0dHa
4>0q0}J=5
z/xPI)R[
j;y~vX b
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 xmcZN3 ){+
vio>P-2Eho
f\dfKNm6
zaHZ5%{LQD
三、常见错误3#:不必要的克隆 7$lnCvm
s+lBai*#
B8T$<
>*h+N?
m
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: `8W HVC$
O1\Hx8^
9D1WUUa
E3O^Tg?j
/*** Example class.The value should never * be negative.*/
#O}}pF
public class Example{ ;\2Z?Kq
private Integer i = new Integer (0); 4\&Y;upy+
public Example (){ } o=($'(1
hA5')te<