怎样进行异常处理呢?

引言

         “如何更好地使用Java异常处理机制”。这是一个老生常谈,仁者见仁,智者见智的问题,笔者今天将根据自身与他人在实践当中的经验与解决方案进行总结,提出一些个人见解。为了篇幅的考虑,本文将假设所有读者对Java异常处理机制有相应的了解,对于还未接触过Java异常处理机制的朋友,可以参阅《The Java Tutorial》《Thinking in Java》相应的章节。因文笔、技术等能力有限,此文无法避免的出现描述不当、遗漏或错误,如有发现,还请各位看官多多指点与海涵。

关于异常

1.         异常的基本概念

在展开论述前,首先一起探讨一下异常(exception)的定义,在《The Java Tutorial》是这样定义的:异常是程序在执行时发生的事件,它将会打断指令的正常流程。在Java中,异常的结构请见图一:

图一 Java 异常结构图

而根据处理类型分类可以分为 :

  1. 未受检异常(unchecked)

编译器不要求强制处置的异常,其中RuntimeException与其子类就属于此类异常。

  1. 受检异常(checked)

编译器要求必须处置的异常,如Exception、IOException等。

2.         关于“高级异常”与“低级异常”

针对业务系统或应用程序而言,异常应该有一个级别的概念,级别越高的异常越其表达出来的含义,应该越符合系统或应用程序对应现实世界中的具体业务;反之,级别越低的异常将更符合计算机的表达方式。

比如,一位用户去ATM机取钱,当他遇到“余额已不足”异常时,此异常时用户可以理解的,这种异常的级别相应位高级的;当系统抛出“被减数小于零”异常时,这与用户取钱这项行为几乎无关,对用户而言,也无法理解,对应的这种异常的级别相应较低。在此,为了更好的进行下文描述,本人将在此约定命名,级别高的异常为“高级异常”,而级别低的,称之为“低级异常”。

异常处理

1.         异常处理的目标

在进行异常处理前,我们必须有明确的目标,我们应该对异常处理机制所能达到的效果,有一个清晰的描述。因此,在进行异常处理时,应满足以下几个要求:

a)         根据不同用户,给出相应能够理解的异常信息。

b)         异常信息绝对不能丢失,并且还需较详细的状态描述和堆栈信息。

c)         异常层次分明,职责分明。

在满足以上几点的同时,异常处理的代码应尽可能做到美观,增强程序代码的可读性。

2.         一些异常处理的示例

在上一节,我们有了一个比较清晰的异常处理目标,接下来我们将看看一些示例,看是否能达到我们的目标。

a)         忽略异常

try {

doSomething();

}
catch (SomeException e) {}

点评:直接将异常信息全给“吃”了,并没有给出任何提示,这完全就是掩耳盗铃的行为。如果非得存在,那么也应该详细的解释此异常为什么可以忽略。

 

b)         简单的打印堆栈。

try {

doSomething();

}
catch (SomeException e) {

e.printStackTrace();

}

点评:同上,虽然将堆栈在控制台打印出来,但是这样的信息很容易就被淹没,特别是低级异常,很难得到相应的上下文信息,这势必导致异常难以定位与分析。将异常信息记录下来或许是一个不错的选择。

 

c)         catch所有异常,而不是特定异常。

try {

doSomething();

}
catch (Exception e) {

System.out.println(
"doSomething 执行失败。");

}

申明:为了更关注于我们的问题,在这和后面直接以System.out.println作为异常处理的动作(后面不再申明)。但实际处理中,并不建议。原因请见上一个案例 b)

点评:此段代码将捕获可能由 doSomething抛出的任何 RuntimeException 并且阻止它们进行扩散。建议根据实际需求按级别分别捕获相应的异常,非得catch  Exception,那么Exception应该是最后 catch。

 

d)         不清晰的描述

try {

process( someFile );

}
catch (Exception e) {

System.out.println(
"文件处理错误");

}

点评: “文件处理错误”?这里catch住所有的异常,再给出一个不清晰的描述。到底是文件不存在错误呢?还是打开流错误呢?更有甚者,此处有可能出现一个与文件没有半点关系的错误。

 

e)         将特定异常转成一般异常。

try {

doSomething();

}
catch (SomeException e) {

throw new Exception(e);

}

