- 浏览: 190076 次
- 性别:
- 来自: 上海
文章分类
最新评论
关于 Java 中引入的 Checked Exceptions,目前存在着很多反对意见。正方的观点是引入 Checked Exceptions,可以增加程度的鲁棒性。反方的观点是 Checked Exceptions 很少被开发人员正确使用过,并且降低了程序开发的生产率和代码的执行效率。
正方代表 James Gosling
http://www.artima.com/intv/solid.html
反方代表 Anders Hejlsberg
http://www.artima.com/intv/csdes.html
中文版:http://www.csdn.net/develop/article/22/22612.shtm
两位都是大师级的人物,观点的碰撞非常精彩。
我 个人更倾向于 James Gosling 的观点,即稳定性更加重要。C++ 程序的后期维护成本远远高于 Java 程序,更不要说前期的开发成本了。Java 要担负企业级的核心应用(HA 24X7),没有这些系统级的保证是不可能的。C++、Delphi 都中没有 Checked Exceptions,所以 Anders 设计 C# 时也没有加 Checked Exceptions。当然我并不是说没有 Checked Exceptions 就一定不能写出稳定的程序(你说,我就是比你牛,我用汇编语言都能写出稳定的程序!),但是显然使用 Java 语言写出稳定的程序比用 C++ 更容易。Checked Exceptions 就是编程语言所提供的系统级保证,能否用好是你自己的问题了,并不象 Anders 说的那样严重。我觉得 Anders 大师对于企业级应用并没有太多的概念。James Gosling 看了 C# 的设计后觉得不过如此。当然这只是大师本人的观点,我们未必一定要接受了。
robbin 发表时间:2003-12-17
我没有资格评论大师们的观点,但是我知道绝大多数的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的。
dlee 发表时间:2003-12-17
robbin 的话加深了我对于 Exception 的理解。我觉得 try...catch...finally 对于我是一个很好的约束,帮助我编写出更高质量的代码。好象我走路的拐杖一样,我在 Eclipse 中写程序也比在 UltraEdit 中写程序效率高得多,因为 Eclipse 对于我写的代码有更高的要求,UltraEdit 几乎没有任何要求。我很少直接使用 throws Exceptions 把所有 Exceptions 都甩出去的。你不知道程序员有多懒,如果不强制要求,我敢打赌他们 90% 的场合下是完全不用 Exception。
robbin 发表时间:2003-12-17
另外纠正一个错误的观点:很多人喜欢定义方法的返回类型为boolean型的,当方法正确执行,没有出错的时候返回true,而方法出现出现了问题,返回false。这在Java编程当中是大错而特错的!
方法的返回值只意味着当你的方法调用要返回业务逻辑的处理结果的。如果业务逻辑不带处理结果,那么就是void的,不要使用返回值boolean来代表方法是否正确执行。
例如 用户登陆方法
boolean login(String username, String password);;
很多人喜欢用boolean返回,如果是true,就是login了,如果false就是没有登陆上。其实是错误的。还有的人定义返回值为int型的,例如如果正确返回就是0,如果用户找不到就是-1,如果密码不对,就是-2
int login(String username, String password);;
然后在主程序里面写一个if else来判断不同的流程。
int logon = UserManager.login(xx,xx);;
if (logon ==0); {
...
} else if (logon == 1); {
...
} else if (logon ==2); {
..}
这是面向过程的编程逻辑,不是面向对象的编程逻辑。
应该这样来写:
User login(String username, String password); throws UserNotFoundException, PasswordNotMatchException;
主程序这样来写:
try {
UserManager.login(xx,xx);;
....
用户登陆以后的主事件流代码
} catch (UserNotFoundException e); {
...
用户名称没有的事件处理,例如产生一个提示用户注册的页面
} catch (PasswordNotMatchException e); {
....
密码不对的事件处理,例如forward到重新登陆的页面
}
potian 发表时间:2003-12-17
我想应该分两个问题来讨论:
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上面有一些说明。
dlee 发表时间:2003-12-17
我觉得 potian 说的很对。因为性能以及 Exception 设计目的上的原因,不应该用 Exception 来处理本来有可能发生的流程。Exception 就是用来处理真正的异常的。另外 Exception 用得过多对于性能有很大影响,所以要慎重使用。要把 Exception 设计作为整个 OOAD 设计中重要的一部分来考虑。
所以我们得到的结论不是 Java 中是否应该引入 Checked Exceptions 的问题,而是如何用好 Checked Exceptions 的问题。
以 前我还买过《Design by Contract 原则与实践》,里面讲的开发方法就更加怪了,可以说整个是一个基于断言的开发方法。好在 JDK 1.4 以后已经提供了 Assert,否则用 Java 是无法实现 DbC 的。目前直接支持这种开发方法的语言好象只有 Eiffel。书中的例子一半用 Eiffel,一半用 Java 的。
muziq 发表时间:2003-12-18
在用例里面,主事件流也被称为愉快的(Happy)流程,就是说这个过程使使用者和系统都感到愉快,皆大欢喜,它是需求分析时首先考虑 的内容,UML鼓励我们先集中精力解决主要问题,对不同的问题(主事件流、异常事件流)分别进行分析,各个击破。Java的Exception机制与 UML的思想是一致的。我说Exception不等于错误也是这个意思。在我们的系统中登录的接口就是会分别抛出用户不存在和密码无效的异常,这时业务逻 辑层需要考虑的事情,表示逻辑不需要分别捕捉、分别处理,有专门的错误页面去根据异常类显示不同的提示信息。
potian 发表时间:2003-12-19
无道 写道
我 认为函数抛不抛异常与函数本身的语义密切相关,在函数正常返回的情况下,函数完成了其即定的语义,在返回异常时,说明函数不能顺利地完成语义。因此 login函数当然应该有UserNotFoundException异常,因为login函数的语义就是用户登录,而不是看用户是否存 在;isUserExist当然就应当返回boolean,因为它就是用来检查用户是否存在的,而不需要抛出异常来表明。
这是对的,如果你写的login(user,password)返回一个UserDTO,那么就应该抛出例外,因为你这个方法预期将返回一个正常的User。(这适合于普通的登陆界面,直接把这个UserDTO放到Session里面去)
如果你的login是为了判断能不能成功,那就不是例外。这种情况也很多,譬如登陆以后需要根据目前的用户状态判断是否要改用户补充信息、确认等等。这时候往往第一步判断是否成功,后面根据不同的状态取得不同的用户信息
例外是处理异常的情况,和你类(服务类)的意图密切相关,同一种情况,在某些方法里面是例外,而在另外方法里面就不是例外了。
所以我不太喜欢第一种方式,因为第一种方式实际上在同一个方法中包含了两个责任,验证是否合法以及验证合法以后返回用户信息。这造成了 验证合法不能被细粒度重用(如我第二种情况,当我需要不同的用户信息时)。当然,在一个不需要进行多步认证的应用程序里面,这已经足够了,可以等到第二种 情况出现的时候才去重构。
muziq 发表时间:2003-12-24
重新看了一遍大家的讨论,我又回顾了一下自己目前正在做的这个项目,像是又有了一些新的领悟,请指正:
我们这里的人大多是做应用的,而不是做组件、框架的,这两类开发需要的思路可能不太一样。
从 做应用的角度看,我希望利用异常来管理显示在用户面前的错误提示,这样我就会将异常划分为两类,一类是可以显示给用户看的,或者说用户可以看的懂的,一类 是给维护人员看的,就是通常说的系统错误,可以想象,让用户看到“空指针异常”是极不友好的行为。我们的项目中有一个专门显示错误的页面,它要么显示有具 体业务含义的错误,要么就显示“系统错误”(然后维护人员可以从log中查找错误)。而前一类错误里面,每种错误总有一个不同的异常类和它对应,或者说, 业务逻辑方法向表示逻辑抛出的异常,表示逻辑不需要分别处理(不要显示错误的除外),一律交给错误页面(其实是ErrorPageUIAction,我们 用Struts),错误页面看是什么异常就显示什么错误提示,这里错误页面需要一个XML,XML里面定义了异常和错误提示的对应关系,当然XML里面只 能有用户异常,不在XML里面的异常错误页面就会显示“系统错误”。业务逻辑事先将用户异常登记到XML中,这样错误信息还可以单独、统一的维护。
上 面讲的是将异常展示到错误页面的方法,这其实只涉及到最后一层(Action)捕捉异常和业务逻辑层抛出异常的问题,而如何中间层内的异常如何抛、如何捕 捉的问题考虑的思路一定是不一样的,不过,做应用系统的,业务逻辑层内部交叉引用的不多,所以这一块异常设计的矛盾并不突出。而如果你正在开发高度重用的 组件甚至框架,异常就一定要好好设计一下了。
至于组件、框架的异常如何设计,我想只要去学JDK、Hibernate的那一套东西就没错了。能想到的就是,异常一定是调用者可以理解的,和方法的语义密切相关的。
最 后,回到最初的问题,Why Checked Exception?我们讨厌它的主要原因其实还是它太令人迷惑了,太多人虽然整天又抛又接的,其实根本就没弄明白这里面是怎么一回事,其实你只要用不了 一下午的时间冷静下来,看看例子稍微思考一下,就一定可以弄的很透彻。Anders举的例子其实很可笑的,他说底层方法抛出4个异常,随着梯子越来越高, 你的方法大概需要声明40个异常,其实他一定没有写过Java程序,更没有设计过Java方法,异常只需要对本层方法的调用者有意义,调用者根本就不需要 关心底层错误的细节,那方法干吗还要声明那些底层抛的异常呢?一定是自己在方法里面消化掉了嘛!(需要调用者作为错误处理的异常合并抛一个异常)
dlee 2003-12-24
muziq 看来是一个认真研究问题的人。Anders 确实有故意丑化 Java 的倾向。虽然我讨厌人身攻击,但是一些著名的大师也有掏糨糊的时候,毕竟都不是完人。所以我判断大师的标准就是看他在宣传自己的理论时是否有意在贬低别人 的理论,如果是这样那他就不是我心目中的大师了。
庄表伟 发表时间:2004-03-15
1、异常有两种,一种是源于程序员的疏忽,一种是源于不可控的外力。有些程序员将其混为一谈,然后统一的在catch里忽略掉这种异常,是对自己的“放纵”。
2、 需要捕捉的异常也有两种,一种是自己的程序抛出的,一种是系统抛出的。系统抛出的异常,没办法,必须一一处理好,但是自己的程序抛出的异常,不应该搞出复 杂的异常继承体系出来,只需在异常中填入统一格式的Error Code,然后所有的程序,都统一catch这种异常,然后一律交给某个错误处理对象。而不是在自己的程序里,散布着各种各样的catch。
3、我们要追求的是程序的功能,而不是纯粹意义上的美感。
robbin 发表时间:2004-03-15
引用
需要捕捉的异常也有两种,一种是自己的程序抛出的,一种是系统抛出的。系统抛出的异常,没办法,必须一一处理好,但是自己的程序抛出的异常,不应该搞出复杂的异常继承体系出来
什么叫做程序抛出的异常,什么叫做系统抛出的异常,你能明确界定吗?FileNotFoundException你说算是系统异常 呢?还是程序异常?站在某些程序员的角度,他会觉得是系统异常,不过像我喜欢看JDK源代码的人来说,我对Sun的程序什么情况下抛出 FileNotFoundException很清楚,这些代码对我来说,和我自己写的代码能有什么不同吗?对我来 说,FileNotFoundException就是程序异常。既然JDK可以抛出异常,凭什么我就不能抛出异常?
站在底层程序员的角 度来看,根本没有什么系统异常可言,否则的话,还不如不要定义任何异常得了,干脆就是函数调用返回值,你说为什么Sun不定义0,1,2这样的返回值,而 是抛出异常呢?Java程序无非就是一堆class,JDK的class可以抛异常,我写的class为什么不能抛出?
异常不异常的界 定取决于你所关注的软件层面,例如你是应用软件开发人员,你关心的是业务流程,那么你就应该捕获底层异常,你就应该定义业务层异常,向上抛出业务层异常。 如果是底层程序员,你就应该定义和抛出底层异常。要不要抛出异常和抛出什么异常取决你站在什么软件层面了,离开这个前提,空谈异常不异常是没有意义的。
庄表伟 发表时间:2004-03-15
robbin 写道
什么叫做程序抛出的异常,什么叫做系统抛出的异常,你能明确界定吗?
程序抛出的异常,就是我自己的程序里抛出的Exception。系统的异常,就是我的控制范围之外的,封装好了的异常。这些异常,我甚至完全不知道它为什么会抛出来,只知道我必须一一处理掉。
robbin 写道
像我喜欢看JDK源代码的人来说,我对Sun的程序什么情况下抛出FileNotFoundException很清楚,这些代码对我来说,和我自己写的代码能有什么不同吗?
高手当然可以看人家的源代码,但是我的工作,或者说面向对象的原理,要求我的,只是理解一个对象的接口。
人家的程序,我没有办法,只能老老实实的处理各种各样的异常,但是自己的程序,只要约定清晰,为什么不能使自己轻松一些呢?
robbin 写道
你说为什么Sun不定义0,1,2这样的返回值,而是抛出异常呢?Java程序无非就是一堆class,JDK的class可以抛异常,我写的class为什么不能抛出?
因为0,1,2这样的值表达的含义不够丰富,但是作为返回值,又不合理。
————函数有它的本身的返回值。
因此,返回一个异常,其实就是一个封装完好的,返回的对象。这个对象Type不是在函数名的前面说明,而是在一个更加特别的地方,函数的后面说明。这就是异常的本质————非正常的返回值。
这个返回值,为什么不能用传统的方法处理呢?因为Object x=method();表明它只能接受某一个特定的对象,如果出现Exception的对象,就会报错。因此需要catch来接手处理这样的返回值。
本质上来说,任何返回值,都没有对错之分,只不过是一个约定。在我看来,Exception("SQLException")与SQLException()没有什么实质上的区别。在实际的使用中,多用组合,少用继承,不是大师们谆谆告诫我们的吗?
因此我认为,SUN的JDK里的Exception的设计,并不合理,只应该有一个Exception对象,而不是搞出一堆让人眼花缭乱的Exception继承体系。而这样的继承体系,除了名字的不同,基本上没有任何区别。
muziq 发表时间:2004-03-16
pufan 写道
checked Exception 总是要throw到控制层的,不管你是直接throw,还是转换后throw,还是组合一个新异常throw,因为checked Exception是天灾人祸,你必须捕捉到给用户一个交代。试想,作为一个优秀的楼房架构设计师,他必须考虑到各种异常(天灾人祸)并作出预防措施:雷 击怎么办,发洪水怎么办,地震怎么办,飞机撞又怎么办,还包括对各种底层异常的处理,有白蚁了怎么办,某处发生火灾了怎么办,这是对用户生命的负责。同 样,我们做程序架构设计的同样要给用户一个交代,login时口令错了怎么办,ip地址非法怎么办,用户在他处已登录怎么办,黑客重复提交又怎么办,这同 样也是对用户的负责。....
你大概忽略了系统的“层次”对于异常设计的影响,Robbin和我在前面都提到过这个问题:
robbin 写道
异 常不异常的界定取决于你所关注的软件层面,例如你是应用软件开发人员,你关心的是业务流程,那么你就应该捕获底层异常,你就应该定义业务层异常,向上抛出 业务层异常。如果是底层程序员,你就应该定义和抛出底层异常。要不要抛出异常和抛出什么异常取决你站在什么软件层面了,离开这个前提,空谈异常不异常是没 有意义的。
另外,也建议你去参考一下《Effective Java》中关于“异常转义”的论述,个人认为那是非常精辟的见解,如果你手头找不到这本书的话我可以改天抄到这里。
庄 表伟质疑了异常的继承,我也这么想过,异常之间的继承关系代表了什么语义呢?我认为异常继承的设计除了给API使用者添加理解的困难以外,并不会有什么积 极的意义。不过,我不同意用一个Exception类取代所有异常类的说法,不用继承关系并不足以让我们摒弃异常类的设计,使用单独的类表示不同的异常可 以使API的文档更加友好,更容易编写,捕捉异常的代码更易读。
庄表伟 发表时间:2004-03-16
1、我们知道,在Java语言中,一个函数的返回值类型只能有一个。如果在一个Class中,接受相同参数列的同名函数,有不同的返回值,那么编译器会明确的报错。因为如果这个函数的用户,调用了这个函数名,但是不要求任何返回值,那么jvm将不知道调用哪一个函数。
2、如果一个类继承了另一个父类,想要覆写父类的函数,这个必须与父类函数的返回类型一模一样,既不能是父类函数返回值的子类,也不能是那个返回值的父类。因为无论采用哪一种返回值,都有可能给它的使用者,带来困扰。因此编译器同样很坚决的报告了编译错误。
3、异常的本质,就是一个函数的非正常返回值。因此需要在函数名的后面,用throws来申明,而且也不能用return返回一个Exception对象,而是用throw来返回。
4、我们不可能想象这样的语法:对一个函数的返回值的不同的可能对象,做类似的Case的处理,因为那样会非常困扰,但是事实上,我们却在对一个函数的不同的Exception对象,进行着catch处理。
5、 一个异常体系,可以想象成一棵树,一个函数后面的throws的说明,写出了n个异常,就不仅仅代表着这明确的n个异常,还代表着这n个异常的所有的子类 的一个集合。而在这个函数的(在子类中的)覆写版本来说,它的throws的异常集合,只需要小于父类函数的异常集合,就能够编译通过。
6、但是,我们的throw(XXXException e)子句,又应该是这样的次序:
try{
//
}catch(SubSubSub1Exception e){
//
}catch(SubSubSub2Exception e){
//
}catch(SubSubException e){
//
}catch(SubException e){
//
}catch(Exception e){
//
}
这样才能正确的,处理可能出现的异常,否则就有可能错过一个特定的异常,而对其只进行了大而化之的处理。
7、 按照对象的概念,父对象可以当然的涵盖子对象。但是对于异常来说,子异常并不能被当然的当成父异常,而是应该被明确的特殊对待,否则申明一个子异常的就丧 失了其本来的含义。于是我们就可以看见JAVA语言机制中存在的本质上的矛盾:新出现的子异常,可能不会被任何调用者发现(因为编译器不会报错,调用者也 能大而化之的处理),而程序的本意,却被扭曲了。
8、如果我们关注异常的本质--(函数的非正常返回值)的话,那么我们就有理由相信,一个函数应该只返回一种异常,而且在它的子类的覆盖版本中,也不应该被改变。
9、进一步说,用一个不断继承的,没有任何实质上的功能代码的异常体系中,唯一有意义的,就是这个类的名称,我认为这是对面向对象概念的滥用。正确的用法是,一个系统中,只应该有一个Exception对象,这个对象应该设计为final。
muziq 发表时间:2004-03-16
庄表伟 写道
8、如果我们关注异常的本质--(函数的非正常返回值)的话,那么我们就有理由相信,一个函数应该只返回一种异常,而且在它的子类的覆盖版本中,也不应该被改变。
9、进一步说,用一个不断继承的,没有任何实质上的功能代码的异常体系中,唯一有意义的,就是这个类的名称,我认为这是对面向对象概念的滥用。正确的用法是,一个系统中,只应该有一个Exception对象,这个对象应该设计为final。
异常类可以定义方法来返回与错误相关的信息(还是参见《Effective Java》),要知道面向对象不只有继承这一个特性。
我想引出一个问题,喜欢设计的朋友可以一起探讨一下:
无论是Server side system还是Desktop system,都需要在特定的情况下显示错误信息给用户看,那么你的系统如何显示错误信息呢?异常能起到什么作用?
muziq 发表时间:2004-03-16
muziq 写道
异常类可以定义方法来返回与错误相关的信息(还是参见《Effective Java》),要知道面向对象不只有继承这一个特性。
我想引出一个问题,喜欢设计的朋友可以一起探讨一下:
无论是Server side system还是Desktop system,都需要在特定的情况下显示错误信息给用户看,那么你的系统如何显示错误信息呢?异常能起到什么作用?
一个异常类的确可以这样做,但是真的有人这么用了吗?
大多数人对于异常的使用,不过是因为可以有一个不同的异常类名。
有没有考虑过,为什么会出现这样的使用状况呢?
我认为Exception应该设计为final,就是因为目前的这个对象,已经基本上够用了。而且大多数人,也都认为够用了。
xanada 发表时间:2004-03-26
robbin 写道
另外纠正一个错误的观点:很多人喜欢定义方法的返回类型为boolean型的,当方法正确执行,没有出错的时候返回true,而方法出现出现了问题,返回false。这在Java编程当中是大错而特错的!
其实这种方法也说不上错,使用Exception的关键是,你站在什么样的角度来看这个问题。
如果你是一个API Designer,那么定义方法返回值的做法反而是提倡的,因为如果你在你的API里面抛出太多Checked Exceptions的话,会让客户程序员感到很不爽,因为你抛一个他就得接一个,就得处理一个。所以还不如定义返回值让他自己去做判断或直接抛 Unchecked Exception。
如果你是一个应用程序员,就是说,你这一层已经基本上可以说是最后一层了,那么适当的使用Checked Exception可以很好的提高你的程序的可读性和可维护性。
那么,使用Checked Exception的原则是什么呢?用Bloch的话来说,就是
Use checked exceptions for recoverable conditions and run-time exceptions for programming errors
具体到Robbin讲的用户登陆的例子,登陆失败显然是recoverable的,不属于programming errors,所以我很同意Robbin的观点,即抛出一个LoginFailed Exception来处理这种情况。
至于Checked Exception的系统开销,不要担心,代码可读性和可维护性的提高带来的好处,是值得这些开销的。并且,你尽可以先优化其他不合理的地方,比如随处可见的new,尤其是循环里面的。
打个比方,你要省钱,最先要省的是不买五千块钱的皮鞋,八万块钱的领带,而不是每顿饭尽力省下五毛钱,对不对?
robbin 发表时间:2004-06-12
Java 理论与实践: 关于异常的争论
http://www-900.ibm.com/developerWorks/cn/java/j-jtp05254/index.shtml
http://www-900.ibm.com/developerWorks/cn/java/j-jtp05254/index_eng.shtml
引用
Rod Johnson 是 J2EE Design and Development (请参阅 参考资料) 的作者,这是我所读过的关于 Java 开发,J2EE 等方面的最好的书籍之一。他采取一个不太激进的方法。他列举了异常的多个类别,并且为每个类别确定一个策略。一些异常本质上是次要的返回代码(它通常指示 违反业务规则),而一些异常则是“发生某种可怕错误”(例如数据库连接失败)的变种。Johnson 提倡对于第一种类别的异常(可选的返回代码)使用检查型异常,而对于后者使用运行时异常。在“发生某种可怕错误”的类别中,其动机是简单地认识到没有调用 者能够有效地处理该异常,因此它也可能以各种方式沿着栈向上扩散而对于中间代码的影响保持最小(并且最小化异常淹没的可能性)。
引用
我已经发现非检查型异常的最大风险之一就是它并没有按照检查型异常采用的方式那样自我文档化。除非 API 的创建者明确地文档化将要抛出的异常,否则调用者没有办法知道在他们的代码中将要捕获的异常是什么。
引用
Johnson 建议在每个包的基础上选择检查型和非检查型异常。使用非检查型异常时还要记住,即使您并不捕获任何异常,也可能需要使用 try...finally 块,从而可以执行清除动作例如关闭数据库连接。对于检查型异常,我们有 try...catch 用来提示增加一个 finally 子句。对于非检查型异常,我们则没有这个支撑可以依靠。
robbin 发表时间:2004-08-26
使用Checked Exception还是UnChecked Exception的原则,我的看法是根据需求而定。
如果你希望强制你的类调用者来处理异常,那么就用Checked Exception;
如果你不希望强制你的类调用者来处理异常,就用UnChecked。
那么究竟强制还是不强制,权衡的依据在于从业务系统的逻辑规则来考虑,如果业务规则定义了调用者应该处理,那么就必须Checked,如果业务规则没有定义,就应该用UnChecked。
还是拿那个用户登陆的例子来说,可能产生的异常有:
IOException (例如读取配置文件找不到)
SQLException (例如连接数据库错误)
ClassNotFoundException(找不到数据库驱动类)
NoSuchUserException
PasswordNotMatchException
以上3个异常是和业务逻辑无关的系统容错异常,所以应该转换为RuntimeException,不强制类调用者来处理;而下面两个异常是和业务逻辑相关的流程,从业务实现的角度来说,类调用者必须处理,所以要Checked,强迫调用者去处理。
在这里将用户验证和密码验证转化为方法返回值是一个非常糟糕的设计,不但不能够有效的标示业务逻辑的各种流程,而且失去了强制类调用者去处理的安全保障。
至 于类调用者catch到NoSuchUserException和PasswordNotMatchException怎么处理,也要根据他自己具体的业 务逻辑了。或者他有能力也应该处理,就自己处理掉了;或者他不关心这个异常,也不希望上面的类调用者关心,就转化为RuntimeException;或 者他希望上面的类调用者处理,而不是自己处理,就转化为本层的异常继续往上抛出来。
dlee 发表时间:2004-09-09
《重构》这本书中关于正确使用 Exception 有两个重构方法:
310 页:Replace Error Code with Exception
315 页:Replace Exception with Test
当然,Martin Fowler 说的话并不是圣经,但是他的话是如此中肯以至于我很自然地会首先遵循他的指导。
robbin 发表时间:2005-05-31
异常类层次设计的不好带来的结果就是非常糟糕,例如JTA的异常类层次,例如EJB的异常类层次,但是也有设计的很好的,例如Spring DataAccessException类层次结构。
用设计糟糕的异常类层次来否定异常这个事物,是极度缺乏说服力的,就好像有人用菜刀砍了人,你不能否定这把菜刀一样。
这个帖子这么长了,该讨论的问题都讨论清楚了,总结也总结过n遍了,所以我早就没有兴趣再跟帖了。
实际上,这个讨论中隐含两个不断纠缠的话题:
1、checked ,还是unchecked异常?
2、用自定义的方法调用返回code,还是用异常来表达不期望各种的事件流
经过这么长的讨论,我认为结论已经非常清楚:
1、应该适用unchecked异常,也就是runtimeexception,这样可以让无法处理异常的应用代码简单忽略它,让更上层次的代码来处理
2、应该适用异常来表达不期望的各种事件流
事实上,你们去看一下现在Spring,Hibernate3都已经采用这种方式,特别是Spring的DataAccessException异常层次设计,是一个很好的例子。即使用RuntimeException来表达不期望的各种事件流。
正方代表 James Gosling
http://www.artima.com/intv/solid.html
反方代表 Anders Hejlsberg
http://www.artima.com/intv/csdes.html
中文版:http://www.csdn.net/develop/article/22/22612.shtm
两位都是大师级的人物,观点的碰撞非常精彩。
我 个人更倾向于 James Gosling 的观点,即稳定性更加重要。C++ 程序的后期维护成本远远高于 Java 程序,更不要说前期的开发成本了。Java 要担负企业级的核心应用(HA 24X7),没有这些系统级的保证是不可能的。C++、Delphi 都中没有 Checked Exceptions,所以 Anders 设计 C# 时也没有加 Checked Exceptions。当然我并不是说没有 Checked Exceptions 就一定不能写出稳定的程序(你说,我就是比你牛,我用汇编语言都能写出稳定的程序!),但是显然使用 Java 语言写出稳定的程序比用 C++ 更容易。Checked Exceptions 就是编程语言所提供的系统级保证,能否用好是你自己的问题了,并不象 Anders 说的那样严重。我觉得 Anders 大师对于企业级应用并没有太多的概念。James Gosling 看了 C# 的设计后觉得不过如此。当然这只是大师本人的观点,我们未必一定要接受了。
robbin 发表时间:2003-12-17
我没有资格评论大师们的观点,但是我知道绝大多数的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的。
dlee 发表时间:2003-12-17
robbin 的话加深了我对于 Exception 的理解。我觉得 try...catch...finally 对于我是一个很好的约束,帮助我编写出更高质量的代码。好象我走路的拐杖一样,我在 Eclipse 中写程序也比在 UltraEdit 中写程序效率高得多,因为 Eclipse 对于我写的代码有更高的要求,UltraEdit 几乎没有任何要求。我很少直接使用 throws Exceptions 把所有 Exceptions 都甩出去的。你不知道程序员有多懒,如果不强制要求,我敢打赌他们 90% 的场合下是完全不用 Exception。
robbin 发表时间:2003-12-17
另外纠正一个错误的观点:很多人喜欢定义方法的返回类型为boolean型的,当方法正确执行,没有出错的时候返回true,而方法出现出现了问题,返回false。这在Java编程当中是大错而特错的!
方法的返回值只意味着当你的方法调用要返回业务逻辑的处理结果的。如果业务逻辑不带处理结果,那么就是void的,不要使用返回值boolean来代表方法是否正确执行。
例如 用户登陆方法
boolean login(String username, String password);;
很多人喜欢用boolean返回,如果是true,就是login了,如果false就是没有登陆上。其实是错误的。还有的人定义返回值为int型的,例如如果正确返回就是0,如果用户找不到就是-1,如果密码不对,就是-2
int login(String username, String password);;
然后在主程序里面写一个if else来判断不同的流程。
int logon = UserManager.login(xx,xx);;
if (logon ==0); {
...
} else if (logon == 1); {
...
} else if (logon ==2); {
..}
这是面向过程的编程逻辑,不是面向对象的编程逻辑。
应该这样来写:
User login(String username, String password); throws UserNotFoundException, PasswordNotMatchException;
主程序这样来写:
try {
UserManager.login(xx,xx);;
....
用户登陆以后的主事件流代码
} catch (UserNotFoundException e); {
...
用户名称没有的事件处理,例如产生一个提示用户注册的页面
} catch (PasswordNotMatchException e); {
....
密码不对的事件处理,例如forward到重新登陆的页面
}
potian 发表时间:2003-12-17
我想应该分两个问题来讨论:
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上面有一些说明。
dlee 发表时间:2003-12-17
我觉得 potian 说的很对。因为性能以及 Exception 设计目的上的原因,不应该用 Exception 来处理本来有可能发生的流程。Exception 就是用来处理真正的异常的。另外 Exception 用得过多对于性能有很大影响,所以要慎重使用。要把 Exception 设计作为整个 OOAD 设计中重要的一部分来考虑。
所以我们得到的结论不是 Java 中是否应该引入 Checked Exceptions 的问题,而是如何用好 Checked Exceptions 的问题。
以 前我还买过《Design by Contract 原则与实践》,里面讲的开发方法就更加怪了,可以说整个是一个基于断言的开发方法。好在 JDK 1.4 以后已经提供了 Assert,否则用 Java 是无法实现 DbC 的。目前直接支持这种开发方法的语言好象只有 Eiffel。书中的例子一半用 Eiffel,一半用 Java 的。
muziq 发表时间:2003-12-18
在用例里面,主事件流也被称为愉快的(Happy)流程,就是说这个过程使使用者和系统都感到愉快,皆大欢喜,它是需求分析时首先考虑 的内容,UML鼓励我们先集中精力解决主要问题,对不同的问题(主事件流、异常事件流)分别进行分析,各个击破。Java的Exception机制与 UML的思想是一致的。我说Exception不等于错误也是这个意思。在我们的系统中登录的接口就是会分别抛出用户不存在和密码无效的异常,这时业务逻 辑层需要考虑的事情,表示逻辑不需要分别捕捉、分别处理,有专门的错误页面去根据异常类显示不同的提示信息。
potian 发表时间:2003-12-19
无道 写道
我 认为函数抛不抛异常与函数本身的语义密切相关,在函数正常返回的情况下,函数完成了其即定的语义,在返回异常时,说明函数不能顺利地完成语义。因此 login函数当然应该有UserNotFoundException异常,因为login函数的语义就是用户登录,而不是看用户是否存 在;isUserExist当然就应当返回boolean,因为它就是用来检查用户是否存在的,而不需要抛出异常来表明。
这是对的,如果你写的login(user,password)返回一个UserDTO,那么就应该抛出例外,因为你这个方法预期将返回一个正常的User。(这适合于普通的登陆界面,直接把这个UserDTO放到Session里面去)
如果你的login是为了判断能不能成功,那就不是例外。这种情况也很多,譬如登陆以后需要根据目前的用户状态判断是否要改用户补充信息、确认等等。这时候往往第一步判断是否成功,后面根据不同的状态取得不同的用户信息
例外是处理异常的情况,和你类(服务类)的意图密切相关,同一种情况,在某些方法里面是例外,而在另外方法里面就不是例外了。
所以我不太喜欢第一种方式,因为第一种方式实际上在同一个方法中包含了两个责任,验证是否合法以及验证合法以后返回用户信息。这造成了 验证合法不能被细粒度重用(如我第二种情况,当我需要不同的用户信息时)。当然,在一个不需要进行多步认证的应用程序里面,这已经足够了,可以等到第二种 情况出现的时候才去重构。
muziq 发表时间:2003-12-24
重新看了一遍大家的讨论,我又回顾了一下自己目前正在做的这个项目,像是又有了一些新的领悟,请指正:
我们这里的人大多是做应用的,而不是做组件、框架的,这两类开发需要的思路可能不太一样。
从 做应用的角度看,我希望利用异常来管理显示在用户面前的错误提示,这样我就会将异常划分为两类,一类是可以显示给用户看的,或者说用户可以看的懂的,一类 是给维护人员看的,就是通常说的系统错误,可以想象,让用户看到“空指针异常”是极不友好的行为。我们的项目中有一个专门显示错误的页面,它要么显示有具 体业务含义的错误,要么就显示“系统错误”(然后维护人员可以从log中查找错误)。而前一类错误里面,每种错误总有一个不同的异常类和它对应,或者说, 业务逻辑方法向表示逻辑抛出的异常,表示逻辑不需要分别处理(不要显示错误的除外),一律交给错误页面(其实是ErrorPageUIAction,我们 用Struts),错误页面看是什么异常就显示什么错误提示,这里错误页面需要一个XML,XML里面定义了异常和错误提示的对应关系,当然XML里面只 能有用户异常,不在XML里面的异常错误页面就会显示“系统错误”。业务逻辑事先将用户异常登记到XML中,这样错误信息还可以单独、统一的维护。
上 面讲的是将异常展示到错误页面的方法,这其实只涉及到最后一层(Action)捕捉异常和业务逻辑层抛出异常的问题,而如何中间层内的异常如何抛、如何捕 捉的问题考虑的思路一定是不一样的,不过,做应用系统的,业务逻辑层内部交叉引用的不多,所以这一块异常设计的矛盾并不突出。而如果你正在开发高度重用的 组件甚至框架,异常就一定要好好设计一下了。
至于组件、框架的异常如何设计,我想只要去学JDK、Hibernate的那一套东西就没错了。能想到的就是,异常一定是调用者可以理解的,和方法的语义密切相关的。
最 后,回到最初的问题,Why Checked Exception?我们讨厌它的主要原因其实还是它太令人迷惑了,太多人虽然整天又抛又接的,其实根本就没弄明白这里面是怎么一回事,其实你只要用不了 一下午的时间冷静下来,看看例子稍微思考一下,就一定可以弄的很透彻。Anders举的例子其实很可笑的,他说底层方法抛出4个异常,随着梯子越来越高, 你的方法大概需要声明40个异常,其实他一定没有写过Java程序,更没有设计过Java方法,异常只需要对本层方法的调用者有意义,调用者根本就不需要 关心底层错误的细节,那方法干吗还要声明那些底层抛的异常呢?一定是自己在方法里面消化掉了嘛!(需要调用者作为错误处理的异常合并抛一个异常)
dlee 2003-12-24
muziq 看来是一个认真研究问题的人。Anders 确实有故意丑化 Java 的倾向。虽然我讨厌人身攻击,但是一些著名的大师也有掏糨糊的时候,毕竟都不是完人。所以我判断大师的标准就是看他在宣传自己的理论时是否有意在贬低别人 的理论,如果是这样那他就不是我心目中的大师了。
庄表伟 发表时间:2004-03-15
1、异常有两种,一种是源于程序员的疏忽,一种是源于不可控的外力。有些程序员将其混为一谈,然后统一的在catch里忽略掉这种异常,是对自己的“放纵”。
2、 需要捕捉的异常也有两种,一种是自己的程序抛出的,一种是系统抛出的。系统抛出的异常,没办法,必须一一处理好,但是自己的程序抛出的异常,不应该搞出复 杂的异常继承体系出来,只需在异常中填入统一格式的Error Code,然后所有的程序,都统一catch这种异常,然后一律交给某个错误处理对象。而不是在自己的程序里,散布着各种各样的catch。
3、我们要追求的是程序的功能,而不是纯粹意义上的美感。
robbin 发表时间:2004-03-15
引用
需要捕捉的异常也有两种,一种是自己的程序抛出的,一种是系统抛出的。系统抛出的异常,没办法,必须一一处理好,但是自己的程序抛出的异常,不应该搞出复杂的异常继承体系出来
什么叫做程序抛出的异常,什么叫做系统抛出的异常,你能明确界定吗?FileNotFoundException你说算是系统异常 呢?还是程序异常?站在某些程序员的角度,他会觉得是系统异常,不过像我喜欢看JDK源代码的人来说,我对Sun的程序什么情况下抛出 FileNotFoundException很清楚,这些代码对我来说,和我自己写的代码能有什么不同吗?对我来 说,FileNotFoundException就是程序异常。既然JDK可以抛出异常,凭什么我就不能抛出异常?
站在底层程序员的角 度来看,根本没有什么系统异常可言,否则的话,还不如不要定义任何异常得了,干脆就是函数调用返回值,你说为什么Sun不定义0,1,2这样的返回值,而 是抛出异常呢?Java程序无非就是一堆class,JDK的class可以抛异常,我写的class为什么不能抛出?
异常不异常的界 定取决于你所关注的软件层面,例如你是应用软件开发人员,你关心的是业务流程,那么你就应该捕获底层异常,你就应该定义业务层异常,向上抛出业务层异常。 如果是底层程序员,你就应该定义和抛出底层异常。要不要抛出异常和抛出什么异常取决你站在什么软件层面了,离开这个前提,空谈异常不异常是没有意义的。
庄表伟 发表时间:2004-03-15
robbin 写道
什么叫做程序抛出的异常,什么叫做系统抛出的异常,你能明确界定吗?
程序抛出的异常,就是我自己的程序里抛出的Exception。系统的异常,就是我的控制范围之外的,封装好了的异常。这些异常,我甚至完全不知道它为什么会抛出来,只知道我必须一一处理掉。
robbin 写道
像我喜欢看JDK源代码的人来说,我对Sun的程序什么情况下抛出FileNotFoundException很清楚,这些代码对我来说,和我自己写的代码能有什么不同吗?
高手当然可以看人家的源代码,但是我的工作,或者说面向对象的原理,要求我的,只是理解一个对象的接口。
人家的程序,我没有办法,只能老老实实的处理各种各样的异常,但是自己的程序,只要约定清晰,为什么不能使自己轻松一些呢?
robbin 写道
你说为什么Sun不定义0,1,2这样的返回值,而是抛出异常呢?Java程序无非就是一堆class,JDK的class可以抛异常,我写的class为什么不能抛出?
因为0,1,2这样的值表达的含义不够丰富,但是作为返回值,又不合理。
————函数有它的本身的返回值。
因此,返回一个异常,其实就是一个封装完好的,返回的对象。这个对象Type不是在函数名的前面说明,而是在一个更加特别的地方,函数的后面说明。这就是异常的本质————非正常的返回值。
这个返回值,为什么不能用传统的方法处理呢?因为Object x=method();表明它只能接受某一个特定的对象,如果出现Exception的对象,就会报错。因此需要catch来接手处理这样的返回值。
本质上来说,任何返回值,都没有对错之分,只不过是一个约定。在我看来,Exception("SQLException")与SQLException()没有什么实质上的区别。在实际的使用中,多用组合,少用继承,不是大师们谆谆告诫我们的吗?
因此我认为,SUN的JDK里的Exception的设计,并不合理,只应该有一个Exception对象,而不是搞出一堆让人眼花缭乱的Exception继承体系。而这样的继承体系,除了名字的不同,基本上没有任何区别。
muziq 发表时间:2004-03-16
pufan 写道
checked Exception 总是要throw到控制层的,不管你是直接throw,还是转换后throw,还是组合一个新异常throw,因为checked Exception是天灾人祸,你必须捕捉到给用户一个交代。试想,作为一个优秀的楼房架构设计师,他必须考虑到各种异常(天灾人祸)并作出预防措施:雷 击怎么办,发洪水怎么办,地震怎么办,飞机撞又怎么办,还包括对各种底层异常的处理,有白蚁了怎么办,某处发生火灾了怎么办,这是对用户生命的负责。同 样,我们做程序架构设计的同样要给用户一个交代,login时口令错了怎么办,ip地址非法怎么办,用户在他处已登录怎么办,黑客重复提交又怎么办,这同 样也是对用户的负责。....
你大概忽略了系统的“层次”对于异常设计的影响,Robbin和我在前面都提到过这个问题:
robbin 写道
异 常不异常的界定取决于你所关注的软件层面,例如你是应用软件开发人员,你关心的是业务流程,那么你就应该捕获底层异常,你就应该定义业务层异常,向上抛出 业务层异常。如果是底层程序员,你就应该定义和抛出底层异常。要不要抛出异常和抛出什么异常取决你站在什么软件层面了,离开这个前提,空谈异常不异常是没 有意义的。
另外,也建议你去参考一下《Effective Java》中关于“异常转义”的论述,个人认为那是非常精辟的见解,如果你手头找不到这本书的话我可以改天抄到这里。
庄 表伟质疑了异常的继承,我也这么想过,异常之间的继承关系代表了什么语义呢?我认为异常继承的设计除了给API使用者添加理解的困难以外,并不会有什么积 极的意义。不过,我不同意用一个Exception类取代所有异常类的说法,不用继承关系并不足以让我们摒弃异常类的设计,使用单独的类表示不同的异常可 以使API的文档更加友好,更容易编写,捕捉异常的代码更易读。
庄表伟 发表时间:2004-03-16
1、我们知道,在Java语言中,一个函数的返回值类型只能有一个。如果在一个Class中,接受相同参数列的同名函数,有不同的返回值,那么编译器会明确的报错。因为如果这个函数的用户,调用了这个函数名,但是不要求任何返回值,那么jvm将不知道调用哪一个函数。
2、如果一个类继承了另一个父类,想要覆写父类的函数,这个必须与父类函数的返回类型一模一样,既不能是父类函数返回值的子类,也不能是那个返回值的父类。因为无论采用哪一种返回值,都有可能给它的使用者,带来困扰。因此编译器同样很坚决的报告了编译错误。
3、异常的本质,就是一个函数的非正常返回值。因此需要在函数名的后面,用throws来申明,而且也不能用return返回一个Exception对象,而是用throw来返回。
4、我们不可能想象这样的语法:对一个函数的返回值的不同的可能对象,做类似的Case的处理,因为那样会非常困扰,但是事实上,我们却在对一个函数的不同的Exception对象,进行着catch处理。
5、 一个异常体系,可以想象成一棵树,一个函数后面的throws的说明,写出了n个异常,就不仅仅代表着这明确的n个异常,还代表着这n个异常的所有的子类 的一个集合。而在这个函数的(在子类中的)覆写版本来说,它的throws的异常集合,只需要小于父类函数的异常集合,就能够编译通过。
6、但是,我们的throw(XXXException e)子句,又应该是这样的次序:
try{
//
}catch(SubSubSub1Exception e){
//
}catch(SubSubSub2Exception e){
//
}catch(SubSubException e){
//
}catch(SubException e){
//
}catch(Exception e){
//
}
这样才能正确的,处理可能出现的异常,否则就有可能错过一个特定的异常,而对其只进行了大而化之的处理。
7、 按照对象的概念,父对象可以当然的涵盖子对象。但是对于异常来说,子异常并不能被当然的当成父异常,而是应该被明确的特殊对待,否则申明一个子异常的就丧 失了其本来的含义。于是我们就可以看见JAVA语言机制中存在的本质上的矛盾:新出现的子异常,可能不会被任何调用者发现(因为编译器不会报错,调用者也 能大而化之的处理),而程序的本意,却被扭曲了。
8、如果我们关注异常的本质--(函数的非正常返回值)的话,那么我们就有理由相信,一个函数应该只返回一种异常,而且在它的子类的覆盖版本中,也不应该被改变。
9、进一步说,用一个不断继承的,没有任何实质上的功能代码的异常体系中,唯一有意义的,就是这个类的名称,我认为这是对面向对象概念的滥用。正确的用法是,一个系统中,只应该有一个Exception对象,这个对象应该设计为final。
muziq 发表时间:2004-03-16
庄表伟 写道
8、如果我们关注异常的本质--(函数的非正常返回值)的话,那么我们就有理由相信,一个函数应该只返回一种异常,而且在它的子类的覆盖版本中,也不应该被改变。
9、进一步说,用一个不断继承的,没有任何实质上的功能代码的异常体系中,唯一有意义的,就是这个类的名称,我认为这是对面向对象概念的滥用。正确的用法是,一个系统中,只应该有一个Exception对象,这个对象应该设计为final。
异常类可以定义方法来返回与错误相关的信息(还是参见《Effective Java》),要知道面向对象不只有继承这一个特性。
我想引出一个问题,喜欢设计的朋友可以一起探讨一下:
无论是Server side system还是Desktop system,都需要在特定的情况下显示错误信息给用户看,那么你的系统如何显示错误信息呢?异常能起到什么作用?
muziq 发表时间:2004-03-16
muziq 写道
异常类可以定义方法来返回与错误相关的信息(还是参见《Effective Java》),要知道面向对象不只有继承这一个特性。
我想引出一个问题,喜欢设计的朋友可以一起探讨一下:
无论是Server side system还是Desktop system,都需要在特定的情况下显示错误信息给用户看,那么你的系统如何显示错误信息呢?异常能起到什么作用?
一个异常类的确可以这样做,但是真的有人这么用了吗?
大多数人对于异常的使用,不过是因为可以有一个不同的异常类名。
有没有考虑过,为什么会出现这样的使用状况呢?
我认为Exception应该设计为final,就是因为目前的这个对象,已经基本上够用了。而且大多数人,也都认为够用了。
xanada 发表时间:2004-03-26
robbin 写道
另外纠正一个错误的观点:很多人喜欢定义方法的返回类型为boolean型的,当方法正确执行,没有出错的时候返回true,而方法出现出现了问题,返回false。这在Java编程当中是大错而特错的!
其实这种方法也说不上错,使用Exception的关键是,你站在什么样的角度来看这个问题。
如果你是一个API Designer,那么定义方法返回值的做法反而是提倡的,因为如果你在你的API里面抛出太多Checked Exceptions的话,会让客户程序员感到很不爽,因为你抛一个他就得接一个,就得处理一个。所以还不如定义返回值让他自己去做判断或直接抛 Unchecked Exception。
如果你是一个应用程序员,就是说,你这一层已经基本上可以说是最后一层了,那么适当的使用Checked Exception可以很好的提高你的程序的可读性和可维护性。
那么,使用Checked Exception的原则是什么呢?用Bloch的话来说,就是
Use checked exceptions for recoverable conditions and run-time exceptions for programming errors
具体到Robbin讲的用户登陆的例子,登陆失败显然是recoverable的,不属于programming errors,所以我很同意Robbin的观点,即抛出一个LoginFailed Exception来处理这种情况。
至于Checked Exception的系统开销,不要担心,代码可读性和可维护性的提高带来的好处,是值得这些开销的。并且,你尽可以先优化其他不合理的地方,比如随处可见的new,尤其是循环里面的。
打个比方,你要省钱,最先要省的是不买五千块钱的皮鞋,八万块钱的领带,而不是每顿饭尽力省下五毛钱,对不对?
robbin 发表时间:2004-06-12
Java 理论与实践: 关于异常的争论
http://www-900.ibm.com/developerWorks/cn/java/j-jtp05254/index.shtml
http://www-900.ibm.com/developerWorks/cn/java/j-jtp05254/index_eng.shtml
引用
Rod Johnson 是 J2EE Design and Development (请参阅 参考资料) 的作者,这是我所读过的关于 Java 开发,J2EE 等方面的最好的书籍之一。他采取一个不太激进的方法。他列举了异常的多个类别,并且为每个类别确定一个策略。一些异常本质上是次要的返回代码(它通常指示 违反业务规则),而一些异常则是“发生某种可怕错误”(例如数据库连接失败)的变种。Johnson 提倡对于第一种类别的异常(可选的返回代码)使用检查型异常,而对于后者使用运行时异常。在“发生某种可怕错误”的类别中,其动机是简单地认识到没有调用 者能够有效地处理该异常,因此它也可能以各种方式沿着栈向上扩散而对于中间代码的影响保持最小(并且最小化异常淹没的可能性)。
引用
我已经发现非检查型异常的最大风险之一就是它并没有按照检查型异常采用的方式那样自我文档化。除非 API 的创建者明确地文档化将要抛出的异常,否则调用者没有办法知道在他们的代码中将要捕获的异常是什么。
引用
Johnson 建议在每个包的基础上选择检查型和非检查型异常。使用非检查型异常时还要记住,即使您并不捕获任何异常,也可能需要使用 try...finally 块,从而可以执行清除动作例如关闭数据库连接。对于检查型异常,我们有 try...catch 用来提示增加一个 finally 子句。对于非检查型异常,我们则没有这个支撑可以依靠。
robbin 发表时间:2004-08-26
使用Checked Exception还是UnChecked Exception的原则,我的看法是根据需求而定。
如果你希望强制你的类调用者来处理异常,那么就用Checked Exception;
如果你不希望强制你的类调用者来处理异常,就用UnChecked。
那么究竟强制还是不强制,权衡的依据在于从业务系统的逻辑规则来考虑,如果业务规则定义了调用者应该处理,那么就必须Checked,如果业务规则没有定义,就应该用UnChecked。
还是拿那个用户登陆的例子来说,可能产生的异常有:
IOException (例如读取配置文件找不到)
SQLException (例如连接数据库错误)
ClassNotFoundException(找不到数据库驱动类)
NoSuchUserException
PasswordNotMatchException
以上3个异常是和业务逻辑无关的系统容错异常,所以应该转换为RuntimeException,不强制类调用者来处理;而下面两个异常是和业务逻辑相关的流程,从业务实现的角度来说,类调用者必须处理,所以要Checked,强迫调用者去处理。
在这里将用户验证和密码验证转化为方法返回值是一个非常糟糕的设计,不但不能够有效的标示业务逻辑的各种流程,而且失去了强制类调用者去处理的安全保障。
至 于类调用者catch到NoSuchUserException和PasswordNotMatchException怎么处理,也要根据他自己具体的业 务逻辑了。或者他有能力也应该处理,就自己处理掉了;或者他不关心这个异常,也不希望上面的类调用者关心,就转化为RuntimeException;或 者他希望上面的类调用者处理,而不是自己处理,就转化为本层的异常继续往上抛出来。
dlee 发表时间:2004-09-09
《重构》这本书中关于正确使用 Exception 有两个重构方法:
310 页:Replace Error Code with Exception
315 页:Replace Exception with Test
当然,Martin Fowler 说的话并不是圣经,但是他的话是如此中肯以至于我很自然地会首先遵循他的指导。
robbin 发表时间:2005-05-31
异常类层次设计的不好带来的结果就是非常糟糕,例如JTA的异常类层次,例如EJB的异常类层次,但是也有设计的很好的,例如Spring DataAccessException类层次结构。
用设计糟糕的异常类层次来否定异常这个事物,是极度缺乏说服力的,就好像有人用菜刀砍了人,你不能否定这把菜刀一样。
这个帖子这么长了,该讨论的问题都讨论清楚了,总结也总结过n遍了,所以我早就没有兴趣再跟帖了。
实际上,这个讨论中隐含两个不断纠缠的话题:
1、checked ,还是unchecked异常?
2、用自定义的方法调用返回code,还是用异常来表达不期望各种的事件流
经过这么长的讨论,我认为结论已经非常清楚:
1、应该适用unchecked异常,也就是runtimeexception,这样可以让无法处理异常的应用代码简单忽略它,让更上层次的代码来处理
2、应该适用异常来表达不期望的各种事件流
事实上,你们去看一下现在Spring,Hibernate3都已经采用这种方式,特别是Spring的DataAccessException异常层次设计,是一个很好的例子。即使用RuntimeException来表达不期望的各种事件流。
发表评论
文章已被作者锁定,不允许评论。
-
ReentrantLock与Condition
2017-03-17 14:25 532多线程和并发性并不是什么新内容,但是 Java 语言设计中的创 ... -
java linux监控
2017-03-13 17:49 502http://agapple.iteye.com/blog/1 ... -
transient和volatile两个关键字
2017-02-16 09:47 575transient和volatile两个关 ... -
java 锁机制
2016-12-09 13:43 480一段synchronized的代码被 ... -
java 正则表达式
2016-12-02 10:28 522众所周知,在程序开发中,难免会遇到需要匹配、查找、替换、判断字 ... -
java ClassNotFoundException和NoClassDefFoundException的差别
2016-08-17 19:47 912首先从名字上可以看出一类是异常,一类属于错误。异常可以通过异常 ... -
ThreadLocal
2016-07-19 11:10 335ThreadLocal是什么 Thre ... -
java CAS
2016-07-10 14:55 347cas 乐观锁每次不锁定整个线程,在操作之前进行判断。悲观锁独 ... -
concurrenthashmap
2016-07-10 11:11 425hash table虽然性能上不如 ... -
java 线程池的使用
2016-07-10 09:52 3761. 引言 合理利用线程池能够带来三个好处。第一:降低资源消 ... -
java.util.concurrent
2016-07-03 16:24 412我们都知道,在JDK1.5之 ... -
JVM 配置 以及垃圾收集器的选择
2016-04-15 12:36 734JVM监控的关键指标说明: a) FGC的环比增加次数。Zab ... -
jvm实时监控工具
2016-04-09 09:35 469 -
哈希 、一致性哈希、余数式哈希
2016-04-07 16:10 869什么是Hash Hash,一 ... -
jvm dump 相关
2016-03-22 17:22 684http://www.cnblogs.com/edwardla ... -
深入剖析volatile关键字
2016-03-21 16:02 551深入剖析volatile关键字 ... -
java线程安全问题之静态变量、实例变量、局部变量
2016-03-08 12:52 578java多线程编程中,存在很多线程安全问题,至于什么是线程安全 ... -
有状态的bean和无状态的bean的区别
2016-03-08 11:23 1526有状态会话bean :每个用户有自己特有的一个实例,在用户的生 ... -
Java nio详解
2016-01-20 16:30 557http://www.ibm.com/developerwor ... -
java 不定长数组
2015-11-24 15:00 792在调用某个方法时,若是方法的参数个数事先无法确定该如何处理 ...
相关推荐
"关于异常的作业"这一主题,通常涉及到如何有效地捕获、处理和预防程序运行时可能出现的错误情况。在这个作业中,我们将深入探讨异常的概念、异常处理机制以及在不同编程语言中的实现方式。 异常是在程序执行过程中...
关于异常的使用心得_1
在机器学习领域,异常检测是一种重要的技术,常用于识别数据中的异常值或离群点。这个数据集专门针对异常检测,对于理解机器学习算法在处理此类问题时的应用具有很高的价值。异常检测通常应用于金融欺诈检测、网络...
通过这个"关于Java异常的练习",你可以实践如何有效地处理异常,理解何时使用不同的关键字,以及如何编写符合最佳实践的异常处理代码。通过对`demo9`等示例代码的分析和修改,加深对Java异常处理机制的理解,提升...
异常通常通过异常类的对象来表示,这些对象包含了关于异常的详细信息。例如,数组下标越界、内存不足、磁盘访问错误或网络访问错误等都可以引发异常。当异常发生时,程序会创建一个异常对象并将其传递给Java运行时...
EXCEPTION_RECORD结构包含了关于异常的所有信息,包括异常代码、异常地址等;而CONTEXT结构则保存了处理器的状态信息,如寄存器的值等。在处理线程异常时,这两个结构通常会被用来获取和分析异常发生时的详细情况,...
Java中的异常处理是编程中非常重要的一个环节,它允许开发者优雅地处理程序中出现的错误,防止程序因未预期的问题而崩溃。异常是程序在运行时遇到的错误,比如除0溢出、数组越界、文件找不到等。Java提供了一整套...
在Java编程语言中,异常处理是一项至关重要的技能,尤其对于初学者来说,理解并熟练掌握异常处理机制是构建健壮程序的基础。Java异常处理的主要目的是中断正常的代码流程,处理程序运行时可能出现的问题,如文件未...
最后,关于异常的继承,Java规定如果子类方法覆盖了父类的方法,那么它可以声明抛出父类方法所声明的异常的子类,但不允许声明抛出比父类方法声明的异常更严格的异常。如果父类方法没有声明抛出任何异常,则子类方法...
代码用于测试c++在文件处理部分的异常操作,同时也对用户输入数据的时候就行了判断,在用户输入性别的时候进行了判断,由于时间问题就没有更多的进行判断。在这里使用到了类的相关功能,同时也做到了保证每次写入...
另一个是描述符,包含了关于异常的一些额外信息,如优先级、处理权限等。 4. **编程接口**:在编程中,我们需要使用特定的API或汇编指令来设置和修改异常向量表。例如,在x86系统中,可以使用`lidt`指令加载中断...
5. **异常链**:Java允许创建异常链,这样就可以保留关于异常起源的完整信息。一个异常可以引用导致它的另一个异常。 6. **多catch块**:Java 7引入了多catch语句,允许在一个catch块中处理多种类型的异常,提高了...
在编程世界中,异常处理是一种重要的错误处理机制,它允许程序在出现异常情况时优雅地进行恢复,而不是突然崩溃。C语言和C++都提供了异常处理功能,但它们的实现方式和理念有所不同。 C语言的异常处理主要是通过...
一些研究者,如Aggarwal等人,提供了广泛的关于异常检测技术的概述。这些技术包括但不限于基于统计的方法、机器学习方法、聚类方法、密度方法和基于邻近度的方法等。 综上所述,时序数据异常检测是一个跨学科的领域...
在Java编程中,异常处理是一项至关重要的技术,它确保了程序在遇到错误或异常情况时仍能保持稳定性和可靠性。异常是在程序编译或运行时出现的错误,这些错误可能会中断程序的正常流程。Java提供了丰富的异常处理机制...
异常确定系统是信息技术领域中的一个重要概念,主要用于检测和识别数据流、系统行为或网络通信中的不寻常模式。这种系统能够帮助我们发现潜在的问题,比如网络安全威胁、设备故障、欺诈行为或者业务过程中的异常情况...
自定义异常通常继承自`Exception`类或其子类,并且可以覆盖构造方法来传递更多关于异常的信息。 #### 六、异常处理示例 下面通过一个简单的示例来展示如何使用异常处理机制: ```java public static void main...
我们描述了复合希格斯模型的异常结构,其中最小模型的SO(5)/ SO(4)陪集结构由一个额外的,非线性实现的U(1)α扩展。 另外,我们表明有效的拉格朗日方程式允许一个术语,如QCD的手性拉格朗日方程中的Wess–“ ...
异常信号隐蔽性高,分析难度大,使得无数工程师都败倒在她的石榴裙下,但因其在信号的分析与调试过程中影响很大,工程师们不得不屡败屡战,一路坎坷前行。本文将结合实例进行分析,分享了一种新颖而实用的异常信号...
- **编码规范**: 关于异常处理的编码规范。 #### 17. C++异常和Visual C++ SEH的混合使用 讨论了如何在C++程序中混合使用标准异常处理和SEH。 - **混合使用**: 如何在C++程序中同时使用C++异常处理和SEH。 - **...