代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 Bo%NFB;
!0mI;~q| F
U}j0D2
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。
c=.(!qdH
l0A&9g*l2
QGmn#]w\\
p0<\G
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 UFb)AnK
/FEVmH?
L8#5*8W6
OX\F~+
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 sYf~c0${
tT?cBg{
yd`mG{Z
$(>+VH`l
一、常见错误1# :多次拷贝字符串 $[=%R`~w
=
6\ ^%
1Nd2{(
[Nbm|["q~
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 E\pL!c
l3F6AlPql
XFV!S#yEZ
$aXer:
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: (+y
Dy8r 9
.gOL1`b*
**gXvTqI
String s = new String ("Text here"); n3
r3"~i
n$,*|_$#
qVwIo.g!
yFlm[K5YD
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: H@8sNV/u
xLH)P<^`C
mk+B9?;cF-
2n"V}p>8i#
String temp = "Text here"; O2dW6bt
String s = new String (temp); LsS
k4y'b
\<TXS)w]
'K{Z{[s{
但是这段代码包含额外的String,并非完全必要。更好的代码为: Cg?&wj<
6h,(wo3Y
b-Q>({=i
!6>~?gNd
String s = "Text here"; o;<Xo&
3/W'V,5G6
W~9tKT4
.ndCfdy~
二、常见错误2#: 没有克隆(clone)返回的对象 .|b$NM
#
,_u_'C*!
'2nhv,|.U
$jI3VB
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: _zn.K&I-*k
cacr=iX
:^WKT
"wnzo,
import java.awt.Dimension; QoIT*!
/***Example class.The x and y values should never*be negative.*/ IIF]/Ek]
public class Example{ %9N7Ln|%
private Dimension d = new Dimension (0, 0); X"e5Y!:M-
public Example (){ } [~#WG/!:
Bb^;q#S1
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ $oU*9}}Rn
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ h
WtVWVNL
if (height < 0 || width < 0) W=Mb
throw new IllegalArgumentException(); #_J@-f7^
d.height = height; 'H&2HXw&2
d.width = width; lpeEpI/gM
} dx&'fe*?
JedmaY06=
public synchronized Dimension getValues(){ X=!^] 3zH
// Ooops! Breaks encapsulation +*T7@1
return d; %]DP#~7[|
} Nj
xoTLI
} "UGY2skf;
%g$V\zmU
!tMuuK?IL=
M/ni6%x
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: [los dnH^?
=K{"{5Wb
t.&Od;\[/
{fF3/tL
Example ex = new Example(); C8e
!H
Dimension d = ex.getValues(); @}OL9Ch
d.height = -5; qY<'<T4\
d.width = -10; [t7]{d*
9{}1r2xW
dC$Em@Nb
*-q"3D`
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 <2|O:G
V9(@Y
L
ugn3+
Mjfx~I27
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 ##;Er47@^
[*H N"
8X`tU<Ab
O<"}|nbmQ[
更好的方式是让getValues()返回拷贝: wsN?[=l{s
U.XNv-M
y[\VUzD*'
wX[8A/JPD
public synchronized Dimension getValues(){
HA`@7I
return new Dimension (d.x, d.y); C] 9p5Hs
} =%gRW5R%
7L@K _ZJ
/.i.TQ]
AvSM^
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 !+4cqO
:GW&O /Yo
xw
T%),
k.^coI5
三、常见错误3#:不必要的克隆 ?jt}*q>X]
1Q4}'0U4
s]mY*@a%
9}uW}yJ
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: wUQw!%?>
@@rEs40
(85Fv&a
Q7C;1aO
/*** Example class.The value should never * be negative.*/ Z]WnG'3N
public class Example{ *U$!I?
private Integer i = new Integer (0); uN^=<B?B
public Example (){ } \,&co
$w0lrh[+
/*** Set x. x must be nonnegative* or an exception will be thrown*/ f<*Js)k
public synchronized void setValues (int x) throws IllegalArgumentException{ HXYRH
if (x < 0) pY~,(s|Qb
throw new IllegalArgumentException(); 0}FOV`n
i = new Integer (x); J}@z_^|"mJ
} {^rs#, W
!\#_Jw%y
public synchronized Integer getValue(){ pM&YXb?
// We can’t clone Integers so we makea copy this way. jhX[fT1m
return new Integer (i.intValue()); 1q3(
@D5~+
} ND,Kldji
} npyAJp
?EpSC&S\
8kt5KnD2
o|iYd
n\
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 RQB]/D\BO
ww{_c]My
7GSV
6^n0[7
方法getValue()应该被写为: kctzNGF|
K+)%KP
d:q +
F3lw@b3])
public synchronized Integer getValue(){ #:|+XLL
// ’i’ is immutable, so it is safe to return it instead of a copy. 9F-
)r'
return i; 'snn~{hG
} 5,;`$'?a%
G"59cv8z4R
KkMay
CBKkBuKuk
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: (ihP`k-.
<{:
8dOo Q
Dbaf0
?Boolean ow;R$5G
?Byte *P!e:Tm)
?Character 3!o4)yJWx
?Class $RwB_F
?Double oi&Wo'DX
?Float &Q=ZwC7#
?Integer omf Rs
?Long cZ+7.oDu
?Short yag}fQ(XH
?String GOB(#vu
?大部分的Exception的子类 4Kv[e]10(
F;!2(sPS
Q U
F$@)A
DtWwGC
四、常见错误4# :自编代码来拷贝数组 0g<K [mPr7
uw7{>9
-g/hAxb5
/_-;zL
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: 'QH1=$Su
b2&