代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 _T=";NSa
^}:0\;|N
H)y_[:[
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 rxZk!- t)L
%:dd#';g
;2^zkmDM
0/cgOP!^
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 6vzvH
)ub!tm
mXsSOAD<
5bol)Z9BO
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 =w:H9uj6F
t*Z-]P
?wjk=hM2
0\eSiXs
一、常见错误1# :多次拷贝字符串 Cq-99@&;
Eok8+7g0&
z_8Bl2tl
=CL,+
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 psS^
$-E<{
"'>fTk_
r8A'8g4cM
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: FtWO[*#
O_5;?$[m
e0#{'_C
DnN+W
String s = new String ("Text here"); "k),;1
j}8^gz]
a&`^M
g7eI;Tpv
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: QEmktc1 7
E#kH>q@K`$
5F:\U
qzk]9`i1:
String temp = "Text here"; dO-Zj#%7z8
String s = new String (temp); dtXtZ!g2
s GrI%3[e"
%H}M[_f
2 m72PU<.
但是这段代码包含额外的String,并非完全必要。更好的代码为: dE(d'*+a
C ?\HB#41
9g$fFO
g](&H$g
String s = "Text here"; Af^9WJ
l8lJ &
*LvdrPxU=
UG6\OgkL+
二、常见错误2#: 没有克隆(clone)返回的对象 +ERuZc$3,
paxZlA
o
#EH\Q%
TI8EW
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: 0bGQO&s
[
C{6m?6
2J`LZS
2[KHmdgtB
import java.awt.Dimension; UZgrSX {
/***Example class.The x and y values should never*be negative.*/ V{rQ@7SE
public class Example{ q?f-h<yRQ
private Dimension d = new Dimension (0, 0); -BsZw.
7P
public Example (){ } `Cu9y+t
fY|vq
amA;
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ pFwe&_u]
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ YIYuqtnSJ
if (height < 0 || width < 0) >EgMtZ88.<
throw new IllegalArgumentException(); W7IAW7w8U
d.height = height; rE\&FVx
d.width = width; *`tQX$F
} U.|0y =
^9|&w.:@Q
public synchronized Dimension getValues(){ .GW)"`HbU
// Ooops! Breaks encapsulation eBe5H
=I@
return d; "fSK7%BP
} >lugHF$G
} X`I=Z ysB
|@)jS.Bn
{_4zm&
o7AI
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: `1R[J4e
+ZRm1q
o:Tpd 0F
McvLU+
Example ex = new Example(); iyMoLZ5
Dimension d = ex.getValues(); ;i 3C
d.height = -5; 1oG'm
d.width = -10; *(VwD)*
V_)465g
xf{=~j/L
,9.NMFn
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 "+BuFhSLf
PC)V".W1
PS??wlp7
M5]$w]Ny9
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 5eas^Rm
lq27^K
W1Om$S1
@h7
i;Ok
更好的方式是让getValues()返回拷贝: j,N,WtE
I4zm{ 1g
QFEc?sEe
l{_1`rC'
public synchronized Dimension getValues(){ &|Vzo@D(!
return new Dimension (d.x, d.y); }z2K"eGt
} ]tEH `Kl
o(xt%'L`t
vu/P"?F
LeMo")dk\
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 jL~. =QD
8;Df/%
bj 0-72V
W-vEh
三、常见错误3#:不必要的克隆 X""}]@B9z
~G~:R
0"`|f0}c
"=9)|{=m
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: @z(s\T
m pM,&7}
NW?h~2
Oxh.&
/*** Example class.The value should never * be negative.*/ 97VS
xhr
public class Example{ [JVUa2Sm
private Integer i = new Integer (0); T-lHlm
public Example (){ } >zv}59M
UC"_#!3
/*** Set x. x must be nonnegative* or an exception will be thrown*/ [b@9V_
public synchronized void setValues (int x) throws IllegalArgumentException{ F#7A6|
if (x < 0) IQ9Rvnna
throw new IllegalArgumentException(); ~ponYc.Y
i = new Integer (x); .BZ3>]F3<
} P vS\
1?T^jcny:M
public synchronized Integer getValue(){ 4iZ7BD
// We can’t clone Integers so we makea copy this way. |_wbxdq
return new Integer (i.intValue()); `"j _]
} :FI4GR*?
} XFvPc
5E\&O%W"
ixo?o]Xb`
@*~cmf&FIQ
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 8x<; AL|`
|'12Kv]#Xa
</7?puVR
VXu1Y xY
方法getValue()应该被写为: >J@hqW
`T$CUlt6
4031~A8
3 e<sNU?
public synchronized Integer getValue(){ Vu1X@@z
// ’i’ is immutable, so it is safe to return it instead of a copy. {@<EVw
return i; 9vz"rHV
} {@`Z`h"N
E3o J;E
+J%9%DqF
dK?vg@|'
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: [ncOtDE
Q
,)}t
Nn|~:9#
/s^O M`5
?Boolean 1$~W~O
?Byte Q::6|B,G
?Character }\)O1
?Class ]!04L}hy|P
?Double ?hwT{h
?Float '-m )fWf
?Integer 6/eh~ME=
?Long F;_L/8Ov1
?Short -!z,t7!
?String :g=z}7!s
?大部分的Exception的子类 Z3
$3zyi
-+=+W
7\1bq&a<
R} aHo0r
四、常见错误4# :自编代码来拷贝数组 <