代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 E=>FjCsu<-
G5CI<KRK#
1XD,uoxB
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 a{R%#e\n
P%#<I}0C
EJsM(iG]~M
vJ'2@f$
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 s;3= {e.
M7@2^G]p
8DegN,?
r]b_@hT',
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 ~S8* t~
!t gi
mT.u0KUIy
[/e<l&y
一、常见错误1# :多次拷贝字符串 bI:zp!-.
MBqt&_?K
JwAYG5W
f}x.jxY?
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 Y*H|?uNF
Pmh8sw
RLLTw ?]$
cNM3I,o7
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: T[j#M+p
ZuS0DPS`L
#6+@M
b/C`Jp
String s = new String ("Text here"); ~c %hWt
kic/*v\6@
YgUvOyaQXf
5u*-L_
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: 'H
\9:7
no<
^f]33
@>W(1mRi
Z@]e{zO
String temp = "Text here"; Z yE `/J'
String s = new String (temp); DV<` K$ET
cd$m25CxC
a{
?`t|
PfC!lI
BU
但是这段代码包含额外的String,并非完全必要。更好的代码为: I?ae\X@M
2T V X)q<\
m^GJuPLW
Si6al78
String s = "Text here"; LIZRoG8
=o& >fw
K':K{ee>
YKO){f5
二、常见错误2#: 没有克隆(clone)返回的对象 L$IQuy
L5
veX}
%*`J k#W:
o1FF"tLkN
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: y0'Rmk,
PYM(Xz$
7z!tKs"TMT
wnM9('\
import java.awt.Dimension; %l,,_:7{
/***Example class.The x and y values should never*be negative.*/ Qj.l:9%
public class Example{ 4KH45|;3
private Dimension d = new Dimension (0, 0); ~%SH3$
public Example (){ } _Jme!Oaa
}Rz3<eON
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ eC[$B99\
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ kH]yl
2
if (height < 0 || width < 0) Q4f/Z
throw new IllegalArgumentException(); Hhari!RXC
d.height = height; 2@%$;.
d.width = width; <iH`rP#
} &Nczv"TM
2\7`/,U6
public synchronized Dimension getValues(){ :k.NbN$i\
// Ooops! Breaks encapsulation pO ml8SQf
return d; %2XHNW
} z#]Jv!~EPE
} `<\1[HJ\
X&0 uI*r
RV5n,J
uWM{JEOl
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: 8;Yx<woR
{
T-'t/0e(
Gcig*5
BbgnqzU
Example ex = new Example(); N1|$$9G+
Dimension d = ex.getValues(); ZE2$I^DY-
d.height = -5; 0IfKJ*]M
d.width = -10; XI22+@d6
IFDZfx
'+$EhFwD
}lfnnK#
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 ub;ZtsM,%
8"fD`jtQ
/XhIx\40l
{|1Y:&M?
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 .8y3O]
F@<CsgKB-
ad:&$
7D!u1?]d{
更好的方式是让getValues()返回拷贝: KN7n@$8YM
%oq[,h
<X
*X,
/7C
j4;0|zx-i
public synchronized Dimension getValues(){ A9kzq_3
return new Dimension (d.x, d.y); Zxbo^W[[
} FvJd8kV
Vv8jEZ8
V( -mD
gMaN)ESqd4
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 ho0@ l
Q)LM-ZJKQ
x}Aw)QCh+r
VxXzAeM
三、常见错误3#:不必要的克隆 ]Yvga!S"C
^9ePfF)5
F$hYKT2|
LWHd~"eU
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: qHP78&wUx
8`fjF/
$`-4Ax4%
=Q[b'*o7
/*** Example class.The value should never * be negative.*/ T+<A`k: -
public class Example{ `/~8}Y{
private Integer i = new Integer (0); -tyK~aasQ
public Example (){ } ngat0'oa
/l<<_uk$
/*** Set x. x must be nonnegative* or an exception will be thrown*/ 1$81E.
public synchronized void setValues (int x) throws IllegalArgumentException{ V2i@.@$j
if (x < 0) )I$q 5%q8
throw new IllegalArgumentException(); w);6K[+;
i = new Integer (x); Vgyew9>E
} 6p?JAT5
\@1=stK:F
public synchronized Integer getValue(){ &bp=`=*
// We can’t clone Integers so we makea copy this way. e`v`XSA[p
return new Integer (i.intValue()); @$2))g`
} q7-L53.x
} ~I799Xi
ZG du|
>+
4huRb
gF5a5T,
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 (>I`{9x>6
E4W zU
LbZ:&/t^y8
y:h}z).
方法getValue()应该被写为: hweaGL t0
ZJ 77[
*L'>U[Pl7
OLvcivf
public synchronized Integer getValue(){ NU*fg`w
// ’i’ is immutable, so it is safe to return it instead of a copy.
u*#ZXW
return i; \;mH(-
} !k/Pv\j/R
Kbb78S30
P b]3&!a
e4z1`YLsG
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: +5&wOgx
k!KDWb
-~QHqU.
8-Hsgf.*
?Boolean Z+StB15
?Byte 3:f[gV9K
?Character r@o6voX
?Class 0`I-2M4F*Q
?Double DmBS0NyR7Y
?Float Z KOXI%~Mc
?Integer _"#!e{N|
?Long n]u<!.X
?Short yH<$k^0r*
?String
W":PG68
?大部分的Exception的子类 `St.+6^J
fS"Hr 0
W5' 3$,X9
.]9c /
四、常见错误4# :自编代码来拷贝数组 T1r3=Y4
jh.@-
kee|42E
f7 'q-
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: a+9*@z2
AT\qiznvP
xGG,2W+z
6O22P?v
public class Example{ \J6hI\/4^
private int[] copy; &V<W>Y>|l*
/*** Save a copy of ’data’. ’data’ cannot be null.*/ 7oR:1DXw|
public void saveCopy (int[] data){ )
9oH,gZ
copy = new int[data.length]; )#}mH @
for (int i = 0; i < copy.length; ++i) KPpHwcYxT
copy = data; G5,~Z&}YS
} )|I5j];L
} wfP5@ !I
"sKa`WN}
u^j {U}
MCP "GZK6W
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: `W-&0|%Ta
@YH+cG|
pMJ1v
7 uarh!
void saveCopy (int[] data){ k3t78Qg
try{ D>!6,m2
copy = (int[])data.clone(); eJo3 MK
}catch (CloneNotSupportedException e){ P/ oXDI8
// Can’t get here. tWdhDt8$&
} cf7v[ZZ}
} w?,M}=vg
Y=T'WNaL)0
}rdIUlVO\
c0Dmq)HK?
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: kpI{KISQu
\M"UmSB o
olW|$?
6ITLGA
static int[] cloneArray (int[] data){ *E~VKx1
try{ 5eA8niq#
return(int[])data.clone(); jkF8\dR
}catch(CloneNotSupportedException e){ :EtMH(
// Can’t get here. '>v^6iS
} )!Bd6-
} D5an\gE
X{g%kf,D=
gLSA!#[h
((rv]f{
这样的话,我们的saveCopy看起来就更简洁了: =]>NDWqpHN
=9LC<2
e\b`n}nC
PjIeZ&p
void saveCopy (int[] data){ =D^TK-H
copy = cloneArray ( data); s6}Xt=j
} ?QA\G6i4
!tHt,eJy
G^(}a]>9
1KYN>s:
五、常见错误5#:拷贝错误的数据 ]p~IYNl2%j
0~ &"
T|"7sPgGR
Se!)n;?7Sw
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: Fn^C{p^
GyC /_ntn
- /c7nF
%k0EpJE%
import java.awt.Dimension; dS`Bk6Y
/*** Example class. The height and width values should never * be IF@HzT;Q
negative. */ &l