诊断Java代码: Impostor Type错误模式

当使用字段中特殊的标记来区别对象类型时,可能会产生标记对相关数据误贴标签的错误 ― 通称为 Impostor Type 错误模式。在诊断 Java 代码的这一部分中,Eric Allen 对这个错误的症状和起因进行了分析,详细说明了预防错误发生的方法,并讨论了一种吸引人的混合实现方法,这种方法不使用 impostor type,但最后,还是有很多相同的缺点产生。请在 讨论论坛与作者及其他读者分享您对本文的看法。

程序中除了最无关紧要的部分外都要对某些数据类型进行操作。静态类型系统提供了一种方法,它能够确保程序不会对给定类型的数据进行不当的操作。Java 语言的优点之一是严格的区分类型,所以在程序运行前已消除了类型错误。作为开发人员,我们可以使用这个类型系统提供更健壮且没有错误的代码。然而,我们却常常没有让类型系统发挥出最大的潜力。

Impostor Type 错误模式

很多程序可以更多地使用静态类型系统,但它们没有这样做,而是依赖包含区别数据类型标记的特殊字段。

依靠这些特殊字段区别数据类型,这样的程序放弃了类型系统专门提供给它们的保护措施。当这些标记中的一个对它的数据误贴了标签,就会产生我称之为 Impostor Type的错误。

症状

impostor type 错误的一种常见症状是很多概念上不同类型的数据都被同样(并且错误)的方式处理。另一常见症状是数据与任何指定的类型都不匹配。

首要规则是,只要当概念上的数据类型和它被程序处理的方法不匹配,就可以怀疑是否发生了这个模式的错误。

为说明引入这种模式的错误是多么的轻而易举,让我们来考虑一个简单的示例。假设我们需要处理各种各样的欧几里得几何学形状,如圆形、正方形等等。这些几何形状没有坐标,但含有一个 scale 变量,所以可以计算它们的面积。

清单 1. 用 imposter type 实现各种几何形状

public class Form {
   String shape;
   double scale;
   public Form(String _shape, double _scale) {
     this.shape = _shape;
     this.scale = _scale;
   }
   public double getArea() {
     if (shape.equals("square")) {
       return scale * scale;
     }
     else if (shape.equals("circle")) {
       return Math.PI * scale * scale;
     }
     else { // shape.equals("triangle"), an equilateral triangle
       return scale * (scale * Math.sqrt(3) / 4);
     }
   }
}

时间: 2024-12-27 19:16:15

诊断Java代码: Impostor Type错误模式的相关文章

诊断Java代码: 臆想实现错误模式,第2部分

臆想实现重温 回想一下 上次接口的 臆想实现是一个合法的实现,但不满足接口规范的某些未经检查的方面.我们考虑一下下面的堆栈接口,以及许多未被其单独的类型签名捕获的不变量: 清单 1. 一个堆栈接 public interface Stack { public Object pop(); public void push(Object top); public boolean isEmpty(); } 例如,请考虑我们希望任意堆栈实现都遵守的下列规则: 如果一个对象 o 被压进堆栈 s ,且在堆栈

诊断Java代码: Broken Dispatch错误模式

整体和部分 还记得这条谚语吗,"整体大于部分之和"?如果把一个个独立的事件组合成一个相互作用的整体,产生的结果会比单个个体的作用之和要大得多. 程序也是一样的道理.随着一个个新方法被添加到程序中,整个程序可能的控制流程迅速增加.对于大型程序而言,很快局面就会无法控制了.就象是一个荒谬而又不可思议的戏法,有时您得到的最终结果并不是您所期望的方向 ― 这同您在重载方法或者覆盖方法时遇到的情况有些类似. Broken Dispatch 错误模式 面向对象语言的最强大的特性之一就是继承多态性.

诊断Java代码: Double Descent错误模式

不要强制转换这个类! 与可怕的 空指针异常(该异常除了报告空指针之外,对于将要发生的事情什么也不说)不同,类强制转换异常相对来说容易调试. 类强制转换经常发生在递归下行数据结构的程序中,通常是当代码的某些部分在每次方法调用中下行了两级且在第二次下行时调度不当时发生的.程序员可通过学习 Double Descent 错误模式来识别这种问题. Double Descent 错误模式 本周的专题是 Double Descent 错误模式.它通过类强制转换异常来表明.它是由递归下行复合数据结构引起的,这

