时间: 2021-07-31 作者:daque
代码查看是祛除bug最要害的本领之一,那些查看在大普遍功夫都更加生效。因为代码查看自己所对准的东西,即是俯视所有代码在尝试进程中的题目和bug。而且,代码查看对取消少许更加详细的缺点大有裨益,更加是那些不妨简单在观赏代码的功夫创造的缺点,那些缺点常常不简单经过呆板上的尝试辨别出来。正文就罕见的java代码中简单展示的题目提出少许树立性倡导,再不您在查看代码的进程中提防到那些罕见的详细性缺点。
常常给旁人的处事挑错要比找本人的错简单些。别样视角的生存也证明了干什么作家须要编纂,而疏通员须要教授的因为。不只不该当中断旁人的品评,咱们该当欢送旁人来创造并指出咱们的编制程序处事中的不及之处,咱们会收获颇丰的。
正轨的代码查看(code inspection)是普及代码品质的最宏大的本领之一,代码查看—由共事们探求代码中的缺点—所创造的缺点与在尝试中所创造的缺点各别,所以两者的联系是互补的,而非比赛的。
即使查看者不妨有认识地探求一定的缺点,而不是靠漫无手段的欣赏代码来创造缺点,那么代码查看的功效会一举两得。在这篇作品中,我列出了11个java编制程序中罕见的缺点。你不妨把那些缺点增添到你的代码查看的查看列表(checklist)中,如许在过程代码查看后,你不妨坚信你的代码中不复生存这类缺点了。
罕见缺点1# :屡次正片字符串
尝试所不许创造的一个缺点是天生不行变(immutable)东西的多份正片。不行变东西是不行变换的,所以不须要正片它。最常用的不行变东西是string。
即使你必需变换一个string东西的实质,你该当运用stringbuffer。底下的代码会平常处事:
string s = new string ("text here");
然而,这段代码本能差,并且没有需要这么搀杂。你还不妨用以次的办法来重写上头的代码:
string temp = "text here";
string s = new string (temp);
然而这段代码包括特殊的string,并非实足需要。更好的代码为:
string s = "text here";
罕见缺点2#: 没有克隆(clone)归来的东西
封装(encapsulation)是面向东西编制程序的要害观念。悲惨的是,java为不提防冲破封装供给了简单——java承诺归来独占数据的援用(reference)。底下的代码揭穿了这一点:
import java.awt.dimension;
/***example class.the x and y values should never*be negative.*/
public class example{
private dimension d = new dimension (0, 0);
public example (){ }
/*** set height and width. both height and width must be
nonnegative * or an exception is thrown.*/
public synchronized void setvalues (int height,int width)
throws illegalargumentexception{
if (height < 0 || width < 0)
throw new illegalargumentexception();
d.height = height;
d.width = width;
}
public synchronized dimension getvalues(){
// ooops! breaks encapsulation
return d;
}
}
example类保护了它所保存的height和width值长久非负数,试图运用setvalues()本领来树立负值会触发特殊。悲惨的是,因为getvalues()归来d的援用,而不是d的正片,你不妨编写如次的妨害性代码:
example ex = new example();
dimension d = ex.getvalues();
d.height = -5;
d.width = -10;
此刻,example东西具有负值了!即使getvalues() 的挪用者长久也不树立归来的dimension东西的width 和height值,那么仅凭尝试是不大概检验和测定到这类的缺点。
悲惨的是,跟着功夫的推移,存户代码大概会变换归来的dimension东西的值,这个功夫,追寻缺点的基础是件呆板且费时的工作,更加是在多线程情况中。
更好的办法是让getvalues()归来正片:
public synchronized dimension getvalues(){
return new dimension (d.x, d.y);
}
此刻,example东西的里面状况就安定了。挪用者不妨按照须要变换它所获得的正片的状况,然而要窜改example东西的里面状况,必需经过setvalues()才不妨。
罕见缺点3#:不需要的克隆
咱们此刻领会了get本领该当归来里面数据东西的正片,而不是援用。然而,工作没有一致:
/*** example class.the value should never * be negative.*/
public class example{
private integer i = new integer (0);
public example (){ }
/*** set x. x must be nonnegative* or an exception will be thrown*/
public synchronized void setvalues (int x)
throws illegalargumentexception{
if (x < 0)
throw new illegalargumentexception();
i = new integer (x);
}
public synchronized integer getvalue(){
// we can’t clone integers so we makea copy this way.
return new integer (i.intvalue());
}
}
这段代码是安定的,然而就象在缺点1#那么,又作了过剩的处事。integer东西,就象string东西那么,一旦被创造即是不行变的。所以,归来里面integer东西,而不是它的正片,也是安定的。
本领getvalue()该当被写为:
public synchronized integer getvalue(){
// ’i’ is immutable, so it is safe to return it instead of a copy.
return i;
}
java步调比c++步调包括更多的不行变东西。jdk 所供给的几何不行变类囊括:
·boolean
·byte
·character
·class
·double
·float
·integer
·long
·short
·string
·大局部的exception的子类
罕见缺点4# :自编代码来正片数组
java承诺你克隆数组,然而开拓者常常会缺点地编写如次的代码,题目在乎如次的轮回用三行做的工作,即使沿用object的clone本领用一条龙就不妨实行:
public class example{
private int[] copy;
/*** save a copy of ’data’. ’data’ cannot be null.*/
public void savecopy (int[] data){
copy = new int[data.length];
for (int i = 0; i < copy.length; ++i)
copy[i] = data[i];
}
}
这段代码是精确的,但却不用本地搀杂。savecopy()的一个更好的实行是:
void savecopy (int[] data){
try{
copy = (int[])data.clone();
}catch (clonenotsupportedexception e){
// can’t get here.
}
}
即使你常常克隆数组,编写如次的一个东西本领会是个好办法:
static int[] clonearray (int[] data){
try{
return(int[])data.clone();
}catch(clonenotsupportedexception e){
// can’t get here.
}
}
如许的话,咱们的savecopy看上去就更简略了:
void savecopy (int[] data){
copy = clonearray ( data);
}
罕见缺点5#:正片缺点的数据
有功夫步调员领会必需归来一个正片,然而却不提防正片了缺点的数据。因为只是做了局部的数据正片处事,底下的代码与步调员的企图有缺点:
import java.awt.dimension;
/*** example class. the height and width values should never * be
negative. */
public class example{
static final public int total_values = 10;
private dimension[] d = new dimension[total_values];
public example (){ }
/*** set height and width. both height and width must be
nonnegative * or an exception will be thrown. */
public synchronized void setvalues (int index, int height, int width)
throws illegalargumentexception{
if (height < 0 || width < 0)
throw new illegalargumentexception();
if (d[index] == null)
d[index] = new dimension();
d[index].height = height;
d[index].width = width;
}
public synchronized dimension[] getvalues()
throws clonenotsupportedexception{
return (dimension[])d.clone();
}
}
这边的题目在乎getvalues()本领只是克隆了数组,而没有克隆数组中包括的dimension东西,所以,固然挪用者没辙变换里面的数组使其元素指向各别的dimension东西,然而挪用者却不妨变换里面的数组元素(也即是dimension东西)的实质。本领getvalues()的更好本子为:
public synchronized dimension[] getvalues()
throws clonenotsupportedexception{
dimension[] copy = (dimension[])d.clone();
for (int i = 0; i < copy.length; ++i){
// note: dimension isn’t cloneable.
if (d[i] != null)
copy[i] = new dimension (d[i].height, d[i].width);
}
return copy;
}
在克隆亚原子典型数据的多维数组的功夫,也会犯一致的缺点。亚原子典型囊括int,float等。大略的克隆int型的一维数组是精确的,如次所示:
public void store (int[] data)
throws clonenotsupportedexception{
this.data = (int[])data.clone();
// ok
}
正片int型的二维数组更搀杂些。java没有int型的二维数组,所以一个int型的二维数组本质上是一个如许的一维数组:它的典型为int[]。大略的克隆int[][]型的数组会犯与上头例子中getvalues()本领第一本子同样的缺点,所以该当制止这么做。底下的例子演练了在克隆int型二维数组时缺点的和精确的做法:
public void wrongstore (int[][] data)
throws clonenotsupportedexception{
this.data = (int[][])data.clone(); // not ok!
}
public void rightstore (int[][] data){
// ok!
this.data = (int[][])data.clone();
for (int i = 0; i < data.length; ++i){
if (data[i] != null)
this.data[i] = (int[])data[i].clone();
}
}
[page_break]罕见缺点6#:查看new 操纵的截止能否为null
java编制程序生人有功夫会查看new操纵的截止能否为null。大概的查看代码为:
integer i = new integer (400);
if (i == null)
throw new nullpointerexception();
查看固然没什么缺点,但却不需要,if和throw这两行代码实足是滥用,她们的独一功效是让所有步调更痴肥,运转更慢。
c/c++步调员在发端写java步调的功夫往往会这么做,这是因为查看c中malloc()的归来截止是需要的,不如许做就大概爆发缺点。查看c++中new操纵的截止大概是一个好的编制程序动作,这依附于特殊能否被使能(很多编写翻译器承诺特殊被遏止,在这种情景下new操纵波折就会归来null)。在java 中,new 操纵不承诺归来null,即使真的归来null,很大概是假造机解体了,这功夫即使查看归来截止也杯水车薪。
罕见缺点7#:用== 代替.equals
在java中,有两种办法查看两个数据能否十分:经过运用操纵符,大概运用一切东西都实行的.equals本领。亚原子典型(int, flosat, char 等)不是东西,所以她们只能运用==操纵符,如次所示:
int x = 4;
int y = 5;
if (x == y)
system.out.println ("hi");
// this ’if’ test won’t compile.
if (x.equals (y))
system.out.println ("hi");
东西更搀杂些,==操纵符查看两个援用能否指向同一个东西,而equals方规则实行更特意的十分性查看。
更显得凌乱的是由java.lang.object 所供给的缺省的equals本领的实行运用==来大略的确定被比拟的两个东西能否为同一个。
很多类掩盖了缺省的equals本领再不更有效些,比方string类,它的equals本领查看两个string东西能否包括同样的字符串,而integer的equals本领查看所包括的int值能否十分。
大局部功夫,在查看两个东西能否十分的功夫你该当运用equals本领,而对于亚原子典型的数据,你用该运用==操纵符。
罕见缺点8#: 污染亚原子操纵和非亚原子操纵
java保护读和写32位数大概更小的值是亚原子操纵,也即是说不妨在一步实行,所以不大概被打断,所以如许的读和写不须要同步。以次的代码是线程安定(thread safe)的:
public class example{
private int value; // more code here...
public void set (int x){
// note: no synchronized keyword
this.value = x;
}
}
然而,这个保护仅限于读和写,底下的代码不是线程安定的:
public void increment (){
// this is effectively two or three instructions:
// 1) read current setting of ’value’.
// 2) increment that setting.
// 3) write the new setting back.
++this.value;
}
在尝试的功夫,你大概不会捕捉到这个缺点。开始,尝试与线程相关的缺点是很难的,并且很耗功夫。其次,在有些呆板上,那些代码大概会被翻译成一条训令,所以处事平常,惟有当在其它的假造机上尝试的功夫这个缺点才大概表露。所以最佳在发端的功夫就精确地同步代码:
public synchronized void increment (){
++this.value;
}
罕见缺点9#:在catch 块中作废除处事
一段在catch块中作废除处事的代码如次所示:
outputstream os = null;
try{
os = new outputstream ();
// do something with os here.
os.close();
}catch (exception e){
if (os != null)
os.close();
}
纵然这段代码在几个上面都是有题目的,然而在尝试中很简单漏掉这个缺点。底下列出了这段代码所生存的三个题目:
1.语句os.close()在两处展示,画蛇添足,并且会带来保护上面的烦恼。
2.上头的代码只是处置了exception,而没有波及到error。然而当try块运转展示了error,流也该当被封闭。
3.close()大概会抛出特殊。
上头代码的一个更优本子为:
outputstream os = null;
try{
os = new outputstream ();
// do something with os here.
}finally{
if (os != null)
os.close();
}
这个本子取消了上头所提到的两个题目:代码不复反复,error也不妨被精确处置了。然而没有好的本领来处置第三个题目,大概最佳的本领是把close()语句独立放在一个try/catch块中。
罕见缺点10#: 减少不需要的catch 块
少许开拓者听到try/catch块这个名字后,就会想固然的觉得一切的try块必需要有与之配合的catch块。
c++步调员更加是会如许想,由于在c++中不生存finally块的观念,并且try块生存的独一来由只然而是为了与catch块相称对。
减少不需要的catch块的代码就象底下的格式,捕捉到的特殊又登时被抛出:
try{
// nifty code here
}catch(exception e){
throw e;
}finally{
// cleanup code here
}
不需要的catch块被简略后,上头的代码就减少为:
try{
// nifty code here
}finally{
// cleanup code here
}
罕见缺点11#;没有正真实现equals,hashcode,大概clone 等本领
本领equals,hashcode,和clone 由java.lang.object供给的缺省实行是精确的。悲惨地是,那些缺省实行在大局部功夫毫无用途,所以很多类掩盖个中的几何个本领以供给更有效的功效。然而,题目又来了,当接受一个掩盖了几何个那些本领的父类的功夫,子类常常也须要掩盖那些本领。在举行代码查看时,该当保证即使父类实行了equals,hashcode,大概clone等本领,那么子类也必需精确。精确的实行equals,hashcode,和clone须要少许本领。
总结
我在代码查看的功夫起码遇到过一次那些缺点,我本人也犯过个中的几个缺点。好动静是只有你领会你在找什么缺点,那么代码查看就很简单处置,缺点也很简单被创造和窜改。即使你找不到功夫来举行正轨的代码查看,以自查的办法把那些缺点从你的代码中废除会大大俭朴你的调节和测试功夫。花功夫在代码查看上是犯得着的。