代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 ]%\,.&=hT
a:kAo0@":j
%()d$.F
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 %go2tv:|W
)H8_.]|
MqyjTY::Xg
%pC<T*f
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 ,/;Aew;
1'kO{Ge*p:
=C"[o\]VV
R+ * ; [
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 pwFp<O"
ewDYu=`*
&,X}M
mG~_*8}e<
一、常见错误1# :多次拷贝字符串 ("$/sT
=%Y1] F
YagfCi ?
k(gbUlCc
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 K9!HW&?<|
}LHYcNw^z
]33!obM
TOwd+]B
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: &?<uR)tl
"TZq")-
(lk9](;L
TCr4-"`r-{
String s = new String ("Text here"); fr17|#L+s
( }-*irSsj
2g.lb&3W
_&<n'fK[
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: ' \JE>#
GO"`{|o
!3Q0Ahf
Y.^L^ "%dF
String temp = "Text here"; :@A&HkF
String s = new String (temp); Y
},E3<
~Y 6'sM|
O<u=Vz3c~0
>O'\
jp}$l
但是这段代码包含额外的String,并非完全必要。更好的代码为: _~kw^!p>Kr
'Wlbh:=$
Nx}nOm
*PJH&g#Ge
String s = "Text here"; x|H`%Z
bA;OphO(
a:FU- ^B4~
`Os=cMR
二、常见错误2#: 没有克隆(clone)返回的对象 bI):-2&s}
wu
<0or2
i:lc]B
0PzSp ]
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: f56yI]*N=<
1w,_D.1'
dwO fEYC
RS5<] dy
import java.awt.Dimension; f:o.[4p2
/***Example class.The x and y values should never*be negative.*/ ~_ THvx1
public class Example{ "LBMpgpU
private Dimension d = new Dimension (0, 0); 0~|0D#klB
public Example (){ } aLk3Yg@X
fSo8O
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ 19 5_1?'<
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ 0'^M}&zCi
if (height < 0 || width < 0) Y}~sTuWU
throw new IllegalArgumentException(); 3Y#Q'r?
d.height = height; `3TR`,=
d.width = width; 7B?Y.B
} 7)?C+=,0
H2X_WSwm
public synchronized Dimension getValues(){ @0 +\:F
// Ooops! Breaks encapsulation kmQ:wf:
return d; LdUz;sb
} G% F#I
} ZO+RE7f*?c
SN6 QX!3
g2OnLEF]s
pPReo)
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: ~q>jXi
vYgJu-Sl
/[R=-s ;
Z{8%Cln
Example ex = new Example(); * #yF`_p
Dimension d = ex.getValues(); K\xz|Gq
d.height = -5; V@'Xj .ze
d.width = -10; `M@ESA(e
p=+Y7NE)
xP8/1wd.
0h-NT\m
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 gtKih
O,$*`RZpx
fB2ILRc
FZ*"^=)`G
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 " ityx?
CD1Ma8I8
R|?n
B`SX3,3
更好的方式是让getValues()返回拷贝: snbXAx1L
SSe;&Jk2d
={g"cx
Et6j6gmif
public synchronized Dimension getValues(){ Ey@^gHku\
return new Dimension (d.x, d.y); h#1:ypA6l
} [^"}jbn/
)nd^@G^
vJE=H9E
*|&Y ,H?
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 g *5_m(H
g[cnaS|?
=!Ik5LiD
{i>AQ+z61f
三、常见错误3#:不必要的克隆 _L,~WYRo
MN: {,#d0
&A:&2sP8
Dj/Hz\
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: a1,)1y~
?K-4T
\8(Je"S
1^_W[+<S/
/*** Example class.The value should never * be negative.*/ h;=~%2Y
public class Example{ F:zmO5L5
private Integer i = new Integer (0); =Jp:dM*
public Example (){ } O%t? -h
B:>:$LIL
/*** Set x. x must be nonnegative* or an exception will be thrown*/ QPuc{NcB>
public synchronized void setValues (int x) throws IllegalArgumentException{ =svFw&q"
if (x < 0) JMAdsg/
throw new IllegalArgumentException(); %[XP}L$
i = new Integer (x); _'p/8K5)=
} =CzGI|pb
T
m"B
public synchronized Integer getValue(){ |AvPg
// We can’t clone Integers so we makea copy this way. D;sG9Hky
return new Integer (i.intValue()); 0hY3vBQ!
} 4KH'S'eR
} (-<hx~
wOH:'sk["
4m~y%>
&
x(?Rm,
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 fb Bu^]^S
=8_b&4.:&
+ 149 o2
8Hq4ppC
方法getValue()应该被写为: IlJ"t`Z9)
NXD-
y,?=,x}o#
=1 Plu5
public synchronized Integer getValue(){ CG uuadNI
// ’i’ is immutable, so it is safe to return it instead of a copy. #x 6/"Y2
return i; -axKnfj
} CUDA<Fm
4{>r_^8
A}"|_&E
e;v7!X
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: dPO"8HQ
CLND[gc
#-%D(=&I
M|nLD+d~8
?Boolean OcB&6!1u
?Byte ;$tdn?|
?Character qFVZhBC
?Class j6s j 2D
?Double Z71_D
?Float &wQ<sVQ0$
?Integer V 2Xv)
?Long Dx\~#$S!=
?Short t!4 (a0\$F
?String hq4&<Zr(
?大部分的Exception的子类 P%B|HnG^
mN-O{k0\
FOD'&Yb&
e"1mdw"
四、常见错误4# :自编代码来拷贝数组 ^/%o
I;O{
a<*+rGI
'*[7O2\%/
5NkF_&S_1
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: e'~Qe_
Uhu?G0>O
SN|!FW.*:
C;ab-gh
public class Example{ YdV.+v(30
private int[] copy; Z/Wf
/*** Save a copy of ’data’. ’data’ cannot be null.*/ Wrbv<8}%c
public void saveCopy (int[] data){ ke@OG! M /
copy = new int[data.length]; {^
BZ#)m|
for (int i = 0; i < copy.length; ++i) zEjl@Kf
copy = data; ys!O"=OJ
} Dhm;K$T
} N9ipw r'P
u/k'
ry=
NXLb'mH~
I3Co
这段代码是正确的,但却不必要地复杂。saveCopy()的一个更好的实现是: iTevl>p!
FoE}j
%cs"PS
(4z_2a(Dl,
void saveCopy (int[] data){ =f@71D1
try{ yfwR``F
copy = (int[])data.clone(); wo62R&ac
}catch (CloneNotSupportedException e){ LQqba4$
// Can’t get here. !C4)P3k
} .WeSU0XG
} Q@p'nE,
&n]v
BZOl&G(
Z9H2! Cp
如果你经常克隆数组,编写如下的一个工具方法会是个好主意: ^0"fPG`
DmWa!5
S^q^=q0F
m
Urb
static int[] cloneArray (int[] data){ r:rPzq1
try{ 5~>j98K
return(int[])data.clone(); ^69(V LK
}catch(CloneNotSupportedException e){ TN Z-0
// Can’t get here. -~sW@u)O
} 9k4z__K e
} p Dg!Cs
io"NqR#"v
XiV*d06{
J*ofa>
这样的话,我们的saveCopy看起来就更简洁了: lX.1B&T9Lr
0(C[][a*u
(g dzgLHy
UQI!/6F
void saveCopy (int[] data){ .: wg@Z
copy = cloneArray ( data); rD6NUS
} cEXd#TlY~X
<`q-#-V@
w3iX "w
^^V+0 l
五、常见错误5#:拷贝错误的数据 zWN]#W`
@<OsTF L
-0'<7FSQ
od@!WjcM[8
有时候程序员知道必须返回一个拷贝,但是却不小心拷贝了错误的数据。由于仅仅做了部分的数据拷贝工作,下面的代码与程序员的意图有偏差: R0w~ Z
*?Oh%.HgF
?y%Mm09
8u*Q^-fpo0
import java.awt.Dimension; xt@v"P2Ok
/*** Example class. The height and width values should never * be }If,O
negative. */ b;`MHEzw&q
public class Example{ ~urk
Uz
static final public int TOTAL_VALUES = 10; ;Srzka2
private Dimension[] d = new Dimension[TOTAL_VALUES]; e*<pO@Uy
public Example (){ } nbw8YO(=
wd,6/5=lh
/*** Set height and width. Both height and width must be nonnegative * or an exception will be thrown. */ t[({KbIy
public synchronized void setValues (int index, int height, int width) throws IllegalArgumentException{ J,h'eY5
if (height < 0 || width < 0) JBV
06T_4o
throw new IllegalArgumentException(); uw>y*OLU+
if (d[index] == null) mmC MsBfL
d[index] = new Dimension(); X#W6;?Z\
d[index].height = height; B|>eKI
d[index].width = width; uYE"OUNWL
} QVb{+`.7
public synchronized Dimension[] getValues() BL0xSNE**
throws CloneNotSupportedException{ x {Rj2~KC
return (Dimension[])d.clone(); ? _[q{i{
} H_iQR9Ak7
} s2tNQtq0W
HS.eK#:N
(6)|v S
^MWp{E
这儿的问题在于getValues()方法仅仅克隆了数组,而没有克隆数组中包含的Dimension对象,因此,虽然调用者无法改变内部的数组使其元素指向不同的Dimension对象,但是调用者却可以改变内部的数组元素(也就是Dimension对象)的内容。方法getValues()的更好版本为: mphs^k< Z
1<]?@[l<
;%AY#b4m
UHI<8o9
public synchronized Dimension[] getValues() throws CloneNotSupportedException{ /Zz[vf
Dimension[] copy = (Dimension[])d.clone(); }Zp[f6^Q
for (int i = 0; i < copy.length; ++i){ meD83,L~N
// NOTE: Dimension isn’t cloneable. $ -]9/Ct
if (d != null) u\K`TWb%
copy = new Dimension (d.height, d.width); lo7>$`Q
} `j6O
return copy; k
c L
+
} sEa| 2$
M\08 7k
SR4 mbQ:
j3o?B
在克隆原子类型数据的多维数组的时候,也会犯类似的错误。原子类型包括int,float等。简单的克隆int型的一维数组是正确的,如下所示: -9 |)O:
4?`*#DPl
@Y%i`}T%(
;A?86o'?
public void store (int[] data) throws CloneNotSupportedException{ :9|CpC`.
this.data = (int[])data.clone(); L3S29-T
// OK C61E=$
} |kHzp^S
7Zh#7jiZ`
fHF*#
SG)|4$"
拷贝int型的二维数组更复杂些。Java没有int型的二维数组,因此一个int型的二维数组实际上是一个这样的一维数组:它的类型为int[]。简单的克隆int[][]型的数组会犯与上面例子中getValues()方法第一版本同样的错误,因此应该避免这么做。下面的例子演示了在克隆int型二维数组时错误的和正确的做法: tHJahK:"k
>Qf`xUZ
YQ-V^e6
ocj^mxh=O
public void wrongStore (int[][] data) throws CloneNotSupportedException{ tY`%vI [
this.data = (int[][])data.clone(); // Not OK! :<6gP(
} _nIt4l7
public void rightStore (int[][] data){ kc[<5^b5
// OK! q$B|a5a?
this.data = (int[][])data.clone(); E**Hu 9
for (int i = 0; i < data.length; ++i){ Uot LJa
if (data != null) 69Q#UJ
this.data = (int[])data.clone(); W>$mU&ew[
} +qa^K%K
} !$0ozDmD
e$-Y>Dd
/,9n1|FrG
AR)A <