Welcome 微信登录

首页 / 软件开发 / JAVA / 诊断Java代码: Fictitious Implementation错误模式,第1部分

诊断Java代码: Fictitious Implementation错误模式,第1部分2011-02-10 IBM Eric E. AllenJava 语言接口是一种强大的工具。它具有多继承的很多优点,而没有什么问题。为客户希望使用的所有服务指定一个接口,使得在需要时插进这种接口的不同实现成为可能。

遗憾的是,规范中可以被表达的部分只有方法说明。对任何实现来说,很可能还有很多其它不变量希望被掌握,但是 Java 语言没有提供检查它们的工具。

臆想错误模式

由于这种限制,很可能“实现”了一个接口而实际上没有满足预期的语义。由这种 Fictitious Implementation导致的错误就是本周专栏的主题。

例如,请看一看下面这个堆栈的接口:

清单 1. 堆栈的接口

public interface Stack {
public Object pop();
public void push(Object top);
public boolean isEmpty();
}

从 Java 类型检查器的角度看,包含符合如上说明的方法的任何类可以作为 Stack 的合法实现。但是实际上,我们希望堆栈能满足一些另外的要求。例如:

如果一个对象 o 被压入堆栈 s ,并且在堆栈上进行的下一步操作是 pop ,那么这个操作的返回值应该是 o 。

如果对于一个给定的堆栈 s , s.isEmpty() 的返回值是 true ,并且在这个堆栈上进行的下一步操作是 pop ,那么调用 pop 应该抛出一个 RuntimeException 异常。

还有大量其它的可以指定的不变量。我们希望堆栈怎么处理多次 push 操作?对于多线程会有什么行为?很难通过编程来实施这些不变量。我们可以(并且应该)在文档编写时提及它们,但是编写实现的开发者可能容易忽略它们。如果发生这种情况,那么依赖这些不变量的客户将不能完成这种实现,就形成了错误。我称这种模式的错误为 Fictitious Implementation ,因为我公正地将其归咎于实现而不是客户。正如任何错误都有自己的模式一样,Fictitious Implementation 可能不能立刻看出,而是潜伏,一直隐藏到某种不平常的执行路径发现它。

不要责怪 Java 语言!

在继续这篇专栏前,我要指出我并不是批评 Java 语言不能指定这种不变量。允许这种规范的任何机制都会有很多随之而来的缺点。首先,我们想要指定的很多不变量不能被静态地检查。虽然类型说明只表达了不变量的一小部分,但是比上面我们概述的用于堆栈的这类约束容易检查。

在接口中允许更多可表达规范有另一方面的缺点:这样做,很容易让 Java 语言背负很多问题,使得语言中到处都是多继承。请看下面的接口:

清单 2. 弹出器接口

public interface Popper {
public Object pop();
}