快精灵印艺坊 您身边的文印专家
广州名片 深圳名片 会员卡 贵宾卡 印刷 设计教程
产品展示 在线订购 会员中心 产品模板 设计指南 在线编辑
 首页 名片设计   CorelDRAW   Illustrator   AuotoCAD   Painter   其他软件   Photoshop   Fireworks   Flash  

 » 彩色名片
 » PVC卡
 » 彩色磁性卡
 » 彩页/画册
 » 个性印务
 » 彩色不干胶
 » 明信片
   » 明信片
   » 彩色书签
   » 门挂
 » 其他产品与服务
   » 创业锦囊
   » 办公用品
     » 信封、信纸
     » 便签纸、斜面纸砖
     » 无碳复印纸
   » 海报
   » 大篇幅印刷
     » KT板
     » 海报
     » 横幅

诊断和纠正 Java 程序中反复出现的错误类型

欢迎光临诊断 Java 代码,一个隔周更新的新专栏,着重讨论和您日常编程工作有关的 Java 解决方案。本文为第一篇,介绍了错误模式的概念,一个异常有用的概念,它将提高您检测和修正代码中错误的能力。您会了解到一种最普遍的错误模式,这将为您开始识别和避免更高级的错误模式奠定基础。
错误模式和它们为什么有用
正如好的编程技能涉及很多设计模式(您可以在不同的程序上下文中组合和应用这些模式)的知识相同,好的调试技能也涉及对 错误模式的一定了解。错误模式就是已发出的错误和程序中潜在的错误之间的重复出现的相互关系。这种概念对编程来说并不新鲜。医生们在诊断疾病时依赖相似类型的相互关系。他们在实习期间通过和资格较老的医生共同工作来学习这些。他们的教育就是集中在做这种诊断上的。相反,我们软件工程师的教育是集中在过程设计和算法分析上的。这些技能固然重要,但是人们对调试过程的教育却很少关注。相反,我们得自己去“拾起”这种技能。随着极端编程的出现和它对单元测试的注意,这种做法已经开始改变了。但是频繁的单元测试只是解决了问题的一部分。一旦发现错误,就必须诊断和纠正它们。幸运的是,很多错误都遵循我们可以识别的几种错误模式的其中一种。一旦您可以识别出这些错误模式,您就可以诊断出错误的原因并且更快地纠正它了。

错误模式与反模式有关,反模式是一次又一次被证实是失败的公共软件设计的模式。虽然反模式是设计模式,错误模式却是与编程错误相关的错误的程序行为的模式。这与设计根本没有关系,而是与编程和调试过程有关。

通过示例学习
为了说明错误模式后面的思想,让我们来考虑一种基本错误模式,编程新手(常常还有更高级的程序员)经常会碰到这种错误模式。在后面的文章中,我们会谈到更高级的错误模式。对每一种模式,我会讨论将有助于把该模式的错误的发生控制到最少的编程原则(并非暗示所有的错误都是不遵循编程原则的结果;不管我们遵循多少原则,我们都会犯错误)。

为了分类起见,我会使用下面的形式(从医学上借用一些术语)来概括错误模式描述:

模式名称
症状
起因
治疗方式和防备措施
Rogue Tile 模式
也许它是编程新手中最普遍的错误模式,起因是复制和粘贴一段代码到程序的其它部分。有时,复制的一小部分因为功能上需求的略微不同而作了改动。不可避免地,错误在一个副本中被修正了,而在另一个副本中没有被修正,这样在错误症状复发时就会让您很头疼。尽管大多数程序员很快就认识了这种错误模式,但他们中很少人采取适当的措施来将这种错误的出现控制到最少。您很轻易就会偷懒不去思索而简朴地复制您认为已经可以运行的代码。但是工作效率由于修正代码而丧失,这是因为不加选择的复制―粘贴操作很快降低了复制代码带来的任何工作效率。

我称此为 Rogue Tile 模式是因为,一段代码的各个副本可以被看成是分布在程序中的“tile”。由于不同副本中的代码出现了差异,副本就变成了“rogue tile”。

症状
这种错误的模式的最普遍症状是,在您认为已经修正了问题以后,程序还继承表现出错误的行为。

起因
为了理解这种情况发生的原因,我们来看看下面的二元树类层次结构:

public abstract class Tree {

}

public class Leaf extends Tree {

public Object value;
...
}

public class Branch extends Tree {

public Object value;
public Tree left;
public Tree right;
...
}






对于这些类要注重的第一件事就是,两种详细类都包含 Object 类型的 value 字段。假如您决定稍后让树包含,比如说,Interger,您也许会忘记更新其中的一个字段声明。假如程序的其它部分需要这些字段是 Interger 的话,程序就很可能不会编译。您或许记得您改变了其中一个类的 value 字段的类型,却忽略了一个事实,就是您没有在其它类中作相应的改变。

一些防备措施
当然,这个示例所示的错误是编程新手可以很快学会通过分解出公共代码来避免的。在本例中,字段声明应该移到 Tree 类中。它的两个子类就会继续这个字段,而且对字段声明的任何改变都只需要在一个地方出现。

