代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 Ytn9B}%o
;AG8C#_
.]8ZwAs=&
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 d[iQ`YW5
bV^rsJm
x]}^v#
/CrSu
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 uy>q7C
p*XANGA
T$8)u'-pa
?g_3 [Fk
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 ; 5*&xz
'TTLo|@"-
Xr,1&"B&t
G<L;4nA)
一、常见错误1# :多次拷贝字符串 yuh *
ik)|{%!K]H
S\CCrje
?qb}?&1
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 2=*H 8'k
Amtq"<h9a
wW Lj?;bx
'p^t^=dQ
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: \[;0KV_
)*$lp'~7N
k$n|*kCh
/J]5H
String s = new String ("Text here"); jk;j2YNPw
P0;n9>g
/p/]t,-j2
|Tv#4st
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: `aOFs+<)
* `JYC
z0d.J1VW
/4y o`
String temp = "Text here"; *IB4[6
String s = new String (temp); &sl0W-;0
" s,1%Ltt
GV1pn) 4
.#EFLXs
但是这段代码包含额外的String,并非完全必要。更好的代码为: v&6-a* <Z
Pd8![Z3
B`EJb71^Xy
9=s<Ld
String s = "Text here"; ko!)s
R!HXhQ
W~)}xy
y#`tgJ:
二、常见错误2#: 没有克隆(clone)返回的对象 v_yw@
t$` r4Lb9/
@="Pn5<]C
F/]2G^-
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点:
\__i
kpuz]a7pK
:@yEQ#nFp
zOJ%}
import java.awt.Dimension; A@`}c,G
/***Example class.The x and y values should never*be negative.*/ Xu{1".\
public class Example{ z[N`s$;
private Dimension d = new Dimension (0, 0); &w\{TZ{
public Example (){ } ::`HQ@^
Fw_#N6Q
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ <3nMx^
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ )Om*@;r(
if (height < 0 || width < 0) ~-k9%v`
throw new IllegalArgumentException(); jVi) Efy
d.height = height; T9=I$@/
d.width = width; 1Yq!~8
} X;$+,&M"
\$K20)
public synchronized Dimension getValues(){ 5%"V[lDx@
// Ooops! Breaks encapsulation ;[ZEDF5H
return d; j;zM{qu_
} xR~hwj
} ibcRU y0%
`>o{P/HN
hDDn,uzpd
*;W+>W
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: I{|O "8
U4'#T%*
6bg
;q(*7
.'6gZKXY
Example ex = new Example(); 7g^]:3f!
Dimension d = ex.getValues(); XPc^Tq
d.height = -5; [NTzcSN.
d.width = -10; &$+AXzn
,~U>'&M;
!|(-=2`
1er
TldX
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 3l~^06D
KYm0@O>;
p
T?}Kc
hE{K=Tz$
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 <)Dj9' _J
X0HZH?V+
hPB9@hT$
Q0sI(V#
更好的方式是让getValues()返回拷贝: hgG9m[?K
M-VX;/&FR
"nynl'Ryk
'@v\{ l
public synchronized Dimension getValues(){ SO/c}vnBB
return new Dimension (d.x, d.y); E: 68?IJ
} @mCEHI{P
!)f\%lb
.^`{1%
yX>K/68
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 u,ho7ht3(
WCZjXDiwJ
:U|1 xgB
RNk\.}m
三、常见错误3#:不必要的克隆 k t#fMd$
u[;\y|75
Q-oktRK
(XTG8W sN
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: k=$TGqQY?
; nfdGB
bW427B0
z_$% -6
/*** Example class.The value should never * be negative.*/ BKCiIfkZ
public class Example{ 3DX*gsx(
private Integer i = new Integer (0); ^CYl\.Y@
public Example (){ } Qp5VP@t
;+R&}[9,A)
/*** Set x. x must be nonnegative* or an exception will be thrown*/ ^LnTOdAE
public synchronized void setValues (int x) throws IllegalArgumentException{ B3`5O[6
if (x < 0) {lzWrUGO
throw new IllegalArgumentException(); QW~E&B%
i = new Integer (x); 6Igz:eX
} Y1\ }5k{>
&&8x%Pml
public synchronized Integer getValue(){ B:Oa}/H
// We can’t clone Integers so we makea copy this way. /{J4:N'B>
return new Integer (i.intValue()); n+9=1Oo"
} NN{?z!
} AR%4D3Dma
Tk[ $5u*,
!PlEO 2at
e)k9dOR
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 bH nT6Icom
nc29j_Id
e2Pcm_Ahv*
q9K)Xk$LF
方法getValue()应该被写为: |3b^~?S
r|8d
4
cl3K<'D
a.\:T,cP>
public synchronized Integer getValue(){ 3ZPWze6
// ’i’ is immutable, so it is safe to return it instead of a copy. sE<V5`Z=
return i; 7aRi5
} !*&V-4
Pj^{|U2 1
05#1w#i
PdFKs+Z`
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: F,F4nw<W
2,oKVm+
k"%~"9
2zA4vZkbcw
?Boolean :pY/-Cgv
?Byte *;slV3
?Character +o{R _
?Class M/'sl;
?Double [S%_In
?Float wmL'F:UP
?Integer 2wg5#i
?Long )EuvRLo{S7
?Short I_#kgp
?String ^/>(6>S^M
?大部分的Exception的子类
x+:UN'"r
mDABH@R
#G|RnV%t$~
=o(5_S.u;
四、常见错误4# :自编代码来拷贝数组 9&2O9Nz6
8^2oWC#U(
lv<*7BCp
I*{nP)^9
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: dL 1tl
LmrfN?5
myQagqRx
~H_/zK6e
public class Example{ nNV'O(x}
private int[] copy; /9*B)m"
/*** Save a copy of ’data’. ’data’ cannot be null.*/
(N6i4
g6
public void saveCopy (int[] data){ V7Lxfoa4
copy = new int[data.length]; 7kLz[N6Ll
for (int i = 0; i < copy.length; ++i) [PM2\#K
copy = data; (Z q/
} jD]~ AwRJ
} N^G
Mp,8
J?1 uKR
::lKL
wu!59pL
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: a2O75 kWnm
bHYy }weZ
X/!o\yyT
6 7.+
.2
void saveCopy (int[] data){ wE>\7a*P%
try{ iL&fgF"'
copy = (int[])data.clone(); O,
wJR
}catch (CloneNotSupportedException e){ K(rWNO
// Can’t get here. [wOn|)&
&
} n1t*sk/J
} l`{\"#4
=`F(B
IB"w&