以文本方式查看主题 - 中文XML论坛 - 专业的XML技术讨论区 (http://bbs.xml.org.cn/index.asp) -- 『 编程心得 』 (http://bbs.xml.org.cn/list.asp?boardid=42) ---- Java 理论与实践: 关于异常的争论 (http://bbs.xml.org.cn/dispbbs.asp?boardid=42&rootid=&id=8274) |
-- 作者:admin -- 发布时间:6/14/2004 11:31:00 AM -- Java 理论与实践: 关于异常的争论 Java 理论与实践: 关于异常的争论 要检查,还是不要检查? 级别: 入门 Brian Goetz 关于在 Java 语言中使用异常的大多数建议都认为,在确信异常可以被捕获的任何情况下,应该优先使用检查型异常。语言设计(编译器强制您在方法签名中列出可能被抛出的所有检查型异常)以及早期关于样式和用法的著作都支持该建议。最近,几位著名的作者已经开始认为非检查型异常在优秀的 Java 类设计中有着比以前所认为的更为重要的地位。在本文中,Brian Goetz 考察了关于使用非检查型异常的优缺点。请在附带的讨论论坛中与作者和其他读者一起分享您有关本文的心得体会(您也可以点击文章顶部或底部的 讨论 来访问该论坛。) 对于因为编程错误而导致的异常,或者是不能期望程序捕获的异常(解除引用一个空指针,数组越界,除零,等等),为了使开发人员免于处理这些异常,一些异常被命名为非检查型异常(即那些继承自 RuntimeException 的异常)并且不需要进行声明。 传统的观点 因为 Java 语言并不要求方法捕获或者指定运行时异常,因此编写只抛出运行时异常的代码或者使得他们的所有异常子类都继承自 RuntimeException ,对于程序员来说是有吸引力的。这些编程捷径都允许程序员编写 Java 代码而不会受到来自编译器的所有挑剔性错误的干扰,并且不用去指定或者捕获任何异常。尽管对于程序员来说这似乎比较方便,但是它回避了 Java 的捕获或者指定要求的意图,并且对于那些使用您提供的类的程序员可能会导致问题。 检查型异常代表关于一个合法指定的请求的操作的有用信息,调用者可能已经对该操作没有控制,并且调用者需要得到有关的通知 —— 例如,文件系统已满,或者远端已经关闭连接,或者访问权限不允许该动作。 如果您仅仅是因为不想指定异常而抛出一个 RuntimeException,或者创建 RuntimeException 的一个子类,那么您换取到了什么呢?您只是获得了抛出一个异常而不用您指定这样做的能力。换句话说,这是一种用于避免文档化方法所能抛出的异常的方式。在什么时候这是有益的?也就是说,在什么时候避免注明一个方法的行为是有益的?答案是“几乎从不。” 在 Effective Java: Programming Language Guide 一书中(请参阅 参考资料),Josh Bloch 提供了下列关于检查型和非检查型异常的知识点,这些与 “The Java Tutorial” 中的建议相一致(但是并不完全严格一致): 第 39 条:只为异常条件使用异常。也就是说,不要为控制流使用异常,比如,在调用 Iterator.next() 时而不是在第一次检查 Iterator.hasNext() 时捕获 NoSuchElementException。 对于检查型异常的一些批评 检查型异常不适当地暴露实现细节 一个用于装载用户概要的方法,在找不到用户时应该抛出 NoSuchUserException,而不是 SQLException —— 调用者可以很好地预料到用户可能找不到,但是不知道如何处理 SQLException。异常链可以用于抛出一个更为合适的异常而不用丢弃关于底层失败的细节(例如栈跟踪),允许抽象层将位于它们之上的分层同位于它们之下的分层的细节隔离开来,同时保留对于调试可能有用的信息。 据说,诸如 JDBC 包的设计采取这样一种方式,使得它难以避免该问题。在 JDBC 接口中的每个方法都抛出 SQLException,但是在访问一个数据库的过程中可能会经历多种不同类型的问题,并且不同的方法可能易受不同错误模式的影响。一个 SQLException 可能指示一个系统级问题(不能连接到数据库)、逻辑问题(在结果集中没有更多的行)或者特定数据的问题(您刚才试图插入行的主键已经存在或者违反实体完整性约束)。如果没有犯不可原谅的尝试分析消息正文的过失,调用者是不可能区分这些不同类型的 SQLException 的。(SQLException 的确支持用于获取数据库特定错误代码和 SQL 状态变量的方法,但是在实践中这些很少用于区分不同的数据库错误条件。) 不稳定的方法签名 有时,当程序员对因为实现的改变而导致从方法签名中增加或者删除异常感到厌烦时,他们不是通过使用一个抽象来定义特定层次可能抛出的异常类型,而只是将他们的所有方法都声明为抛出 Exception。换句话说,他们已经认定异常只是导致烦恼,并且基本上将它们关闭掉了。毋庸多言,该方法对于绝大多数可任意使用的代码来说通常不是一个好的错误处理策略。 难以理解的代码 异常淹没 极度通用的 try...catch 块是另一种形式的异常淹没,并且更加难以检测,因为这是 Java 类库中的异常类层次的结构而导致的(可疑)。让我们假定一个方法抛出四个不同类型的异常,并且调用者遇到其中任何一个异常都将捕获、记录它们,并且返回。实现该策略的一种方式是使用一个带有四个 catch 子句的 try...catch 块,其中每个异常类型一个。为了避免代码难以理解的问题,一些开发人员将重构该代码,如清单 1 所示: 清单 1. 意外地淹没 RuntimeException try {
尽管该代码与四个 catch 块相比更为紧凑,但是它具有一个问题 —— 它还捕获可能由 doSomething 抛出的任何 RuntimeException 并且阻止它们进行扩散。 过多的异常包装 替换的方法 清单 2. Eckel 的异常适配器类 class ExceptionAdapter extends RuntimeException {
如果查看 Eckel 的 Web 站点上的讨论,您将会发现回应者是严重分裂的。一些人认为他的提议是荒谬的;一些人认为这是一个重要的思想。(我的观点是,尽管恰当地使用异常确实是很难的,并且对异常用不好的例子大量存在,但是大多数赞同他的人是因为错误的原因才这样做的,这与一个政客位于一个可以随便获取巧克力的平台上参选将会获得十岁孩子的大量选票的情况具有相似之处。) Rod Johnson 是 J2EE Design and Development (请参阅 参考资料) 的作者,这是我所读过的关于 Java 开发,J2EE 等方面的最好的书籍之一。他采取一个不太激进的方法。他列举了异常的多个类别,并且为每个类别确定一个策略。一些异常本质上是次要的返回代码(它通常指示违反业务规则),而一些异常则是“发生某种可怕错误”(例如数据库连接失败)的变种。Johnson 提倡对于第一种类别的异常(可选的返回代码)使用检查型异常,而对于后者使用运行时异常。在“发生某种可怕错误”的类别中,其动机是简单地认识到没有调用者能够有效地处理该异常,因此它也可能以各种方式沿着栈向上扩散而对于中间代码的影响保持最小(并且最小化异常淹没的可能性)。 Johnson 还列举了一个中间情形,对此他提出一个问题,“只是少数调用者希望处理问题吗?”对于这些情形,他也建议使用非检查型异常。作为该类别的一个例子,他列举了 JDO 异常 —— 大多数情况下,JDO 异常表示的情况是调用者不希望处理的,但是在某些情况下,捕获和处理特定类型的异常是有用的。他建议在这里使用非检查型异常,而不是让其余的使用 JDO 的类通过捕获和重新抛出这些异常的形式来弥补这个可能性。 使用非检查型异常 通过在 C++ 中使用异常,其中所有的异常都是非检查型的,我已经发现非检查型异常的最大风险之一就是它并没有按照检查型异常采用的方式那样自我文档化。除非 API 的创建者明确地文档化将要抛出的异常,否则调用者没有办法知道在他们的代码中将要捕获的异常是什么。不幸的是,我的经验是大多数 C++ API 的文档化非常差,并且即使文档化很好的 API 也缺乏关于从一个给定方法可能抛出的异常的足够信息。我看不出有任何理由可以说该问题对于 Java 类库不是同样的常见,因为 Jav 类库严重依赖于非检查型异常。依赖于您自己的或者您的合作伙伴的编程技巧是非常困难的;如果不得不依赖于某个人的文档化技巧,那么对于他的代码您可能得使用调用栈中的十六个帧来作为您的主要的错误处理机制,这将会是令人恐慌的。 文档化问题进一步强调为什么懒惰是导致选择使用非检查型异常的一个不好的原因,因为对于文档化增加给包的负担,使用非检查型异常应该比使用检查型异常甚至更高(当文档化您所抛出的非检查型异常比检查型异常变得更为重要的时候)。 文档化,文档化,文档化 参考资料 请阅读 Brian Goetz 的完整的 Java 理论与实践 系列。
关于作者 |
-- 作者:longlongnosee -- 发布时间:4/20/2005 2:56:00 PM -- 懂了一点点,下次再看 |
-- 作者:lywzd -- 发布时间:11/2/2005 10:09:00 AM -- 不错啊. |
-- 作者:reallyh -- 发布时间:12/7/2005 4:47:00 PM -- 研究中。。 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
62.500ms |