诊断Java代码: Liar View错误模式

Liar, liar! 设想一下:您已经为一个分布式系统精心设计了一个极好的 GUI 程序,它包含了客户机请求的所有东西及其它一些东西.您已经让它运行通过了一个自动化测试套件的测试 ― 由于不变量的数量是个天文数字,因此,自动化测试是必须的.测试的结果是程序获得了一张"无错误的健康证明书". 发布这个 GUI 的期限到了,但是,作为一个象您这样严格的程序员,只是为了发现错误的行为 ― 本该在自动化测试中就被捕捉到的行为,您启动了程序,对它做最后一次手工测试.但愿您能够避免这种情形.真的

诊断Java代码: 空标志错误模式

空标志错误模式 在我的上一篇文章中,我说明了用空指针代替各种不同基本类型的数据是如何成为引起 NullPointerException 异常最普遍的原因之一的.这一次,我将说明用空指针代替异常情况怎么也会导致问题的出现.在 Java 程序中,异常情况通常是通过抛出异常,并在适当的控制点捕获它们来进行处理.但是经常看到的方法是通过返回一个空指针值来表明这种情况(以及,可能打印一条消息到 System.err ).如果调用方法没有明确地检查空指针,它可能会尝试丢弃返回值并触发一个空指针异常. 您可能

诊断Java代码: Fictitious Implementation错误模式,第1部分

Java 语言接口是一种强大的工具.它具有多继承的很多优点,而没有什么问题.为客户希望使用的所有服务指定一个接口,使得在需要时插进这种接口的不同实现成为可能. 遗憾的是,规范中可以被表达的部分只有方法说明.对任何实现来说,很可能还有很多其它不变量希望被掌握,但是 Java 语言没有提供检查它们的工具. 臆想错误模式 由于这种限制,很可能"实现"了一个接口而实际上没有满足预期的语义.由这种 Fictitious Implementation导致的错误就是本周专栏的主题. 例如,请看一看下

诊断Java代码

诊断Java代码: Broken Dispatch错误模式 诊断Java代码: Double Descent错误模式 诊断Java代码: Impostor Type错误模式 诊断Java代码: Java编程中的断言和时态逻辑 诊断Java代码: Liar View错误模式 诊断Java代码: Repl提供交互式评价 诊断Java代码: 单元测试与自动化代码分析协同工作 诊断Java代码: 将时态逻辑用于错误模式 诊断Java代码: 进行记录器测试以正确调用方法 诊断Java代码: 空标志错误模式

诊断Java代码:孤线程(Orphaned Thread)错误模式

在多线程代码中,使用驱动其它线程所负责的动作的单个主线程是常见的.这个主线程发送消息,通常是通过把它们放到一个队列中,然后其它线程处理这些消息.但是如果主线程抛出一个异常,那么剩余的线程会继续运行,等待更多输入到该队列,导致程序冻结.在诊断 Java 代码的这一部分中,专职 Java 开发者兼兼职捉虫者 Eric Allen 讨论检测.修复和避免这一错误模式. 用多线程编写代码对程序员大有好处.多线程能使编程(和程序)进行得快得多,而且代码能有效得多地使用资源.然而,跟生活中的很多事情一样,多线

诊断Java代码: 进行记录器测试以正确调用方法

用 JUnit进行单元测试是一个功能强大的方法,它可以确保您的代码基础的完整性,但是一些不变量比其他(方法调用序列是其中一种)更难测试.在诊断Java 代码这一部分,Eric Allen描述了怎样在您的单元测试中使用记录器(一种特殊的侦听器),来确保一个方法调用序列按恰当的顺序发生.请点击文章顶部和底部的 讨论,与作者和其他读者在论坛上分享您关于本文的看法. 随着时间的推移,当系统开发人员,维护人员甚至是系统详细说明改变时,JUnit 框架提供一个很好的方法来改善系统的坚固性.通过测试,您可以检