`
315634034
  • 浏览: 5156 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
最近访客 更多访客>>
社区版块
存档分类
最新评论

JavaEye上关于异常的讨论(转)

    博客分类:
  • java
 
阅读更多
JavaEye上关于异常的讨论
2010-10-02 03:06


异常很早就看到了,不过一直没有把上面的帖子整理下来。今天不小心又看到了,就整理一下吧:)

帖子:http://www.javaeye.com/post/9540 (为什么 Java 中要使用 Checked Exceptions )

jbaggio:

原文:http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html

作者:Gunjan Doshi 2003-11-19

译者注:本文算是一篇学习笔记,仅供学习参考使用,有不妥之处,还请指出。2003-12-04


“本文是Exception处理的一篇不错的文章,从Java Exception的概念介绍起,依次讲解了Exception的类型(Checked/Unchecked),Exception处理的最佳实现:

1. 选择Checked还是Unchecked的几个经典依据

2. Exception的封装问题

3. 如无必要不要创建自己得Exception

4. 不要用Exception来作流程控制

5. 不要轻易的忽略捕获的Exception

6. 不要简单地捕获顶层的Exception”

——选自JAVADigest.Net对原文的介绍

potian:

我想应该分两个问题来讨论:
1。什么时候抛出异常--涉及到服务类
2。抛出checked还是unchecked的异常--涉及到客户类

对第一个问题来说,我想异常本身这个字解释了某些东西,异常就是我们认为在正常情况下不可能发生的问题,并且服务代码不知道如何去处理。譬如说我做一个监控程序,需要用压缩卡提供的API去初始化所有的板卡,API提供的是boolean型的返回值,但我把这个API变成抛出一个异常,因为除非特殊原因,我不认为会发生初始化失败的情况,当然更不知道怎样去处理这个问题。又譬如Hibernate里面的LoadObject使用没有发现这个对象存在,那Hibernate也是认为不可能的,除非其他代码直接删除了数据库里面的记录,那么也需要抛出异常。当然Hibernate本身也不知道如何处理这种情况。
但是如果发生的情况是可以预期的,那我不认为应该抛出例外。象上面这个userExist的情况,我认为应该在前面已经分流,应该首先判断这个用户是否存在,if(userExists()),然后进行处理,而不应当抛出例外。以及login应当返回true或者false。也就是说,这些属于程序的正常流程,而不是例外,不是异常。把例外作为正常程序流程的控制机制,只不过是把服务代码中的if转移到客户代码去,没有减少任何需要处理的代码,反而增加了系统的负担(生成例外栈)。
还有抛出异常的情况是违反方法的先决条件,每一个方法都有自己的先决条件和后置条件,方法只有在正确的前提下才能执行达到一个正确的后果,(所谓类的不变量)。譬如你去存取一个数组的某一个元素,这个存取方法有一个前提条件,就是你的索引应当落入它的最大下标和最小下标之间,不然就应当抛出一个例外。

对于第二个问题,端视于客户代码是否能够根据这个例外进行合理的处理。如果客户代码根本就不知道如何处理这个例外,应当把它作为一个 unchecked例外,例如上面下标的问题,客户代码用一个不合法的下标来存取数组,那么抛出一个checked例外以后,客户代码是+1还是-1?显然根本就不可能做出“合理的”处理,客户既然不能处理,还要强制它去处理,那么就是捕获,打印了事,没有增加任何价值。但是如果是客户可以处理的,或者可以选择不同的方式处理的,那么就可能需要用checked,但我发现很少有这样的情况。对于类似于RemoteException或者 SQLException这些Exception,我一般都转换为具体的业务Exception,而我所有的业务Exception都是 RuntimeException.

所以我的观点是,是否抛出例外就是服务代码是否进行合理的处理,抛出什么类型的例外就是客户代码是否能够合理的处理。

当然,Exception应当是有层次的,我的Snip上面有一些说明。

robbin:

我没有资格评论大师们的观点,但是我知道绝大多数的Java程序员根本就没有领悟“Exception”的真正用处。他们就是把Exception当做异常来理解,没有明白Exception实际上代表了一个UseCase中的异常流的处理。

在使用UseCase来描述一个场景的时候,有一个主事件流和n个异常流。异常流可能发生在主事件流的过程,而try语句里面实现的是主事件流,而 catch里面实现的是异常流,在这里Exception不代表程序出现了异常或者错误,Exception只是面向对象化的业务逻辑控制方法。如果没有明白这一点,那么我认为并没有真正明白应该怎么使用Java来正确的编程。

而我自己写的程序,会自定义大量的Exception类,所有这些Exception类都不意味着程序出现了异常或者错误,只是代表非主事件流的发生的,用来进行那些分支流程的流程控制的。例如你往权限系统中增加一个用户,应该定义1个异常类,UserExistedException,抛出这个异常不代表你插入动作失败,只说明你碰到一个分支流程,留待后面的catch中来处理这个分支流程。传统的程序员会写一个if else来处理,而一个合格的OOP程序员应该有意识的使用try catch 方式来区分主事件流和n个分支流程的处理,通过try catch,而不是if else来从代码上把不同的事件流隔离开来进行分别的代码撰写。

总之 Exception <> 异常

BTW:我是支持BE和JG的观点的。我以前在接触C#的时候,就很奇怪C#的这一点了,Anders有他的理由,但是我认为一个良好的面向对象的软件是应该强制使用Exception的。
异常类层次设计的不好带来的结果就是非常糟糕,例如JTA的异常类层次,例如EJB的异常类层次,但是也有设计的很好的,例如Spring DataAccessException类层次结构。

用设计糟糕的异常类层次来否定异常这个事物,是极度缺乏说服力的,就好像有人用菜刀砍了人,你不能否定这把菜刀一样。

这个帖子这么长了,该讨论的问题都讨论清楚了,总结也总结过n遍了,所以我早就没有兴趣再跟帖了。

实际上,这个讨论中隐含两个不断纠缠的话题:

1、checked ,还是unchecked异常?
2、用自定义的方法调用返回code,还是用异常来表达不期望各种的事件流

经过这么长的讨论,我认为结论已经非常清楚:

1、应该适用unchecked异常,也就是runtimeexception,这样可以让无法处理异常的应用代码简单忽略它,让更上层次的代码来处理

2、应该适用异常来表达不期望的各种事件流

事实上,你们去看一下现在Spring,Hibernate3都已经采用这种方式,特别是Spring的DataAccessException异常层次设计,是一个很好的例子。即使用RuntimeException来表达不期望的各种事件流。

valley913:

何谓异常?
1、 程序正常控制流之外、不能完成函数语义的事件称为异常。
2、 何谓异常,判断的依据是服务的定义,导致不能完成服务的事件就是异常(虽然说从服务提供者与客户的角度上各有不同,但其实是双方对服务的内涵理解不一致,属于契约与交流问题)。
3、 因为不满足服务的所需条件而发生异常事件。这些条件或是参数的非法、或是内存不足、或是网络调用失败等等。

异常的使用:
1、 发生异常一定要抛出,既然是异常,必然是内部无法消化的事件,不可能也不应该在内部消化。
【Effective Java】不要忽略异常。
2、 抛出的异常要语义明确。这需要对异常进行语义转换与包装,尤其是在层与层之间的异常转换。其实,JDK中出现异常层次结构,主要目的就是使语义明确。
3、 服务者内部实现中,不能通过异常来控制流程;但在服务提供者与客户之间的合作来说,有时候,在客户方会有根据不同异常采取不同行为。但异常的根本目的是提供清晰的语义,而不是控制流程。

Checked Exception与UnChecked Exception:
1、 抛出Checked Exception,给直接客户施加一个约束,必须处理,但也是一种自由,客户可分门别类的处理不同异常;
UnChecked Exception则给直接客户以自由,但也是一种欺瞒,因为客户不知道将要发生什么,所有的处理将是系统默认的处理(如打印堆栈到控制台,对开发者、用户都返回一样的内容,不管别人懂与不懂)。
二者的选择其实是约束与自由的权衡。
2、 “对可恢复的情况使用已检查异常,对程序错误使用运行时异常。”而不是一咕脑的全抛出Checker Exception,这服务提供者是友好的、“敏捷的”^_^。
3、 所以,若不需要客户依据不同异常采取不同后续行为,那么抛出UnChecked Exception是友好的;但若客户需要根据不同异常类采取不同行动,抛出Checked Exception是友好的。

Rod Johnson:

一些异常本质上是次要的返回代码(它通常指示违反业务规则),而一些异常则是“发生某种可怕错误”(例如数据库连接失败)的变种。Johnson 提倡对于第一种类别的异常(可选的返回代码)使用检查型异常,而对于后者使用运行时异常。在“发生某种可怕错误”的类别中,其动机是简单地认识到没有调用者能够有效地处理该异常,因此它也可能以各种方式沿着栈向上扩散而对于中间代码的影响保持最小(并且最小化异常淹没的可能性)。

Josh Bloch:

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

第 40 条:为可恢复的条件使用检查型异常,为编程错误使用运行时异常。这里,Bloch 回应传统的 Sun 观点 —— 运行时异常应该只是用于指示编程错误,例如违反前置条件。

第 41 条:避免不必要的使用检查型异常。换句话说,对于调用者不可能从其中恢复的情形,或者惟一可以预见的响应将是程序退出,则不要使用检查型异常。

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

相关推荐

Global site tag (gtag.js) - Google Analytics