代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 =1esUO[nx
r":<1+07
>T(f
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 i3Ffk+ |b
M!I:$DZt
wZOO#&X#r
g^]Q*EBa
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 x/4lD}Pw]
Mv9s
nxH$$}9
I{RktO;1
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 Z4(2&t^
kQH!`-n:T
e~-Dk .i
@L~y%#
一、常见错误1# :多次拷贝字符串 'l;?P
Np=*B_ @8
", |wG7N
K
++kiCoC
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 *wSl~J|ZM%
9Qkww&VEk
tRXM8't
,O-lDzcw
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: $ &^
,(z9
W&*{j;e9%I
/g8yc'{p
"qDEI}
String s = new String ("Text here"); X0U6:
vw(};)8
s0`uSQ2X
r@!~l1$s`
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: Y] n^(V
qQom=x
.Ftml' !
D~%cf
String temp = "Text here"; vAVoFL
String s = new String (temp); E@@quK
OE[/sv
}U(^ QB
<<`."RY#0
但是这段代码包含额外的String,并非完全必要。更好的代码为: '<Vvv^Er
-S|L+">=Z
kB8l`|
I
W!Xgse3
String s = "Text here"; grS,PKH
:J<S-d=
!BY=HFT
{-E{.7
二、常见错误2#: 没有克隆(clone)返回的对象 bX9}G#+U
\fKv+
%
,X(GwX
77
`/YE#M
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: 62zlO{ >rJ
'Gc6ZSLM
NT-du$!u
r!zNcN(%cs
import java.awt.Dimension; %_z]iz4
/***Example class.The x and y values should never*be negative.*/ $DQ
-.WI
public class Example{ a{8GT2h`4
private Dimension d = new Dimension (0, 0); d5i/:
public Example (){ } #F9$"L1Hg
{66vdAu&h<
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ <W5F~K
;41
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ -kJF@w6u
if (height < 0 || width < 0) ~f@;.
throw new IllegalArgumentException(); c]n4vhUa5
d.height = height; O+e8}Tmm
d.width = width; G>!"XK:fB
} J7$=f~$
^]v}AEcmW
public synchronized Dimension getValues(){ N@)g3mX>
// Ooops! Breaks encapsulation \"lzmxe0p
return d; [&39Yv.k,7
} ZNx{7]=a
} C
:e 'wmA
9~4Kbmr>q
v` B_xEl
t,H,*2
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: ngNg1zV/q
c8yD-U/-
Qr0GxGWU
A]n!d}?
Example ex = new Example(); &B3kzs
Dimension d = ex.getValues(); q| j;dI&
d.height = -5; 7IvCMb&%R
d.width = -10; 8CYJR/
@ce4sSo
6?t5g4q*nn
?+TD2~rD(
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 ElYHA
* T~sR'K+|
UM#]olh
HPgMVp'
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 Y>z(F\
> Euput\
tG{Vn +~/
Mr&]RTEE
更好的方式是让getValues()返回拷贝: q2SkkY$_]y
KYeA=
'fgDe
%k2zsM
public synchronized Dimension getValues(){ iffU}ce
return new Dimension (d.x, d.y); `|v#x@s
} `6M(`*Up
qItI):9U
[Z[)hUXE?
k20H|@g2
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 z,NHH):~
?Jma^ S
+6>Pp[%
BpKP]V
三、常见错误3#:不必要的克隆 Q/+a{m0f
(+>n/I6
A^m hPBT_
&iSQ2a!l8b
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: zb3ir|
&v`kyc
pLIBNo?
n^A=ar.
/*** Example class.The value should never * be negative.*/ Z@*Z@]FC
public class Example{ !7w-?1?D
private Integer i = new Integer (0); P_Zo}.{
public Example (){ } s}A]lY
00Rk %QV
/*** Set x. x must be nonnegative* or an exception will be thrown*/
!]`]67lC
public synchronized void setValues (int x) throws IllegalArgumentException{ {!="PnB
if (x < 0) %:/;R_
throw new IllegalArgumentException(); &fTCY-W[
i = new Integer (x); 6v1j*'
} d*G$qUiX
[C&c;YNp
public synchronized Integer getValue(){ 'R$~U?i8
// We can’t clone Integers so we makea copy this way. nQgn^z#
return new Integer (i.intValue()); c9&
8kq5
} u|Ai<2b$
} Z`f _e?
K[9 <a>D`
gi`ZFq@
;P{ *'@
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 JAn3
d,>l;l
>,y QG+
cEN^H
方法getValue()应该被写为: q}8R>`Z{
XR+2|o
,p2
Di
%AJdtJ@0H
public synchronized Integer getValue(){ "47nc1T+n
// ’i’ is immutable, so it is safe to return it instead of a copy. fGxa~Unx
return i; #a0 (Wh7
} |GsLcUv6
=]"PSY7p
fL@[B{XMM
g,1\Gj%y
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: Gh< r_O~L3
ugL$W@
[m4<j
OU[Sm7B
?Boolean QTDI^ZeuF
?Byte KSexG:Xb
?Character %>s y`c
?Class r9QNE>UG
?Double 4RK^efnp
?Float 1,/oS&?E
?Integer m3,v&Z
?Long #A|MNJ%m
?Short KUp
lN1Sy
?String "B\qp "N
?大部分的Exception的子类 Vw,dHIe(3
)S5Q5"j&=f
`fL$t0"
Y/4B*>kl
四、常见错误4# :自编代码来拷贝数组 J"z8olV
J4JKAv~3
7N:,F9V<
Z)ObFJMG5
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: @1kA%LLK
[#.QDe
d6Z;\f7[
.qHgQ_%
public class Example{ e06r5%|.%
private int[] copy; U>{z*D
/*** Save a copy of ’data’. ’data’ cannot be null.*/ :0]KIybt
public void saveCopy (int[] data){ =T+<>/[
copy = new int[data.length]; {%
;tN`{M
for (int i = 0; i < copy.length; ++i) tewC *%3V
copy = data; VbZZ=q=Kd
} gXF.e.uU
} YUc&X