Item 57: Use exceptions only for exceptional conditions

下周三(4.18)要与Bruce Eckel访谈,主题是Exception Handling相关,决定把Effective Java的异常处理这一章的几个item翻译一下。由于是快速翻译,可能有些地方不那么通顺。


Item 57:《只针对不正常情况才使用异常》

有一天,你可能很不幸地遇到下面的这样一段代码:

// 这是对exceptions的严重滥用。永远不要这么做!
try {
int i = 0;
       while(true)
           range[i++].climb();
   } catch(ArrayIndexOutOfBoundsException e) {
}

这段代码在做什么?检查起来并不明显,这也给了我们充分的理由不要使用它。这是一种拙劣的构想,用来循环遍历数组中的元素。这个无限循环会在尝试访问第一个数组边界之外的元素的时候,通过抛出、捕获或者忽略一个ArrayIndexOutOfBoundsException来结束运行。它理应等价于下面这种可以立即被任何一个Java程序员都看得懂的循环遍历数组的标准构想:

for (Mountain m : range)
       m.climb();

那么为什么有人还要优先使用这种基于exception循环而不是用那些标准的写法?这是被误导了,他们企图通过一些错误的理由来提高性能。比如,因为VM会检查所有访问到的数组的边界,那么普通的循环终止检查——隐藏在编译器中但是仍然会在每个for-each循环中呈现——是多余的并且应该被避免。这个理由(reasoning)有三个错误:

  • 因为异常是被设计来处理异常情况的,所以很少有JVM会有动力(incentive)去对它们做优化,让他们比正常情形一样快。

  • 把代码放到try-catch 代码块里会抑制一些现代JVM做的一些优化的生效。

  • 标准的遍历数组的代码风格不一定会造成冗余的检测,现代的JVM实现已经对它们做了优化。

事实上,在现代JVM上,基于异常的(exception-based)编写方式永远比标准方式要慢。在我的机器上,对一个数组循环一百次,基于异常的方式比标准模式要慢两倍以上。基于异常的循环不仅让代码的目的混淆不清、降低效率,而且它并不保证能运行!在一个*看似无关的bug上,循环会悄悄地隐瞒bug,大大复杂了debug的过程。假设循环体中计算的过程包含了对一些无关数组的越界的访问的话。如果使用标准的循环模式,bug会产生一个uncaught exception,立即导致线程终止,打印出完整的栈追踪。如果使用上述不标准的基于异常的循环,bug相关的异常会被捕获,被错误地转义成一个正常的循环终止。

这个故事的寓意很简单:异常,正如它们的名字暗示的,只能被用在异常流程,不应该被用在正常的控制流上。更广义地,你应该使用标准的容易识别的写法,而不是标榜着能提供更高性能的太聪明的技巧。即便这个性能优势真的存在,它也有可能在逐步提升的平台实现面前逐渐消失。这些微妙的bug和维护起来令人头疼的聪明技巧却一定会保留。

这个准则同样对API涉及有所暗示(启发)。一个设计的好的API必须不能强迫客户在正常的控制流里使用异常

总得来说,异常是用在异常流程中的;不要用在正常的控制流中,也不要写API迫使别人这么做。

你可能感兴趣的:(Item 57: Use exceptions only for exceptional conditions)