代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 CM|?;PBuv
9Eu.Y
z^'3f!:3
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 7G zf>n
$GB/}$fd&
rzsAnLxo
kzcl
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 2= S;<J
S&^i*R4]
BUvE~l.,|
^`?2g[AA
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 Xt& rYv
AU0pJB'
+`'=K ;{U
gE;r;#Jt4
一、常见错误1# :多次拷贝字符串 }V:ZGP#!'
Y)lYEhF
2)cq!Zv
:|%k*z
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 }04EM
-q'G]}
aGSix}b1P
uI lm!*0
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: yUd>EnQna
qD!qSM
M5\$+Tu
v{tw ;Z#
String s = new String ("Text here"); H;D5)eJ90
Fp=O:]
9+S$,|9
' m^nKG$"
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: meJ%mY
'ip2| UG
CmP_9M?ce
~[a6
String temp = "Text here"; [0>I6Jl
String s = new String (temp); eICavp
rD_\NgVAs
|[./jg"
mZ_643|
但是这段代码包含额外的String,并非完全必要。更好的代码为: +V
Oczl=
tleWJR8oc
W!jg
Rq@M~;p
String s = "Text here"; 4J5 RtK
ag02=}Q'r
/Pv
dP#!
GUDz>(
二、常见错误2#: 没有克隆(clone)返回的对象 yor6h@F1
0^('hS&
(pv6V2i
|X47&Y
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: vCX
54
0+{CN|0
$ VTk0J-W
2]:Z7Ji
import java.awt.Dimension; eHE?#r16Z
/***Example class.The x and y values should never*be negative.*/ +d!"Zy2|B
public class Example{ C.`!?CW
private Dimension d = new Dimension (0, 0); lY$9-Q(
public Example (){ } JavSR1_
nq%GLUH
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ B>r>z5
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ 6<SX%Bc~
if (height < 0 || width < 0) FE'F@aS\
throw new IllegalArgumentException(); e|
Sw+fhy<
d.height = height; b|Sjh;
d.width = width; p >h&SD?b
} H: rrY
9v3%a3
public synchronized Dimension getValues(){ ab8F\%y-8
// Ooops! Breaks encapsulation 5H!6m_,w
return d; k#"}oI{<
6
} 2fFGS.l
} 'U*Kb
\Xpq=2`
v5A8"&Jr
!n3J6%b9y/
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: U2CCjAgRs
C^ 1;r9
^kh@AgG^
5pz(6gA
Example ex = new Example(); <G60R^o
Dimension d = ex.getValues(); ^3lEfI<pBm
d.height = -5; ' Ivr =-
d.width = -10; mw flx8
wvz_)bN~A
r0:I
X;QhK] Z
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 d: LP8
-_T@kg[0zB
JAU:Wqlg1
ZUK'z
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 ;t5e]
l~'NqmXe
o l8|
859ID8F
更好的方式是让getValues()返回拷贝: 7thB1cOJ
cUD}SOW
aP` V
tWa_-Un3
public synchronized Dimension getValues(){ [fIElH<
return new Dimension (d.x, d.y); 50HRgoP5Y
} vI0::ah/
2`nOYK
|n*<H|
TW!>~|U)y
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 zOT(>1'
a[A*9%a
We:b1sZR
%bZ}vJ5b
三、常见错误3#:不必要的克隆 FWl'='5L
1%k$9[!l%
MClvmv^
sY@x(qkIOc
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: 65AG#O5R
(@ixV$Y
m6YDyQC
bqSp4TI
/*** Example class.The value should never * be negative.*/ WN9K*Tt~o&
public class Example{ K-,8~8[
private Integer i = new Integer (0); *WK0dn
public Example (){ } 4@1C$|k
@`H47@e
/*** Set x. x must be nonnegative* or an exception will be thrown*/ Hribk[99
public synchronized void setValues (int x) throws IllegalArgumentException{ >' e(|P4
if (x < 0) Ln@n6*%(/
throw new IllegalArgumentException(); q8[I`
V{
i = new Integer (x); 5@< D6>6
} .E&-gXJ4
^E= w3g&
public synchronized Integer getValue(){ :y8wv|m
// We can’t clone Integers so we makea copy this way.
QX>Pni
return new Integer (i.intValue()); $*z>t*{7
} iES?}K/q
} 3PgiV%]
L0dj 76'M
t%Hy#z1W_
o<\9OQ0
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 kSq1Q#Bxq
\'.#of
TaTs-]4
5*IfI+}
方法getValue()应该被写为: flzHZH
r'~^BLT`#
iQJ[?l`
w|0w<