代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 2UJjYrm
>* >}d%
RDWUy(iX
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 3}9c0%}F
o/5loV3h
1&Ruz[F5
7\nR'MOZ
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。
Tq*K
=^
o"-*,:Qe
pZaOd;t
nb ,+!)+
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 %AnqT|\#,
1aBQ.-E-
"[tb-$ER
&D*22R4{CX
一、常见错误1# :多次拷贝字符串 %1^E;n
;;? Zd
.*W_;F o
S@[B?sNj
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 6
r}R%{
\4 5%K|
0G}]d17ho
C])b 3tM,7
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: 1]% ]"JbV
+(l(|lQy$
@^UnrKSd
l11+sqg
String s = new String ("Text here"); $>=?'wr
CZ4Nw]dtR
a15kFun
.tH[A[/1 a
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: .\:{6_
,3~[cE<4
?|,-Bft3
~![J~CkPS
String temp = "Text here"; FvVR \a
String s = new String (temp); 7;x}W-`iF
%MH!L2|
^a{cK
LZF%bJv
但是这段代码包含额外的String,并非完全必要。更好的代码为: $zv&MD!&h
nTQ&nu!
,wPvv(b]a
! uX0G4
String s = "Text here"; .Qz412
Wd<|DmSy
d:SLyFD$q
;r.0=Uo9]
二、常见错误2#: 没有克隆(clone)返回的对象 ~"8D]
?@YABl
S?K x:]
%|\Af>o4d
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: |p\vH#6y+
O\&-3#e
pf[m"t6G~
S&Szc0-|k
import java.awt.Dimension; u-%|ZSg
/***Example class.The x and y values should never*be negative.*/ !Un&OAy.!
public class Example{ rS&"UH?c7
private Dimension d = new Dimension (0, 0); `m7w%J.> n
public Example (){ } ~H~iKl}|7
Iq["(!7E5
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ SL ) ope
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ i4s_:%+
if (height < 0 || width < 0) eb#p-=^KP
throw new IllegalArgumentException(); +u\kTn
d.height = height; 8LH\a.>
d.width = width; SQ0?M\D7
} }K'gjs/N;
}Md5a%s<
public synchronized Dimension getValues(){ fs,]%g^
// Ooops! Breaks encapsulation jhF&
return d; :HW\awv
} PPMAj@B}V
} >^N{
&8xwR
3<R8_p
TkyP_*
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: XS oHh-
4Mck/i2
Iy8fN"I9D
N.D7
Example ex = new Example(); QpI\\Zt6
Dimension d = ex.getValues(); lV
M)'m
d.height = -5; 0Q4i<4 XW
d.width = -10; 7Adg;
U6x$R O!
hy|Yy&-
Lh;U2pA
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 )~2~q7
7GG:1:2+>
>O$JS,
zz**HwRt
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 [
@ASAhV^+
Sk7sxy<F'
/C\tJs
2m{d>
更好的方式是让getValues()返回拷贝: -50Qy[0. "
sEzl4I
k;V (rf`
)1, U~+JFU
public synchronized Dimension getValues(){ `)WC|= w2
return new Dimension (d.x, d.y); M7gb3gw6
} g)L<xN8
[M/0 Qx[,
;m#_Rj6
?mn&b G
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 Ls6C*<8
;>*Pwz`~jT
,Z$!:U
U~I
y),5
三、常见错误3#:不必要的克隆 d\-v+'d*+
?"-1QG
V:<Z
9!?Ywc>0#
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: 7xh91EU:4
U%r|hn3
AkAQ%)6qV
u2
t=*<X
/*** Example class.The value should never * be negative.*/ RaC8Sq7hW
public class Example{ *4OB
88$
private Integer i = new Integer (0); 8T5W6Zs1
public Example (){ } 76(/(v.x
DI0Wk^ m
/*** Set x. x must be nonnegative* or an exception will be thrown*/ Pe/8=+qO
public synchronized void setValues (int x) throws IllegalArgumentException{ 6lob&+
if (x < 0) ^I:f4RWo
throw new IllegalArgumentException(); ~A03J:Yc7
i = new Integer (x); X4CiVV
} j.kv!;Rj=
nq
qqP
public synchronized Integer getValue(){ k7kPeq
// We can’t clone Integers so we makea copy this way. }uiD8b{I
return new Integer (i.intValue()); 3g87i r
} a[=;6!
} p\22_m_wd
hV}C.- 6h
zK>}x=
h@CP
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 'OI(MuSn
UK5u"@T
k2/t~|5
h{ T{3
方法getValue()应该被写为: R5N~%Dg)3
^Eif~v
dR!x)oO=
SZD7"m4
public synchronized Integer getValue(){ e/b
|
sl
// ’i’ is immutable, so it is safe to return it instead of a copy. vD76IG j m
return i; 3$4I
} 3w}ul~>j
G *
=>
w* \JA+
2sYz$ZGC"#
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: &mkL4jXG
,wZq~;2
4ufT-&m};s
*nB-]
w/
?Boolean "#P#;]\ `
?Byte #'4Psz
?Character !.{"Ttn;s
?Class 7QdboEa
?Double [&sabM`Ul
?Float K"cV7U rE
?Integer :Q ?p^OC
?Long j[4l'8Ek
?Short Uc9hv?
?String E&dxM{`
?大部分的Exception的子类 V3<#_:;
8&SWQ
LcTTfb+<
h{:
]'/@~
四、常见错误4# :自编代码来拷贝数组 Y-+JDrK
Z5eM
qNWSDZQ
5a|{ytP
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: =klfCFwP
DD}YbuO7
"a-;?S&
mhI
public class Example{ {7Hc00FM
private int[] copy; -s^)HR
l
/*** Save a copy of ’data’. ’data’ cannot be null.*/ d%:J-UtG"
public void saveCopy (int[] data){ Y/T-2)D
copy = new int[data.length];
@<koL
for (int i = 0; i < copy.length; ++i) \|C*b<
copy = data; T0N6k acl
} q<[o 4qY
} b+$E*}
a H\A
ko"xR%Q
a5 pXn v]A
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: gOr%N!5
@M6F?;
:qj7i(
p@ U[fv8u
void saveCopy (int[] data){ pGr4b:N
try{ v oO7W"
copy = (int[])data.clone(); vCUbbQz
}catch (CloneNotSupportedException e){
7n*"9Ai(
// Can’t get here. AWg'J
} "A0y&^4B@
} ,z#S=I
0,B"p
.:O($9^Ho
:r7!HG_
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: !Y 9V1oVf"
7bQST0 ?
T1%}H3
+v/-qyA
static int[] cloneArray (int[] data){ ^O!;KIe{g
try{ TLq^5,qG
return(int[])data.clone(); Js^(mRv=
}catch(CloneNotSupportedException e){ Zr(eH2}0D
// Can’t get here. Kw(S<~9-@
} "q
KVGd
} bX
6uGu
7
a%/D~5Z
M\RHFTB<C
Qe_C^(P
这样的话,我们的saveCopy看起来就更简洁了: '7g]@Q7
z:=E-+
:<HLw.4O
hV&