代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 SG@-b(
H4{CiZ
G>f2E49BXt
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 XjINRC8^4
4/:}K>S_
vWpoaz/w
e$=UA%
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 H)VzPe# {
NuQ
l
uS}qy-8J
@})]4H
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 ;2\+O"}4H
]R?{9H|jwE
glo Y@k~
bjCO@t
一、常见错误1# :多次拷贝字符串 :+*q,lX8
TVs#,
3I):W9$Qp
eF=cMC
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。
XMpa87\
& cV$`L
'"Z\8;5i
t'{IE!_
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: "`q:
g+1&l iV
"J(0J
p;0p!~F=49
String s = new String ("Text here"); .0]\a~x
6zR9(c:a~
(RBzpAiH
7uq/C#N
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: 8urX]#
[QZ g=."
2/F";tc\'
i&_&4
String temp = "Text here"; lNRGlTD%
String s = new String (temp); SR8)4:aKW
l\t\DX"s_
-'%>Fon
YDxEWK<
但是这段代码包含额外的String,并非完全必要。更好的代码为: 1r?hRJ:'
0+dc
J<;@RK,c_
wY'w'%A?
String s = "Text here"; ?_V&~?r
1XXuFa&
eg Xbe)ld
[Zxv&$SQ
二、常见错误2#: 没有克隆(clone)返回的对象 'L$}!H1y
1O,:fTG<
oqUF_kh
;U)xZ _Ew~
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: w 8BSY
W{W8\
='G-wX&k
3LW_qX
import java.awt.Dimension; pB5#Ho>S
/***Example class.The x and y values should never*be negative.*/ ATzFs]~K;
public class Example{ dn1Fwy.
private Dimension d = new Dimension (0, 0); ?%A9}"q]
public Example (){ } :tf'Gw6v
6m$lK%P{1
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ hH(w O\s
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ U]A JWC6
if (height < 0 || width < 0) .$"13"
throw new IllegalArgumentException(); #T3dfVWv
d.height = height; cKEDRX3
d.width = width; h"3Mj*s
} N(Sc!rX
+oev NM
public synchronized Dimension getValues(){ slTE.
// Ooops! Breaks encapsulation XT%\Ce!
return d; r\T'_wo
} pt$\pQ
} riv8qg
sOqT*gwr:
hZ`<ID
{|{;:_.>
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: 9_-6Lwj6t
8yDe{
Rl{e<>O\^
~J:]cy)Q
Example ex = new Example(); cw"Ou%
Dimension d = ex.getValues(); s3sPj2e{
d.height = -5; 9T#${NK
d.width = -10; %EH{p@nM&-
lW|`8ykp
W+Q^u7K
SxI-pH'
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 Q].p/-[(
(Cb;=:3G
of=N+
W
Mj6
0?k
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 MAQ(PIc>T
lc[)O3,,B
(L<qJd1Q
G
_-JR
更好的方式是让getValues()返回拷贝: /*2)|2w
IqAML|C
|i\%>Y,
+l hJ8&
public synchronized Dimension getValues(){ lG5KZ[/Or
return new Dimension (d.x, d.y); `Kbf]"4q
} 8+@j %l j
hQ ?zc_3
6,cJ3~!48
cDIZkni=
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 %#x
l+^
bRD-[)
)uu(I5St
+L|x^B3
三、常见错误3#:不必要的克隆 Nsn~mY%
cq0-Dd9^&
r yNe=9p
%<0'xJ%%Q
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: [\3W_jR
|Kb
m74Z%
FBxg^g%PB@
t0_4jVt
/*** Example class.The value should never * be negative.*/ $p|Im,
public class Example{ Z 4QL&?U
private Integer i = new Integer (0); R-YNg
public Example (){ } A <_{7F9
k8c(|/7d
/*** Set x. x must be nonnegative* or an exception will be thrown*/ jwpahy;\WL
public synchronized void setValues (int x) throws IllegalArgumentException{ H<") )EJI
if (x < 0) kvG.?^ v
throw new IllegalArgumentException(); {l"(EeW6)
i = new Integer (x); uaE,F^p
} zY9CoadZ
zygH-3C7o
public synchronized Integer getValue(){ f?$yxMw:@
// We can’t clone Integers so we makea copy this way. 6WX?Xc]$3
return new Integer (i.intValue()); &=]!8z=
} :nOI|\rC
} "5204I
-tIye{
]nNn"_qh
21O@yNpS$
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 V :/v
r
,rV;T";r
}9kn;rb$g
vmg[/#
方法getValue()应该被写为: nC(Lr,(
2@W`OW Njm
2wu\.{6Zp
t$
97[ay
public synchronized Integer getValue(){ *q"1I9zvT
// ’i’ is immutable, so it is safe to return it instead of a copy. G.r .Z0
return i; gO{$p q}
} Dn)B19b
B@v
(ZY
#jJ0Mxg
ZUD{V
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: Oy b0t|do+
=ld!=II
$_3)m
*{,}pK2*
?Boolean X.sOZb?$
?Byte 7 0PGbAD
?Character m>|7&l_
?Class k[)/,1
?Double d3\KUR^
?Float BiDyr
?Integer |ZC'a!
?Long O`$\Plt|v
?Short +koW3>
?String Lr9E02
?大部分的Exception的子类 k<