避免使用空指针做为标志以防止出现异常

 为开发健壮的程序,咱们常常用空指针代替异常状况,但这实际上却把控制流限制在方法调用和返回的普通方式,同时也隐藏了异常状况发生的迹象。在这篇专栏里,Eric Allen 展现了这种错误模式(他称之为空标志错误模式)怎样产生难以调试的意外结果。和咱们讨论过的其它错误模式同样,您能够应用某种编程技巧来减小这种错误的出现。
空标志错误模式
在个人上一篇文章中,我说明了用空指针代替各类不一样基本类型的数据是如何成为引发 NullPointerException 异常最广泛的缘由之一的。这一次,我将说明用空指针代替异常状况怎么也会致使问题的出现。在 Java 程序中,异常状况一般是经过抛出异常,并在适当的控制点捕获它们来进行处理。可是常常看到的方法是经过返回一个空指针值来代表这种状况(以及,可能打印一条消息到 System.err)。若是调用方法没有明确地检查空指针,它可能会尝试丢弃返回值并触发一个空指针异常。html

您可能会猜测,之因此称这种模式为空标志错误模式,是由于它是不一致地使用空指针做为异常状况的标志引发的。java

原由
让咱们来考虑一下下面的这个简单的桥类(从 BufferedReaders 到 Iterators):程序员

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Iterator;编程


public class BufferedReaderIterator implements Iterator {设计模式

private BufferedReader internal;模块化

public BufferedReaderIterator(BufferedReader _internal) {
this.internal = _internal;
}工具

public boolean hasNext() {
try {测试

boolean result = true;网站

// Let´s suppose that lines in the underlying input stream are known
// to be no greater than 80 characters long.
internal.mark(80);this

if (this.next() == null) {
result = false;
}
internal.reset();
return result;
}
catch (IOException e) {
System.err.println(e.toString());
return false;
}
}

public Object next() {
try {
return internal.readLine();
}
catch (IOException e) {
System.err.println(e.toString());
return null;
}
}

public void remove() {

// This iterator does not support the remove operation.
throw new UnsupportedOperationException();
}

}

 

由于这个类做为 Iterator 接口的桥接实现,代码必须从 BufferedReader 捕获 IOException 异常。每一种方法经过返回某个缺省值来处理 IOException。对于 hasNext,返回 false 值。这是合理的,由于若是 IOException 异常被抛出,客户就不该该期望能从 Iterator 检索到另外一个元素。另外一方面,在 IOException 异常(由于它取决于 internal.readLine() 的返回值)和 internal 是空的状况下,next 都返回 null。但这不是 Iterator 对象的客户所期待的。正常状况下,在没有更多元素的 Iterator 上调用 next 时,会抛出一个 NoSuchElementException 异常。若是咱们的 Iterator 的客户依赖于这种行为,它极可能会尝试丢弃从调用 next 返回的空指针,结果致使 NullPointerException 异常。

无论 NullPointerException 异常何时出现,都要对如上所述的状况做检查。这种错误模式的出现很广泛。

预防措施
尽管这种错误模式常常出现,使用空标志还是很是没有根据的(与上例的状况同样)。让咱们来重写 next,使它如咱们指望的同样抛出 NoSuchElementException 异常:

public Object next() {
try {
String result = internal.readLine();
if (result == null) {
throw new NoSuchElementException();
}
else {
return result;
}
}
catch (IOException e) {

// The original exception is included in the message to notify the
// client that an IOException has occurred.
throw new NoSuchElementException(e.toString());
}
}

请注意:要使其他的代码能使用修改过的方法,咱们还必须:

导入 java.util.NoSuchElementException。
修正 hasNext,使其再也不调用 next 来进行测试。最简单的修正方法是只要直接调用 internal.readLine()。

 

另外一种处理 IOException 异常的方法是捕获它们,并代替它们抛出 RuntimeException 异常。决定这样作是基于对目标平台上预计 IOException 异常出现频率的估计。若是很频繁,那么您可能想试着从中恢复。

调用这个新 next 方法的任何代码可能都不得不处理抛出的 NoSuchElementException 异常。(固然,代码能够简单地选择忽略它们并容许程序异常终止。)若是这样,与原始代码抛出的 NullPointerException 异常相比,产生的错误消息和抛出异常的位置所提供的信息要丰富得多。若是抛出的异常是检查过的异常(好比 IOException),那么它会更有用,由于除非处理了异常,不然类的客户代码将不编译。利用这种方法,咱们甚至能够在程序运行前排除某些错误发生的可能性。可是,在这个示例中,不破坏 Iterator 接口,就不能抛出这样一个检查过的异常。所以,为了重复使用在 Iterators 上运行的代码,咱们牺牲了一些静态检查。静态检查的目的和重复使用的目的之间的这种矛盾是很广泛的。

总结
在我完成这篇文章前,我要提醒许多常用空标志的程序员注意。许多程序员会争辩说这会使他们的程序更“健壮”。毕竟,他们可能会说,健壮的系统可以适当地处理不一样的状况,而不是一遇到小问题就抛出异常。可是这种争辩忽视了这样一种事实,即异常是加强代码健壮性的有力工具,它容许在异常状况下控制能快速传送到最适合控制的位置。另外一方面,空标志的使用把控制流限制在方法调用和返回的普通方式(固然,一直到整个程序崩溃)。此外,这样使用空标志,程序员有效地掩盖了异常状况出现位置的迹象。谁知道空指针在被丢弃前从方法到方法传递了多远?这只能使得诊断错误以及肯定怎样修正它们更加困难。经验证实这种代码常常中断。咱们首要关注的应该是避免这种困惑,使诊断尽量容易。所以,做为准则,我努力编写能够尽快通知异常状况的代码,而且尝试着仅从没有指示程序错误的异常状况中恢复。

即便在代码中尽可能避免使用空标志,您仍要不可避免地处理使用了空标志的旧代码。事实上,许多 Java 类库自己,好比咱们上面使用过的 Hashtable 类和 BufferedReader 类都用了空标志。当使用这样的类时,您能够经过在执行前,显式检查操做是否将返回空来避免错误。例如,对于 Hashtables,我老是在调用 get 以前用 containsKey 进行测试。可是,尽管采用这种预防手段,这种错误模式仍然是最常碰到的错误模式之一。

下面是本周的错误模式的小结:

模式:空标志
症状:使用空指针做为异常状况的标志的代码块报告 NullPointerException 异常。
原由:调用方法没有检查做为返回值的空指针。
治疗和预防措施:抛出异常来报告异常状况。

 

在下一篇文章中,我将讨论与类强制转换异常有关的错误模式。

参考资料

请务必阅读 Eric Allen 的前一个关于错误模式的诊断 Java 代码专栏:
The Dangling Composite bug pattern(developerWorks,2001 年 3 月)
错误模式:介绍(developerWorks ,2001 年 2 月)
一种防止异常状况处理不一致的方法是面向表征的编程: 一种将程序的常常绕过模块边界的部分模块化的编程风格。请查看 AspectJ,Java 语言的一种面向表征的扩展,带有支持许多流行的 Java IDE 的实现。
静态肯定可能出现空指针异常的方法是一种被称为 set-based analysis 的技术。The Carnegie Mellon School of Computer Science 网站为这种方法提供了简短介绍,同时还提供与本文相关的一些技术出版物的连接。
DePaul 大学的软件工程部已经在自动化定理方面作了一些工做,在 Java 代码中侦测出空指针异常。
访问 Patterns 主页,获取关于设计模式以及怎样使用它们的好的介绍。
请查看 JUnit,经过将代码 "test-infested" 来捕捉更多的错误。

 

本文转自 ☆★ 一应俱全 ★☆ - www.baoluowanxiang.com 转载请注明出处,侵权必究!
原文连接:http://www.baoluowanxiang.com/a/program/java/2011/0401/3053.html

相关文章
相关标签/搜索