这篇文章讨论这种非常好异常处理技术,并且展示如何去实现和使用异常链.
简介
异常链(也名”异常嵌套”)是一种异常处理的技术.它将一个原始的异常并且它在抽象层次上由低向高传递的过程中所产生的全部异常组成一个列表,这样在客户端和服务端环境都可以大量简化软件调试工作,并且不增加任何附加的复杂度。这篇文章讨论这种非常好异常处理技术,并且展示如何去实现和使用异常链
异常是接口的一部分
当我们的教授教我们有关封装与模块编程时,我们就被告知应该隐藏实现的细节。比如说,一个提供猎头服务的并不需要刊登广告告诉我们他们是如何及在什么地方找到雇员的,只需要将存取这些服务的方法设为公共的。通过把一个组件的实现从使用它的其它组件中分开,从而减少了复杂性。大多数的程序员意识到异常形成了任何一个类的重要的一部分,但是并不能确定如何处理那些他们在自己代码中无法直接处理的低层异常。从一个高层类当中抛出一个低层异常并不是一个好主意,因为暴露类的实现的细节。
对这个类来说,正确的解决方案就是捕获一个低层异常,然后抛出一个抽象层次高点的异常。
如果方法getEmployee()通过JDBC从数据库中取得employee对象,比如说,在方法内,一个SQLException可能被捕获,这异常可以被转化为EmployeeLookupException 并且重新抛给调用它的方法。
转换异常类的对象
当然不能简单的改变一个异常的类型。当我们把一个异常类对象在转换过程中把它的信息越转越少时候,我们就不禁会疑问这样做的必要性。我们不能把一种异常上抛给另外一些它们之间不存在父子关系的异常。比如,SQLException肯定不是从EmployeeLookupException继承而来,或者,反之已然
在这篇文章中所谓”转换异常”意味着捕获一种异常并且重新抛出一种不同类的全新异常。在某种意义上,转换而来的异常是由前一个异常而引起的,当异常”EmployeeLookupException”被创建和抛出时,这并不意味着第二个异常的出现,而它正是在一个相对高一点的抽象层上表示原先的异常
try {
stmt.execute(sql);
} catch (SQLException ex) {
throw new EmployeeLookupException();
}
对于多数程序员来说,显而易见,这种异常转换是有一个严重的缺陷的:异常的根结点丢失了,异常EmployeeLookupException 是一个logon失败引起的抑或是SQL查询错误?异常转换使得调试更困难了。我已经在上面的段落讨论了应该隐蔽方法的实现细节,但是我们并不想把调试工作弄得比它所需要还要复杂。这样,所能变动就会是那些被隐藏起来的实现细节。但这并不是一个正当的理由去混淆异常发生的原因和在运行时异常发生的位置。我们想使它容易被发现。而用异常链可以达到这两个目标。
上抛和其它异常处理技术
对于异常转换丢失了必要的信息这个问题一个通常的解决方案是通过上抛转换。通常情况下,上抛的是Exception异常和以隐含的方式在每个方法声明抛出Exception异常,这就破坏了在java中需要捕获异常的真正意图。编译器不需要程序员通过这种方法去捕获任何异常。
public Employee getEmployee() throws Exception
{
... database query code ..
}
上抛异常使得异常处理更加因难了,而好的异常转换应该添加更多的上下文,从而使得对问题的描述更精确了。期望中的异常转换,从抽象层次来说,是从低到高的。这样就很少有异常类的继承层次与之相匹配。要记住:一个程序员在设计一个类时所需要抽象的应该与他所正在要解决的问题所相关的。比如说,Exception 比SQLException更普遍。但对一个”an employee lookup service” 来说可能就没有在抽象层次更高的异常了
在这种情况EmployeeLookupException 和 NoPermissionException 则是抽象层次更高一点的一些好的例子。
一个更为糟糕的异常处理策略就是无论忽不忽略它之前捕获并记录每个异常,只要每个异常是被记录的。调试就会想对简单。很不幸,这种将会使调用方法去检测和处理错误变为不可能。而它也会抵销异常存在的意义。
[另外一个问题,在日志记录文件当中,面对一大堆混乱无序的异常信息记录你会很快找到对你解决问题有帮助的信息吗?一个原始异常引发了其它的异常,又因为在日志记录文件中找不到任何它们相关联的信息,你能解决完一个问题而去安心睡觉吗?]
什么是异常链?
异常链就是因为一个根异常而引发的一连串的异常,而这些异常组成一个链表。每个异常在被捕获后,将其转化为更高抽象级别后再次抛出,同时异常也被添加到了链表当中了。
这样就提供了一个异常是为什么发生的一个完整的记录。
try {
stmt.executeUpdate(sql);
} catch (SQLException ex) {
throw new EmployeeLookupException("Query failure",ex);
}
实现
异常链被实现为一个逆序的异常链表,每个异常在链中都是链的结点。链表中的第一个异常可以是任何类型的,并没有在功能上的任何限制。但每个接下来的异常必须有一种途径去指向前一个异常这样这个链才是可以被维护的。此外,必须还要一种方法读取和检测这个异常链。我把这种能提供这种的功能的异常叫做可链接的。也就是说,通过他们一个异常链可以被构建。
为了达到这种目的, 一个可链接的异常必须做两件事:
1. 至少提供一个构器函数使用另外一个异常作为参数并存储它。
2. 重写每个printStackTrace(),首先打印出它们自己的stack trace,然后调用先前异常的printStackTrace()方法打印出先前异常的stack trace.
第一个要求使得一个异常链可以被创建。第二个提供一种简单的方法即通过调用printStackTrace()来展显异常
实际上,这些简单的规则使得可以将异常类添加到链表当中去,而无需给使用的用户强加一些额外的要求,即使当用户使用可链接的异常。一个程序员也没必要了解异常链,如链被使用,然后在Stack Trace时就会获得很大的好处。
如果不使用,对开发人员而言,这也没有走了学习上弯路。而且并不会比使用不可链接的异常坏到那儿去。任何一个异常类都可以写成满足上面所描述的要求,而无论它的基类是什么。
让我们展示一个继承于Exception的异常链的简单实现。
当创建可链接的异常时,ChainedException 可以被当做模板。或者简单地使那些类继承于它也可以。注意要使构造函数把Throwables当参数。而非Exceptions,这使得异常链表可以包含JAVA所能抛出的所有异常。包括编译与运行异常同时也包含错误。这些构造函数的实现是非常简单的。它们将给定异常的引用保存在成员变量_previousTrowable。这个引用将在方法printStackTrace()中使用到。我们也注意到有些构造函数并不将任何Throwabe和Exception作为参数。这些构造函数是被用来构造根异常的。
服务端应用
在服务端使用异常链是非常简单的。像通常一样建立异常链,并且当记录日志时,确定使用方法printStackTrace()。当捕获异常时,无需去记录每个异常。只需要在链中最后一个异常时操作,因为在链中,会自动显示每个异常的信息和调用printStackTrace()方法。
如果对一个刚发生的异常单独记录起来是没什么危害,然而,一个可能不好的结果就是带来一个混乱的日志。Server管理员对于阅读日志判断错误很熟悉,同时他们也会感激对于使用
异常链提供的额外信息。下节我们将描述在客户端程序中一个特殊的对话框类是如何使用户可以读取异常链中的异常的。
客户端
JAVA语言的一个巨大好处就是它可以从错误中恢复过来的能力。 JAVA程序通常情况下并不会因为一个错误而自动停止运行的。相反,错误会以异常的形式被抛出。在JAVA客户端,如果运行异常没有被处理,主要的事件分发器将会在标准输出上打印异常的Stack trace。这个简单的动作实际上是非常有用的。当用户的按钮点击动作实际并没有完成任何事情时候,至少发生错误的有关信息是可以看到的。所以许多的程序员决定用这种方式处理所有的异常--à记录异常并中断操作。
一个还要好的方法就是通知用户程序所发生的任何错误。通常用一个对话框。好的对话框会清楚的显示两件东西:是什么样的操作失败了?同时为什么?我已经实现了一个对话框可以和能提供这两种信息的异常一起使用。无论是异常链还是单独的异常都能并无区别的使用它。
为了使用ExceptionDiolog, ”try-catch”块得加入到一个程序员已经注册了的GUI事件处理器
当一个异常被事件处理器所捕获后,ExceptionDiolog的事例会被创建。对话框的标题会向用户表明用户所请求的什么操作因为异常抛出而失败!对话框的信息会显示调用异常中的getLocalizedMessage()方法的结果,接下来,提供第二块重要的信息。即说明操作为什么不能完成。图3显示了将详细信息隐藏起来的对话框。
try {
... event handler code ...
} catch (Exception ex) {
ExceptionDialog dlg = new ExceptionDialog(frame,ex,"Look-up failed.");
dlg.show();
}
为了更详细了查看发生异常的原因。用户可以点击”Show detaills”按钮展开对话框。展开来的对话框显示了异常的调用堆栈(the call stack of each exception)并且如是一个异常链的话同时也会显示链中的每个异常的调用堆栈。
Leon·He
Broaden Gate(ShenZhen) Corp
E-Mail:English98@tom.com
Do things as right as possible.
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1762432