诊断Java代码: 将时态逻辑用于错误模式

尽管传统的断言可以增加对 Java 代码所作的检查次数,但仅用它们,还是有许多检查无法完成。处理这种情况的方法之一就是使用 时态逻辑。请回忆上个月的文章“ Assertions and temporal logic in Java programming”,时态逻辑有助于提供比程序中的方法更有力的断言,从而有助于增强用其它方式难以正式表达的不变量。

我们不必费力搜寻去发现有助于防止我们程序出错的许多有用的不变量。实际上,可以通过使用此类时态逻辑断言来加大我们消除一些最常见错误模式的力度。在本文中,我们将研究一些错误模式,使用时态逻辑能会对它们产生最积极的影响。我们将把下列错误模式用作示例:

悬挂复合(Dangling Composite)。当您的软件抛出空指针异常时这个错误发生,因为递归数据类型的错误表示,所以这是个难以诊断的问题。

破坏者数据(Saboteur Data)。这种错误在已存储的数据不能满足某些句法或语义约束时发生;此时软件会因为代码操纵的数据中的错误而不是编码中的错误而崩溃。

Split Cleaner。当资源的获取和释放按方法边界一分为二,允许某些控制流不释放它们本该释放的资源,其表现形式为泄漏或过早地释放它们时,这种错误发生。

孤线程(Orphaned Thread)。当主线程抛出异常而剩余线程继续运行,并等待更多输入到该队列时,这种错误发生;这种错误可能导致程序冻结。

臆想实现。当您“实现”了接口,但实际上没有满足其预期的语义时,这种错误发生。

悬挂复合错误模式

悬挂复合错误模式包括递归数据类型的错误表示、将一些类型的基本情况表示为空值而非单独的类。这样做会导致难以诊断的 NullPointerException 。

例如,假定您创建了下列“讨厌的”数据类型来表示二叉树:

清单 1. 一些非常糟糕的树

public class Tree {
  public int value;
  public Tree left;
  public Tree right;
  // Leaves are represented as Trees
  // with null branches.
  public Tree(int _value) {
   this.value = _value;
   this.left = null;
   this.right = null;
  }
  public Tree(int _value, Tree _left, Tree _right) {
   this.value = _value;
   this.left = _left;
   this.right = _right;
  }
}

大家注意,除了作为糟糕的示例,请不要将这个代码用于任何其它用途!

这段代码造成了悬挂复合。二叉树叶子被表示为左右分支都是空值的 Tree。如果我们尝试向下递归这样的树,可能很容易引发 NullPointerException

防止这种错误模式的最佳方法是:将数据类型的表示重构为 Composite 类层次结构(请在 参考资料一节参阅关于这个主题的文章)。假定您恰好已经完成了这样的重构,如下所示:

时间: 2024-11-02 01:49:21

诊断Java代码: 将时态逻辑用于错误模式的相关文章

诊断Java代码::Split Cleaner错误模式

Java 编程语言的一个特色是存储自动管理,它把程序员从很容易出错的释放使用后的内存的工作中解放出来.尽管如此,许多程序还是得处理资源问题,例如文件和数据库连接,这些都必须在使用之后明确地释放掉.跟手工管理存储一样,程序员在手工管理资源时也会犯很多错误.其中一个就是本周专栏的主题 ― Split Cleaner错误模式. 分开还是不分开 在管理诸如文件和数据库连接这样的资源时,您必须在使用完资源后把它释放掉.当然,对代码的任何指定的执行,您希望一次获得资源,然后一次将其释放.要做到这点,您可以采

诊断Java代码: 连续初始化器错误模式

您经常会看到代码不是仅仅通过调用构造函数对类进行初始化,它还通过一些紧接着的意在设置各个域的动作对类进行初始化.不幸的是,这样紧接着的动作是错误的高发地带,会带来连续初始化(run-on initialization)类型的错误. 连续初始化 由于各种原因(多数是糟糕的),您经常会看到这样的类定义,其中的类构造函数并不带有足够的参数来适当地初始化类的所有域.这样的构造函数要求客户机类用几个步骤来对实例进行初始化(设置未被初始化的域的值),而不是用一个构造函数调用就行了.以这样的方式初始化实例是一

诊断Java代码

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

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

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

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

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

诊断Java代码: 设计可扩展的应用程序,第2部分

玻璃箱可扩展性是指这样一种方式:软件系统可在源代码可以查看而不可以修改时被扩展 ― 它是黑箱设计(在这里构建扩展时,不查看原始代码)和开放箱设计(扩展代码直接写入到基础代码)的折衷.因为新的扩展直接建立在原始代码基础上,但不改动原始代码,所以,玻璃箱设计或许是扩展一个软件系统最有效.最安全的方法.在 诊断 Java 代码的这一部分中,Eric Allen 详述了上个月谈及的玻璃箱可扩展性主题.读完本文后,您将知道什么时候使用玻璃箱,并将获得一些如何实现它的提示. 随着信息处理任务(和与之相关的成

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

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

诊断Java代码: 设计可扩展应用程序,第3部分

对应于我们上一篇" 诊断 Java 代码"中所讨论的透明盒可扩展性,黑盒可扩展性是指,在源代码既不能查看也不能修改时,可以扩展软件系统的方法.通常通过系统配置或使用特定于应用程序的脚本语言来进行这样的扩展.在本专题中,Eric Allen 讨论了何时设计黑盒可 扩展性的系统是有意义的,并提供了如何有效地实现这一设计的一些想法.阅读了本文后,您将知道何时使用黑盒并掌握如何实现它的一些技巧. 我已在以前的文章中谈到了代码重用设计策略的重要性(主要是因为各种信息处理任务的差异和相应费用的增加

诊断Java代码: Java编程中的断言和时态逻辑

虽然传统断言可以增加对 Java 代码执行的检查次数,但有许多检查不能用它们来执行.弥补这一缺陷的方法是使用"时态逻辑",它是一种用于描述程序状态如何随时间而更改的形式体系.在本文中,Eric Allen 将讨论断言,介绍时态逻辑并描述用于处理程序中时态逻辑断言的工具 (下一篇文章将检查以前的错误模式和时态逻辑的应用程序). 我们大家同意对 Java 代码检查得越多就越好,我们检查了断言在测试新的和改进的编程中的用法.虽然传统断言可以增加执行的检查次数,但有许多检查不能用它们来执行.