第57条-只针对异常的情况才使用异常

第57条、只针对异常的情况才使用异常

不知道你否则遇见过下面的代码。

1
2
3
4
5
6
try {
int i = 0;3
while (true)
range[i++].climb();
}catch (ArrayIndexOutOfBoundsException e) {
}

这段代码的意图不是很明显,其本意就是遍历变量range[]中的每一个元素,并执行元素的climb(),当下标超出range[]的长度时,将会直接抛出ArrayIndexOutOfBoundsException异常,catch代码块将会捕获到该异常,但是未作任何处理,只是将该错误视为正常工作流程的一部分来看待。这样的写法确实给人一种匪夷所思的感觉,让我们再来看一下修改后的写法。

1
2
3
for (Mountain m : range) {
m.climb();
}

和之前的写法相比其可读性不言而喻。那么为什么又有人会用第一种写法呢?显然他们是被误导了,他们企图避免for-each循环中JVM对每次数组访问都要进行的越界检查。这无疑是多余的,甚至适得其反,因为将代码放在try-catch块中反而阻止了JVM的某些特定优化,至于数组的边界检查,现在很多JVM实现都会将他们优化掉了。在实际的测试中,我们会发现采用异常的方式其运行效率要比正常的方式慢很多。
除了刚刚提到的效率和代码可读性问题,第一种写法还会掩盖一些潜在的Bug,假设数组元素的climb()中也会访问某一数组,并且在访问的过程中出现了数组越界的问题,基于该错误,JVM将会抛出ArrayIndexOutOfBoundsException异常,不幸的是,该异常将会被climb()之外catch语句捕获,在未做任何处理之后,就按照正常流程继续执行了,这样Bug也就此被隐藏起来。
这个例子的教训很简单:”异常应该只用于异常的情况下,它们永远不应该用于正常的控制流”。虽然有的时候有人会说这种怪异的写法可以带来性能上的提升,即便如此,随着平台实现的不断改进,这种异常模式的性能优势也不可能一直保持。然而,这种过度聪明的模式带来的微妙的Bug,以及维护的痛苦却依然存在。
根据这条原则,我们在设计API的时候也是会有所启发的。设计良好的API不应该为了正常的控制流而使用异常。如Iterator,JDK在设计时充分考虑到这一点,客户端在执行next()之前,需要先调用hasNext()已确认是否还有可读的集合元素,见如下代码。

1
2
3
for (Iterator i = collection.iterator(); i.hasNext(); ) {
Foo f = i.next();
}

如果Iterator缺少hasNext(),改为下面的写法。

1
2
3
4
5
6
try {
Iterator i = collection.iterator();
while (true)
Foo f = i.next();
}catch (NoSuchElementException e) {
}

这应该非常类似于本条目开始时给出的遍历数组的例子。在实际的设计中,还有另外一种方式,即验证可识别的错误返回值,然而该方式并不适合于此例,因为对于next(),返回null可能是合法的。那么这两种设计方式在实际应用中有哪些区别呢?

  1. 如果是缺少同步的并发访问,或者可被外界改变状态,使用可识别返回值的方法是非常必要的,因为在测试状态(hasNext())和对应的调用(next())之间存在一个时间窗口,在该窗口中,对象可能会发生状态的变化。因此,在该种情况下应选择返回可识别的错误返回值的方式。
  2. 如果状态测试方法(hasNext())和相应的调用方法(next())使用的是相同的代码,出于性能上的考虑,没有必要重复两次相同的工作,此时应该选择返回可识别的错误返回值的方式。
  3. 对于其他情形则应该尽可能考虑”状态测试”的设计方式,因为它可以带来更好的可读性。

    总结

    需要异常的地方才使用异常,不能使用异常去控制正常的业务流程。

第45条-将局部变量的作用域最小化

优点

增强代码的可读性,可维护性,并降低错误的可能性。

优化思路

  1. 要使局部变量的作用域最小化,最有力的方法就是在第一次使用它的地方声明,这样有便于读者集中注意力,不容易分散;如果在之前申明,阅读者很容易忘记变量的类型和变量的意义。
  2. 过早的声明变量,使得变量的作用域过早的扩展(作用域扩大),变量销毁的时间过于晚。
  3. 变量的作用域从申明点开始,到这个方法的结束。
  4. 局部变量声明的时候,都应该要先初始化变量。比如,A a;写成A a=null;相对更好,更直观的看到a = null
  5. 循环的使用,forwhileforwhile好用。
    for变量定义在for的作用域内部,while则在外部。这样容易造成变量使用错误,而不会报异常。
    for更简短,增强可读性。
  6. 将局部变量的作用域最小化。如果一个方法有2个操作(不只是业务),可以拆分成2个方法,变量不会冲突,耦合度低。(平时开发也是这样,耦合度低,扩展性好,可读性强,好维护)
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×