继承看这个示例,我们可能还会编写在一个 Tree 中相加和相乘所有节点的方式。为了简朴起见,我将以递归的方法来编写这些方式。

// in class Tree:

public abstract int add();
public abstract int multiply();

// in class Branch:

public int add() {
return this.value.intValue() + left.add() + right.add();
}

public int multiply() {
return this.value.intValue() * left.multiply() + right.multiply();
}

// in class Leaf:

public int add() { return this.value.intValue(); }
public int multiply() { return this.value.intValue(); }






请注重我在 multiply 方式中为 Branch 类引入的错误:我没有用第三项去乘,而是加了它。错误发生了,因为我通过复制 add 方式中的代码并作稍微(但不完全)的改动创建了 multiply 方式。这种错误异常隐蔽,因为调用 multiply 方式永远不会发出错误信号。事实上,在很多情况下,它会返回一个看上去完全合理的结果。

就象以前相同,我们可以通过分解出公共代码来将这种错误控制到最少。在这种情况下,我们可以编写一个单独的方式,它在 Tree 上累计一个运算符(作为一个参数传送)。我们可以使用一种被称为公共模式的设计模式(不是错误模式!)在对象中封装这个运算符。

public abstract class Operator {
public abstract int apply(int l, int r);
}

public class Adder extends Operator {
public int apply(int l, int r) {
return l + r;
}
}

public class Multiplier extends Operator {
public int apply(int l, int r) {
return l * r;
}
}






然后我们就可以如下面的代码所示在我们的 Tree 类层次结构中改变这个方式:

// in class Tree:

public abstract int accumulate(Operator o);

public int add() {
return this.accumulate(new Adder());
}

public int multiply() {
return this.accumulate(new Multiplier());
}

// in class Leaf:

public int accumulate(Operator o) {
return value.intValue();
}


in class Branch:

public int accumulate(Operator o) {
return o.apply(this.value.intValue(),
o.apply(left.accumulate(o),
right.accumulate(o)));
}






通过分解出公共代码,我们消除了在 add 和 multiply 方式正文中出现复制―粘贴错误的可能性。另外,请注重我们不再需要为 Tree 的每一个子类编写单独的 add 和 multiply 方式了。

分解出公共代码是一个很好的习惯,但它并不适用于所有的情况。比如说,Java 类型系统的简朴性常常迫使我们在精确类型检验和保持对程序的每个不同的功能性元素的单点控制(请参阅参考资料,阅读我写的关于 NextGen 的文章)之间作出选择。正因为这个,Rogue Tile 模式是所有开发人员必须一直努力以控制到最少的一种错误类型。

下一篇内容是什么?
简而言之,这是我们的第一个错误模式。您可能想把它剪下来钉在您的公告牌上作为提醒。

模式:Rogue Tile
症状:代码似乎表现出前面纠正过的错误依然存在。
起因:复制―粘贴代码片段的至少一个副本还包含在其它副本中已经修正了的错误。
治疗和防备措施:假如可能,分解出公共代码;否则就对其进行更新。避免复制和粘贴代码。
在我的下一篇文章中,我会探究 Java 代码中出现过的其它一些普遍的错误模式。我们将特殊看一下作为空指针非常而出现的错误模式,并讨论如何将它们的出现次数控制到最少。

参考资料

访问模式主页,这是一个介绍设计模式以及如何使用它们的优秀主页。
反模式主页提供了关于该主题的书籍的信息和链接。
假如您还没有这么做,请查阅极端编程,一个迅速开发简洁的、健壮的软件的强盛的新方式。
然后下载 JUnit 并马上开始单元测试。
Eric Allen 的关于 NextGen(Java 的一个运行时泛型类型的扩展)的文章说明了一个更加强盛的类型系统是怎样帮助减轻存在于分解公共代码和使用类型系统在编译期间捕获错误的两个目标之间的一些压力的。


关于作者
Eric Allen 在 Cornell 大学获得计算机科学和数学的学士学位。他目前是 Cycorp 公司的 Java 软件开发人员带头人,还是 Rice 大学的编程语言小组的半工半读硕士生。他的研究涉及正规语义模型和 Java 语言的扩展,都是在源代码和字节码的级别上的。目前,他正在为 NextGen 编程语言实现一种从源代码到字节码的编译器,这也是 Java 语言的泛型运行时类型的一种扩展。





返回类别: 教程
上一教程: Java I/O API之性能分析 (上)
下一教程: 深探java.util.logging包

您可以阅读与"诊断和纠正 Java 程序中反复出现的错误类型"相关的教程:
· JAVA程序开发小经验 - 使用ObjectStream会出现的问题
· 回答:为什么执行JAVA程序时,会出现Exception in thread"main" java.lang.NoClassDefFoundError的错?...
· 在Java程序中截获控制台输出
· 在JAVA应用程序中显示数据库的BLOB图像
· Java 程序中的多线程
    微笑服务 优质保证 索取样品