代码审查是消灭Bug最重要的方法之一,这些审查在大多数时候都特别奏效。由于代码审查本身所针对的对象,就是俯瞰整个代码在测试过程中的问题和Bug。并且,代码审查对消除一些特别细节的错误大有裨益,尤其是那些能够容易在阅读代码的时候发现的错误,这些错误往往不容易通过机器上的测试识别出来。本文就常见的Java代码中容易出现的问题提出一些建设性建议,以便您在审查代码的过程中注意到这些常见的细节性错误。 o4y']JSN
@OpNHQat9
zg)sd1@
通常给别人的工作挑错要比找自己的错容易些。别样视角的存在也解释了为什么作者需要编辑,而运动员需要教练的原因。不仅不应当拒绝别人的批评,我们应该欢迎别人来发现并指出我们的编程工作中的不足之处,我们会受益匪浅的。 jNDx,7F-
V@-)\RZm
%/%UX{8R
%kshQ%P)?
正规的代码审查(code inspection)是提高代码质量的最强大的技术之一,代码审查?由同事们寻找代码中的错误?所发现的错误与在测试中所发现的错误不同,因此两者的关系是互补的,而非竞争的。 xg@NQI@7
#KlCZ~s
"2ru 7Y"
c3%@Wj:fo
如果审查者能够有意识地寻找特定的错误,而不是靠漫无目的的浏览代码来发现错误,那么代码审查的效果会事半功倍。在这篇文章中,我列出了11个Java编程中常见的错误。你可以把这些错误添加到你的代码审查的检查列表(checklist)中,这样在经过代码审查后,你可以确信你的代码中不再存在这类错误了。 E0n6$5Uc?
!~i'
-4]
0#o/ ^Ah
kS5_
一、常见错误1# :多次拷贝字符串 q!~ -(&S
s"KJiQKGM
q'[}9e`Q
rZXrT}Xh{W
测试所不能发现的一个错误是生成不可变(immutable)对象的多份拷贝。不可变对象是不可改变的,因此不需要拷贝它。最常用的不可变对象是String。 WiL2
y]
oaO+
KL,/2(
68fiG
如果你必须改变一个String对象的内容,你应该使用StringBuffer。下面的代码会正常工作: l\0w;:N3
t&q N: J
/UcV
SMr13%KN/
String s = new String ("Text here"); . 5y"38e
=<@2#E)
iRo.RU8>
Z6C=T;w
但是,这段代码性能差,而且没有必要这么复杂。你还可以用以下的方式来重写上面的代码: Cs3^9m6;d
?
8aaD>OR$
= {'pUU
WUc#)EEM)
String temp = "Text here"; PC$CYW5
String s = new String (temp); f>o,N{|
( lm&*tKm
2zSG&",2D
%q;jVj[
但是这段代码包含额外的String,并非完全必要。更好的代码为: %.v{N6
&.13dq
b3-eR5U/
s.Y4pWd5@
String s = "Text here"; TcTM]ixr
o{b=9-V
!rDdd%Z
UV
4>N
二、常见错误2#: 没有克隆(clone)返回的对象 $0oO
&)*
_mvxsG
`nXVE+E@
54;J8XT7
封装(encapsulation)是面向对象编程的重要概念。不幸的是,Java为不小心打破封装提供了方便??Java允许返回私有数据的引用(reference)。下面的代码揭示了这一点: c\6+=\
j:T/ iH!YF
%=AxJp!a
qW:)!z3\
import java.awt.Dimension; =o}"jVE
/***Example class.The x and y values should never*be negative.*/ sbkQ71T:
public class Example{ z{"2S="
private Dimension d = new Dimension (0, 0); W%2
80\h
public Example (){ } 3nZ9m
@RFs/'
/*** Set height and width. Both height and width must be nonnegative * or an exception is thrown.*/ mME4 l
public synchronized void setValues (int height,int width) throws IllegalArgumentException{ Xv <G-N4
if (height < 0 || width < 0) v8gdU7Ll,
throw new IllegalArgumentException(); UtB6V)YI
d.height = height; #*$P'r
d.width = width; X{n- N5*
} )D'^3)FF
o@]So(9f
public synchronized Dimension getValues(){ +;g{$da5
// Ooops! Breaks encapsulation |6UtW{2I/
return d; g*)K/Z0pJ$
} fJ\sguZ
} 9odJr]
KTvzOI8
N"/-0(9[
CycUeT
Example类保证了它所存储的height和width值永远非负数,试图使用setValues()方法来设置负值会触发异常。不幸的是,由于getValues()返回d的引用,而不是d的拷贝,你可以编写如下的破坏性代码: `b8v1Os^2
&1l=X]%
Y={&5Mir
|py6pek|
Example ex = new Example(); ri`R<l8
Dimension d = ex.getValues(); 9!9Z~/*m
d.height = -5; IM$2VlC
d.width = -10; 4k/VBZB
yKXff1^M
yc2/~a_Gx
9jN)I(^D6
现在,Example对象拥有负值了!如果getValues() 的调用者永远也不设置返回的Dimension对象的width 和height值,那么仅凭测试是不可能检测到这类的错误。 +$xeoxU>;
-yGDh+-
%N;!+
;F_g
V._6=ZJ
不幸的是,随着时间的推移,客户代码可能会改变返回的Dimension对象的值,这个时候,追寻错误的根源是件枯燥且费时的事情,尤其是在多线程环境中。 !3mA0-!+
+,o0-L1D
-/_L*oYli
dC=)^(
更好的方式是让getValues()返回拷贝: sS&Z ,A
xD&^j$Em
ve
~05mg
nf1#tlIJd
public synchronized Dimension getValues(){ "'g[1Li
return new Dimension (d.x, d.y); 2<&Bw2
} 2,lqsd:xM
+\li*G]:J
c PgfTT
Q^p|Ldj
现在,Example对象的内部状态就安全了。调用者可以根据需要改变它所得到的拷贝的状态,但是要修改Example对象的内部状态,必须通过setValues()才可以。 -(`OcGM'L
g}laG8
o7B[R) 4
79\JxiSB
三、常见错误3#:不必要的克隆 yNm:[bOER
'Dvv?>=&
5TBp'7 /s~
Ca%g_B0t
我们现在知道了get方法应该返回内部数据对象的拷贝,而不是引用。但是,事情没有绝对: h'
!imQ
LlBN-9p
|
ohL]7b<
9}B`uJ
/*** Example class.The value should never * be negative.*/ iK?b~Q
public class Example{ X1ZgSs+i
private Integer i = new Integer (0); A2}Rl%+X]6
public Example (){ } 7_2kDDW0
sC[yI Up
/*** Set x. x must be nonnegative* or an exception will be thrown*/ LZ@|9!KDw
public synchronized void setValues (int x) throws IllegalArgumentException{ 8/z3=O&
if (x < 0) Zo KcJA
throw new IllegalArgumentException(); $3Z-)m
i = new Integer (x); SI:U0gUc
} .^$YfTabq
0Z|FZGRP
public synchronized Integer getValue(){ #,{+3Y&5-+
// We can’t clone Integers so we makea copy this way. /ywD{*
return new Integer (i.intValue()); 9WJz~SP+vR
} .1 %T
W)
} RE.r4uOJg
B2Xn?i3 l
\7"@RHcihB
{cpEaOyOM
这段代码是安全的,但是就象在错误1#那样,又作了多余的工作。Integer对象,就象String对象那样,一旦被创建就是不可变的。因此,返回内部Integer对象,而不是它的拷贝,也是安全的。 CF|]e:
nA?Hxos
UD~p'^.m_
6Es?
MW=
方法getValue()应该被写为: 3]-_q"Co4f
Q-#$Aa
>;z<j$;F<
S osj$9E
public synchronized Integer getValue(){ Og;-B0,A
// ’i’ is immutable, so it is safe to return it instead of a copy. |^28\sm2e
return i; Pmi#TW3X
} v#=`%]mL
&D%(~|'
6z,&