《第8章 异常》
异常是Java语言中非常重要、而又容易被轻视的、一个出于非常奇妙的地位的一个东东。这一章讲了关于有效使用异常的指导性原则。
【第39条】只针对不正常的条件才使用异常
异常只应该被用于不正常的情况,它们永远不应该被用于正常的控制流。书中举了一个特殊的例子,在这个例子中,循环的结束条件是当数组下标出界时引发的一个 ArrayIndexOutOfBoundsException。这段程序的作者“自作聪明”地认为“传统”的 for 循环 或 while 循环 需要在每次进行大小判断,是要额外耗费时间的,所以“发明”了这种每次执行完循环体不必“浪费时间去比较大小”的写法,直到产生“下标越界”异常后结束循环。
而事实上,JVM会对那些“传统”的 for while 循环做各种各样的性能优化,而绝不会去对 try-catch 块做出什么优化。这样的“自作聪明”其实反而降低了性能。同时,这样的“非常规”写法,也是代码的可读性急剧下降。而且这种依靠通过异常来达到程序流控制目的的代码,很容易与bug等异常混淆在一起,给调试工作带来了很大的麻烦。
这一条原则,同样也对API的设计者有所启示。当我们在写一个具有状态相关的方法时,不应该强迫调用者为了正常的控制流而使用异常。具有状态相关的方法,简单的说就是不是在什么情况下都可以使用,只有特点条件下才可以。例如,只有在数据库已连接的状态下,才可以调用的数据库操作方法 等。
进一步解释一下,就是说,我们应该通过一个状态检查方法来控制流,而不是使用异常:
// 应该这样
if db.isConnected() {
result = db.select(sql);
}
// 而不该这样
try{
result = db.select(sql);
}catch (DatabaseNotConnectException e){
.....
}
如果称这种方式为“状态检测法”,那么还有一种方式可以称之为“可被识别的返回值”方式。比如,在上面的例子中,既不使用 if 也不适用 try-catch,在 select 方法中,当数据库尚未连接时,不是抛出一个DatabaseNotConnectException,而是返回 null 。
那么正两种方式该如何选择呢?首先,当null是正常情况下本身就可能的一个返回值时“可被识别的返回值”就不适用了,例如书中Iterator.next()的例子。其次,在多线程的程序中,检查状态和真正调用之间,状态可能会被其他线程改变,这种时候就可以使用“可被识别的返回值”方式。最后,当不存在状态检测不稳定的前提下,可以忽略状态检测本身的性能耗费而选用“状态检测法”。
总结:这一条中的前一半,讲的不可用异常来控制程序流,估计我们应该不会犯这样的错误,就当是“无则加免”吧。后一半,讲的两种“方式”倒是非常值得学习和借鉴的。
【Effective Java 学习笔记】系列连载专题请见:
http://tonylian.iteye.com/categories/64208