点评:特定的异常是针对特定的问题而定义的,本身就包含了一定的含义 ,转换成一般异常,这仅仅利于异常处理,并不利于排错、定位。抛出一个与该级别(业务)抽象相关的异常或许会更好一点。

 

f)          将高级异常转成低级异常。

try {

doSomething();

}
catch (HighLevelException e) {

throw new LowLevelException(e);

}

点评:我想,这个应该不需要介绍了吧。J

 

g)         过大的catch区域

try {

doSomething1();

doSomething2();

doSomething3();

}
catch (SomeException e) {

System.out.println(
"异常处理");

}

点评:如果doSomething方法都有可能throw出SomeException时。这样做将不能快速定位,不得不反问到底是哪一个方法抛出的呢。

 

h)         大量细小的try catch块

public void process() {

try {

doSomething1();

}
catch (SomeException e) {

System.out.println(
"异常处理");

}

try {

doSomething2();

}
catch (SomeException e) {

System.out.println(
"异常处理");

}

try {

doSomething3();

}
catch (SomeException e) {

System.out.println(
"异常处理");

}

}

点评:捕获了特定的异常、并且能够清晰的定位到方法,这样总无话可说了吧。没错,这样很好!但是一个逻辑处理的方法被三个try catch块切分的支离破碎,我想在阅读此方法的代码时,一眼望去,这段代码表述的是:‘逻辑处理 -> 异常处理 -> 逻辑处理……’,将try-catch块抽离出来再设定一个见名知意的方法名,会不会更好呢?

3.         异常处理时的一些建议

在实际情况中,我们应该尽量避免上面所述的异常处理示例。在实际操作中,有可能遇到更多的问题,我们不妨考虑一下如下的建议。

a)         尽可能的处理异常

要尽可能的处理异常,如果条件确实不允许,无法在自己的代码中完成处理,就考虑声明异常。如果人为避免在代码中处理异常,仅作声明,这是一种错误和依赖的实践。

b)         具体问题具体解决。

异常的部分优点在于能为不同类型的问题提供不同的处理操作。有效异常处理的关键是识别特定故障场景,并开发解决此场景的特定相应行为。为了充分利用异常处理能力,需要为特定类型的问题构建特定的处理器块。

c)         记录可能影响应用程序运行的异常

至少要采取一些永久的方式,记录下可能影响应用程序操作的异常。理想情况下,当然是在第一时间解决引发异常的基本问题。不过,无论采用哪种处理操作,一般总应记录下潜在的关键问题。别看这个操作很简单,但它可以帮助您用很少的时间来跟踪应用程序中复杂问题的起因。

d)         根据抽象(业务)将低级异常转译为高级异常

一个方法所抛出的异常应该在一个抽象层次上定义,该抽象层次与该方法做什么相一致,而不一定与方法的底层实现细节相一致。例如,一个从文件、数据库或者 JNDI 装载资源的方法在不能找到资源时,应该抛出某种 ResourceNotFound 异常(通常使用异常链来保存隐含的原因),而不是更底层的 IOException 、 SQLException 或者 NamingException 。

try {

doSomething();

}
catch (LowLevelException e) {

throw new HighLevelException("doSomething执行错误。",e);

}

e)         只为异常条件使用异常

也就是说,不要为控制流使用异常,比如,在调用 Iterator.next() 时而不是在第一次检查 Iterator.hasNext() 时捕获 NoSuchElementException。

f)          为可恢复的条件使用检查型异常,为编程错误使用运行时异常

g)         避免不必要的使用检查型异常

对于调用者不可能从其中恢复的情形,或者惟一可以预见的响应将是程序退出,则不要使用检查型异常

推荐阅读

  1. 《改进错误处理风格》:指出系统应该抛出什么和捕获什么。
  2. 《技巧:当不能抛出异常时》:当既不能处理、也不能抛出 checked 异常时,有哪些选择。
  3. 《关于异常的争论》:到底使用受检异常还是非受检异常呢?
  4. 《Effective Java》:JDK5.0 Java Collections Framework的设计者Joshua Bloch 的书很好地阐述了在 Java 语言中如何进行适当的异常处理。本文很多观点直接来源于此书。

posted on 2011-05-03 13:41  清己  阅读(389)  评论(1)    收藏  举报

导航