通过在检测到错误时立刻抛出异常来实现迅速失败,可以有效避免不必要的对象构造或资源占用,比如文件或网络连接。同样,打开这些资源所带来的清理操作也可以省却。
  延迟捕获
  菜鸟和高手都可能犯的一个错是,在程序有能力处理异常之前捕获它。Java编译器通过要求检查出的异常必须被捕获或抛出而间接助长了这种行为。自然而然的做法是立即将代码用try块包装起来,并使用catch捕获异常,以免编译器报错。
  问 题在于,捕获之后该拿异常怎么办?不该做的是什么都不做。空的catch块等于把整个异常丢进黑洞,能够说明何时何处为何出错的所有信息都会永远丢失。把异常写到日志中还稍微好点,至少还有记录可查。但我们总不能指望用户去阅读或者理解日志文件和异常信息。让readPreferences()显示错误信息对话框也不合适,因为虽然JCheckbook目前是桌面应用程序,但我们还计划将它变成基于HTML的Web应用。那样的话,显示错误对话框显然不是个选择。同时,不管HTML还是C/S版本,配置信息都是在服务器上读取的,而错误信息需要显示给Web浏览器或者客户端程序。 readPreferences()应当在设计时将这些未来需求也考虑在内。适当分离用户界面代码和程序逻辑可以提高我们代码的可重用性。
  在有条件处理异常之前过早捕获它,通常会导致更严重的错误和其他异常。例如,如果上文的readPreferences()方法在调用FileInputStream构造方法时立即捕获和记录可能抛出的FileNotFoundException,代码会变成下面这样:
  public void readPreferences(String filename){
  //...
  InputStream in = null;
  // DO NOT DO THIS!!!
  try{
  in = new FileInputStream(filename);
  }
  catch (FileNotFoundException e){
  logger.log(e);
  }
  in.read(...);
  //...
  }
  上 面的代码在完全没有能力从FileNotFoundException中恢复过来的情况下捕获了它。如果文件无法找到,下面的方法显然无法读取它。如果 readPreferences()被要求读取不存在的文件时会发生什么情况?当然,FileNotFoundException会被记录下来,如果我们 当时去看日志文件的话,会知道。然而当程序尝试从文件中读取数据时会发生什么?既然文件不存在,变量in是空的,一个 NullPointerException会被抛出。
  调试程序时,本能告诉我们要看日志后面的信息。那将会是NullPointerException,非常让人讨厌的是这个异常非常不具体。错误信息不仅误导我们什么出了错(真正的错误是FileNotFoundException而不是NullPointerException),还误导了错误的出处。真正 的问题出在抛出NullPointerException处的数行之外,这之间有可能存在好几次方法的调用和类的销毁。我们的注意力被这条小鱼从真正的错误处吸引了过来,一直到我们往回看日志才能发现问题的源头。
  既然readPreferences() 真正应该做的事情不是捕获这些异常,那应该是什么?看起来有点有悖常理,通常合适的做法其实是什么都不做,不要马上捕获异常。把责任交给 readPreferences()的调用者,让它来研究处理配置文件缺失的恰当方法,它有可能会提示用户指定其他文件,或者使用默认值,实在不行的话也 许警告用户并退出程序。
  把异常处理的责任往调用链的上游传递的办法,是在方法的throws子句声明异常。在声明可能抛出的异常时,注意越具体越好。这用于标识出调用你方法的程序需要知晓并且准备处理的异常类型。例如,“延迟捕获”版本的readPreferences()可能是这样的:
  public void readPreferences(String filename)
  throws IllegalArgumentException,
  FileNotFoundException, IOException{
  if (filename == null){
  throw new IllegalArgumentException("filename is null");
  }  //if
  //...
  InputStream in = new FileInputStream(filename);
  //...
  }
  技 术上来说,我们需要声明的异常是IOException,但我们明确声明了方法可能抛出FileNotFoundException。 IllegalArgumentException不是必须声明的,因为它是非检查性异常(即RuntimeException的子类)。然而声明它是为 了文档化我们的代码(这些异常也应该在方法的JavaDocs中标注出来)。
  当然,终你的程序需要捕获异常,否则会意外终止。但这里的技巧是在合适的层面捕获异常,以便你的程序要么可以从异常中有意义地恢复并继续下去,而不导致更 深入的错误;要么能够为用户提供明确的信息,包括引导他们从错误中恢复过来。如果你的方法无法胜任,那么不要处理异常,把它留到后面捕获和在恰当的层面处理。
  结论
  经验丰富的开发人员都知道,调试程序的大难点不在于修复缺陷,而在于从海量的代码中找出缺陷的藏身之处。只要遵循本文的三个原则,能让你的异常协助你跟踪和消灭缺陷,使你的程序更加健壮,对用户更加友好。