在使用CheckingAccount的API时,这些应变都是可以预计的和自然的结果。他们并不是意味着软件或运行环境的失败。这些异常和由于CheckingAccount类中一些内部实施细则引起的真正失败是不同的。
设想CheckingAccount对象在数据库里保持着一个恒定的状态,并使用JDBC API来对之访问。在那个API里,几乎所有的数据库访问方法都有可能因为和CheckingAccount实施无关的原因而失败。比如,有人可能忘了把数据库服务器运行起来,一个未有连上的网络数据线,访问数据库的密码改变了,等等。
JDBC依靠一种“检查的异常”,SQLException,来汇报任何可能的错误。可能出错的绝大多数原由都是数据库的配置,连接,或其所在的硬件设施。对processCheck方法而言,它对上述错误是无计可施的。这不应该,因为processCheck至少了解它自己的实施细则。在调用栈里上游的方法能处理这些问题的可能就更小。
CheckingAccount这个例子说明了一个方法不能成功返回它想要的结果的两个基本原因。这里是两个描述性的术语:
应变
与实际预料相符,一个方法给出另外一种回应,而这种回应可以表达成该方法所要达到的目的之一。这个方法的调用者预料到这个情况的出现,并有相对的应付之道。
故障
在未经计划的情况下,一个方法不能达到它的初衷,这是一个不诉诸该方法的实施细则就很难搞清的情况。
应用这些术语,对processCheck方法而言,一个止付的命令和一个超额的提取是两种可能的应变。而SQLException反映了可能的故障。processCheck方法的调用者应该能够提供应变,但却不一定能有效的处理好可能发生的故障。
Java异常的匹配
在建立应用架构中Java异常的规则时,以应变和故障的方式仔细考虑好“什么可能会出错”是有长远意义的。
条件
应变
故障
被考虑成
设计的一部分
一个糟糕的意外
预计到会发生
经常发生
绝不发生
关注方
上游对该方法的调用者
需要修好这个问题的人
举例 另一种返回方式
程序bug,硬件系统故障,配置错误,丢失的文件,服务器没有运行
最好的匹配
一个检查的异常
一个未检查的异常
应变情况恰如其分地匹配给了Java检查的异常。因为它们是方法的语义算法合同中不可缺少的一部分,在这里借助于编译器的帮助来确保它们得到解决是很有道理的。如果你发现编译器坚持应变的异常必须要处理或者在不方便的时候必须要声明会给你带来些麻烦,你在设计上几乎肯定要做些重构了。这其实是件好事。
出现故障的情况对开发人员而言是蛮有意思的,但对软件逻辑而言却并非如此。那些软件”消化问题“的专家们需要关于故障的信息以便来解决问题。因此,未检查的异常是表示故障的很好方式。他们让故障的通知原封不动地从调用栈上所有的方法滤过,到达一个专门来捕获它们的地方,并得到它们自身包含的有利于诊断的信息,对整个事件提供一个有节制的优雅的结论。产生故障的方法不需要来声明(异常),上游的调用方法不需要捕获它们,方法的实施细则被正确的隐藏起来- 以最低的代码复杂度。
新一些的Java API,比如像Spring架构和Java Data Ojects类库对检查的异常几乎没有依赖。Hibernate ORM架构在3.0版本里重新定义了一些关键功能来去除对检查的异常的使用。这就意味着在这些架构举报的绝大部分异常都是不可恢复的,归咎于错误的方法调用代码,或是类似于数据库服务器之类的底层部件的失败。特别的,强迫一个调用方来捕获或声明这些异常几乎没有任何好处。
设计里的故障处理
在你的计划里,承认你需要去做就迈好了有效处理好故障的第一步。对那些坚信自己能写出无懈可击的软件的工程师们来说,承认这一点是不容易的。这里是一些有帮助的思考方式。首先,如果错误俯拾即是,应用的开发时间将很长,当然前提是程序员自己的bug自己修理。第二,在Java类库中,过度使用检查的异常来处理故障情形将迫使你的代码要应对好故障,即使你的调用次序完全正确。如果没有一个故障处理的架构,凑合的异常处理将导致应用中的信息丢失。
一个成功的故障处理架构一定要达到下面的目标:
减少代码的复杂性
捕获和保存诊断性信息
对合适的人提醒注意
优雅地退出行动
故障是应用的真实意图的干扰。因此,用来处理它们的代码应尽量的少,理想上,把它们和应用的语义算法部分隔离开。故障的处理必须满足那些负责改正它们的人的需要。开发人员需要知道故障发生了,并得到能帮助他们搞清为何发生的信息。即使一个故障,在定义上而言,是不可补救的,好的故障处理会试着优雅地结束引起故障的活动。
对故障情况使用未检查的异常
在做框架上的决定时,用未检查的异常来代表故障情况是有很多原因的。Java的运行环境对代码的错误会抛出“运行时异常”的子类,比如,ArithmeticException或ClassCastException。这为你的框架设了一个先例。未检查的异常让上游的调用方法不需要为和它们目的不相关的情况而添加代码,从而减少了混乱。
你的故障处理策略应该认识到Java类库的方法和其他API可能会使用检查的异常来代表对你的应用而言只可能是故障的情况。在这种情形下,采用设计约定来捕获API异常,将其以故障来看待,抛出一个未检查的异常来指示故障的情况和捕获诊断的信息。
在这种情况下抛出的特定异常类型应该由你的框架来定义。不要忘记一个故障异常的主要目的是传递记录下来的诊断信息,以便让人们来想出出错的原因。使用多个故障异常类型可能有些过,因为你的架构对它们都一视同仁。多数情况下,一条好的,描述性强的信息将单一的故障类型嵌入就够用了。使用Java基本的RuntimeException来代表故障情况是很容易的。截止到Java1.4,RuntimeException,和其他的抛出类型一样,都支持异常的嵌套,这样你就可以捕获和报出导向故障的检查的异常。
你也许会为了故障报告的目的而定义你自己的未检查的异常。这样做可能是必要的,如果你使用Java1.3或更早的版本,它们都不支持异常的嵌套。实施一个类似的嵌套功能来捕获和转换你应用中构成故障的检查的异常是很简单的。你的应用在报错时可能需要一个特殊的行为。这可能是你在架构中创建RuntimeException子类的另一个原因。
建立一个故障的屏障
对你的故障处理架构而言,决定抛出什么样的异常,何时抛出是重要的决定。同样重要的是,何时来捕获一个故障异常,之后再怎么办。这里的目的是让你应用中的功能性部分不需要处理故障。把问题分开来处理通常都是一件好事情,有一个中央故障处理机制长远来看是很有裨益的。
在故障屏障的模式里,任何应用组件都可以抛出故障异常,但是只有作为“故障屏障”的组件才捕获异常。采用此种模式去除了大多数程序员为了在本地处理故障而插入的复杂的代码。故障屏障逻辑上位于调用栈的上层,这样在一个默认的行动被激发前,一个异常向上举报的行为就被阻止了。根据不同的应用类型,默认的行动所指也不同。对一个独立的Java应用而言,这个行动指活着的线程被停止。对一个位于应用服务器上的Web应用而言,这个行动指应用服务器向浏览器