尽管传统的断言可以增加对 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 类层次结构(请在 参考资料一节参阅关于这个主题的文章)。假定您恰好已经完成了这样的重构,如下所示: