代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 f>l}y->-Ug
:2K0/@<x
v:s~Y
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 [ V/*{Z
tb{l(up/a
ks
3<zW(
mi<V(M~p
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 b^6Ooc/-k
}|AUV
Hco[p+
M(I 2M
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 3 LoB-4u?
W}a&L
cFD(Ap
z9'ME
一、常见错误1# :多次拷贝字符串 |;Jcf3e(
<E!M<!h
?
vk;b!
3QU<vdtr
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 O62H4oT
l9#M`x9
?5jkb
XQHvs{Po
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: A;q}SO%b
|brl<*:
` *h-j/M
rjx6Ad/\
String s = new String ("Text here"); 1i#M(u_
/<
h~d
|HhUU1!
(A/V(.!
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: ;la(Q~#
G W|~sE +
?_}[@x
MXSPD#gN
String temp = "Text here"; bC)diC
String s = new String (temp); "*XR'9~7
L%U-MOS=
"4oY F:h
Ej8EQ%P
但是这段代码包含额外的String,并非完全必要。更好的代码为: /wH]OD{
iK= {pd
3dQV5E.
I[@}+p0
String s = "Text here"; k0!b@
c
Mm+_>
50Pz+:
QV4{=1A
二、常见错误2#: 没有克隆(clone)返回的对象 Et4gRS)\
>Vn;1 |w
shjS^CP
gGH<%nHW1
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: FF)F%o+:w
aj|I[65
/mo4Q?^
(9{)4[3MAG
import java.awt.Dimension; AQQeLdTq
/***Example class.The x and y values should never*be negative.*/ s(r(! FZ
public class Example{ ]fnc.^{
private Dimension d = new Dimension (0, 0); L6J=m#Ld
public Example (){ } s+h`,gg9
%Z}A+Rv+*m
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ XGbtmmQG
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ _U|s!60'
if (height < 0 || width < 0) M(0:>G
throw new IllegalArgumentException(); pg [F{T<
d.height = height; xQ-]Iw5
d.width = width; %q`_vtUT
} NoV)}fX$X8
DnMfHG[<
public synchronized Dimension getValues(){ TmvI+AY/
// Ooops! Breaks encapsulation
sas;<yh
return d; -
b:&ACY
} B9&"/tT
} "z1\I\
^
GxuFO5wz
jyb/aov
Pp*|EW 1
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: WIa4!\Ky!
\|L ~#{a
!X
e
pGc_Klq
Example ex = new Example(); OjCTTz
Dimension d = ex.getValues(); >RG
}u
d.height = -5; ?;ZTJ
d.width = -10; z
v*hA/
2$V]XSe
^dJ/>?1
yCwBZ/C
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 Nv{r`J.
UpF,e>s
oe=^CeW"
4. 7m*
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 _{_ybXG|
1(CpTaa
WV]Si2pOZ
%oJ_,m_(
更好的方式是让getValues()返回拷贝: se:]F/
l&R~I6^E
5Q;Fwtm
3P2H!r
public synchronized Dimension getValues(){ $Y5R^Y
return new Dimension (d.x, d.y); Fo|6 PoSo
} jeFX?]Q
^i&sQQ({
a^hDxeG
ODyK/Q3
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 k1e0kxn
N,0l5fD~T
kAsYh4[
P:eY>~m<;
三、常见错误3#:不必要的克隆 q"7rd?r52
66NJ&ac
U p=J&^.
5
?~
?8Hi
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: d9^ uEz(
-aK_
5(W`{{AW
^oDC F
/*** Example class.The value should never * be negative.*/
yr9%,wwN
public class Example{ d~M;@<eD
private Integer i = new Integer (0); M0YV Qa
public Example (){ } _WO*N9Iz
F'^6ra9
/*** Set x. x must be nonnegative* or an exception will be thrown*/ hK5BOq!y
public synchronized void setValues (int x) throws IllegalArgumentException{ tgCEz%
if (x < 0) se(ZiyHp
throw new IllegalArgumentException(); D[yOFJ~p)
i = new Integer (x); j
qfxQ
} `CP#S7W^
9%55R >s$
public synchronized Integer getValue(){ KAVe~j"
// We can’t clone Integers so we makea copy this way. `irz'/"p
return new Integer (i.intValue()); }F=scbpXj
} 8 h
} M S$^m2
FW~%xUSE5
$9k7A 8K
f_2tMiy5
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 P(D0ru
IhoV80b
i P gewjx
29p`G1n
方法getValue()应该被写为: \X1?,gV_
Q}zAC2@L
/UtCJMQ
cBs:7Pnp%
public synchronized Integer getValue(){ COvcR.*0F
// ’i’ is immutable, so it is safe to return it instead of a copy. ' P5ttI#|
return i; zg L0v5vk
} WsO'4~X9
E:'TZ4Z
nQm7At
jYE<d&Cq
Java程序比C++程序包含更多的不可变对象。JDK 所提供的若干不可变类包括: {/d<Jm:
pm`BMy<5PU
*Y'nDv6_P
^'9:n\SKQ
?Boolean TN!8J=sx.
?Byte r$7fw}'I
?Character ,tqMMBwC~_
?Class 3Run.Gv\
?Double V/xGk9L~
?Float eFJ .)Z
?Integer *q**,_?;
?Long |e49F
?Short u By[x 0
?String \[u7y. b
?大部分的Exception的子类 =M39I&N
t6m&+N
{6}H}_(]
\o}m]v
i
四、常见错误4# :自编代码来拷贝数组 A9qbE
5A^$!q P
3jH-!M5
3,;;C(
Java允许你克隆数组,但是开发者通常会错误地编写如下的代码,问题在于如下的循环用三行做的事情,如果采用Object的clone方法用一行就可以完成: CRXIVver
BOqu$f+
7tbM~+<0
"%^T~Z(_j
public class Example{ jFAnhbbCE
private int[] copy; Lc L|'S)
/*** Save a copy of ’data’. ’data’ cannot be null.*/ "`WcE/(
public void saveCopy (int[] data){ A6-K~z^
copy = new int[data.length]; M18<d1*
for (int i = 0; i < copy.length; ++i) L>:YGM"sL
copy = data; D3,9X#B=
} fH{ _X
}
5ZpU><