错误和意外事件

  假想金融应用软件中的CheckingAccount类。CheckingAccount属于客户,维护当前余额,并且可以接受存款,根据支票 接受终止支付命令,以及处理入帐的支票。CheckingAccount对象必须协调并发线程的访问,因为每一个线程都可以改变它的状态。 CheckingAccount的processCheck()方法将Check对象作为参数,从账户余额中正常扣除支票金额。但是调用 processCheck()的支票结算客户端必须为两类意外事件做好准备。首先,CheckingAccount 可能有为支票注册的终止支付命令。第二,账户中可能没有足够的资金来支付支票金额。

  所以,processCheck()方法使用三种可能的方式来响应其调用者。正常的响应方式是支票得到处理,在方法签名中声明的结果返回给调用 服务。这两类意外事件响应代表了金融领域非常真实的情况,它们需要与支票结算客户端进行通信。所有这三种processCheck()响应都是为模仿典型 的支票账户行为而精心设计的。

  在Java中表示意外事件响应的通常方法是定义两种异常,即StopPaymentException和 InsufficientFundsException。客户端忽略这两个异常是不正确的,因为在应用程序正常操作时会被抛出这两个异常。这两个异常有助 于表达方法的所有行为,和方法签名一样十分重要。

  客户端可以轻松地处理这两类异常。如果终止支票的支付,客户端可以取得支票进行特殊处理。如果没有足够的资金,为支付此支票,客户端将从客户的储蓄帐户中转移资金,并重新尝试。

  这些意外事件可以预见,它是使用CheckingAccount API的自然结果。它们并不表示软件或执行环境的失败。将这些异常条件与实际的失败对比,实际的失败是由于CheckingAccount类的内部实现细节问题造成的。

  假设CheckingAccount在数据库中维持持久的状态并使用JDBC API进行访问。在该API中,几乎每一个数据库访问方法都有失败的可能性,但原因与CheckingAccount实现无关。例如,有人会忘记打开数据 库服务器、不小心拔下了网线,或改变了访问数据库的密码。

  JDBC依靠单独的已检查异常SQLException来报告一切可能的错误。大多数错误都与数据库的配置、连接和硬件设备有关。 processCheck()方法并不能以有意义的方式处理这些异常条件。很遗憾,因为processCheck()至少知道它自己的实现方式。调用堆栈 中的上游方法能够处理这些问题的可能性会更小。

  CheckingAccount示例解释了导致方法执行不能返回预期结果的两个基本原因。下面首先介绍一些描述性术语:

  意外事件

  是一种可以预见的情况,要求方法做出某种响应,以便能够表达方法所期望的目的。方法的调用者预见这些情况并采取策略应付这些情况。

  错误

  是一种计划外的情况,它阻止方法达到其预期目的,并且如果不引用方法的内部实现,则无法描述这种情况。

  从这两个术语来看,终止支付命令和透支是processCheck()方法两个可能的意外事件。SQL问题代表了可能的错误异常条件。processCheck()的调用者应当提供一种处理意外事件的方式,但当错误发生时,并不能合理地处理该错误。

  映射Java异常

  对于意外事件和错误,思考其原因将有助于为应用程序架构中的Java异常建立约定。

 

异常条件  意外事件  错误 

认为是(Is considered to be) 

设计的一部分 

难以应付的意外 

预期发生(Is expected to happen) 

有规律但很少发生 

从不 

谁来处理(Who cares about it) 

调用方法的上游代码 

需要修复此问题的人员 

实例(Examples) 

另一种返回模式 

编程缺陷,硬件故障,配置错误,文件丢失,服务器无法使用 

佳映射(Best Mapping) 

已检查异常 

未检查异常 

  意外事件异常条件完美地映射到Java已检查异常。由于它们是方法语义契约中不可或缺的一部分,因此必须借助编译器来确保问题得到了处理。如果 开发人员坚持在编译器有问题时处理或声明意外事件异常,此时编译器会成为一种阻碍,可以断定此软件设计必须进行部分重构。这其实是一件好事。

  错误条件对编程人员来说能够引起关注,而对于软件逻辑却并非如此。“软件诊断学家”收集错误信息以诊断和修复引起错误发生的根源。因此,未检查 Java异常是错误的完美表现方式,它们可以使错误通知完整地过滤调用堆栈上的所有方法,传递到专门用于捕捉错误的层,捕获其中所包含的诊断信息,并为此 活动提供一份受约束的合理结论。错误产生方法并不需要声明,上游代码也不需要捕获它们,方法的实现得到了有效的隐藏——产生少的代码混乱。