论坛首页 Java企业应用论坛

为什么 Java 中要使用 Checked Exceptions

浏览 192549 次
该帖已经被评为精华帖
作者 正文
   发表时间:2004-03-15  
notyy 写道
我觉得全部应该抛出checked异常.因为我写一个类的时候,我怎么知道这个类的用户有没有能力处理我抛出的异常.即使SQLException也不一定客户就不能处理,即使一个UserExistException也不一定客户就能够处理.
我只管抛出checked exception,至于他catch不catch,catch了怎么处理,那不是我应该关心的事情.
即使这个项目里我很清楚我这个类的客户是什么样的,我也无法预计我这个类将来会被不会被用在其他地方,所以应该把决定权交给客户

如果都抛出checked Exception,那使用你的类的客户岂不是很累,:)
我的观点:
抛出runtime Exception表明这是编程bug,是无法恢复的,比如IllegalArgumentException、IllegalStateException等等,这表明是使用你的类的程序员的编程错误,在程序未交付用户使用前必须改正的,所以让他catch到就没有什么意义了,不如中断程序打印到控制台来得直接,这是查找发现bug有力帮助。
抛出checked Exception则是属于天灾人祸,是必须恢复的,比如IOException,IllEmailException等等,这表明不是程序员的错,是一定要catch处理的,给用户良好的提示,保持工作或者是重试。
总之,在设计类时如果你认为产生错误的原因是天灾人祸就抛出checked Exception,如果是使用类的程序员的编程错误,就抛出runtime Exception。现在回头看看login是否应该抛出异常就很清楚了,UserExistedException属于人祸(输错用户名口令、黑客入侵),所以当然应该抛出checked Exception对用户进行提示了:再黑我小心你的脑袋!
0 请登录后投票
   发表时间:2004-03-15  
引用
需要捕捉的异常也有两种,一种是自己的程序抛出的,一种是系统抛出的。系统抛出的异常,没办法,必须一一处理好,但是自己的程序抛出的异常,不应该搞出复杂的异常继承体系出来


什么叫做程序抛出的异常,什么叫做系统抛出的异常,你能明确界定吗?FileNotFoundException你说算是系统异常呢?还是程序异常?站在某些程序员的角度,他会觉得是系统异常,不过像我喜欢看JDK源代码的人来说,我对Sun的程序什么情况下抛出FileNotFoundException很清楚,这些代码对我来说,和我自己写的代码能有什么不同吗?对我来说,FileNotFoundException就是程序异常。既然JDK可以抛出异常,凭什么我就不能抛出异常?

站在底层程序员的角度来看,根本没有什么系统异常可言,否则的话,还不如不要定义任何异常得了,干脆就是函数调用返回值,你说为什么Sun不定义0,1,2这样的返回值,而是抛出异常呢?Java程序无非就是一堆class,JDK的class可以抛异常,我写的class为什么不能抛出?

异常不异常的界定取决于你所关注的软件层面,例如你是应用软件开发人员,你关心的是业务流程,那么你就应该捕获底层异常,你就应该定义业务层异常,向上抛出业务层异常。如果是底层程序员,你就应该定义和抛出底层异常。要不要抛出异常和抛出什么异常取决你站在什么软件层面了,离开这个前提,空谈异常不异常是没有意义的。
1 请登录后投票
   发表时间:2004-03-15  
muziq 写道
Anders举的例子其实很可笑的,他说底层方法抛出4个异常,随着梯子越来越高,你的方法大概需要声明40个异常,其实他一定没有写过Java程序,更没有设计过Java方法,异常只需要对本层方法的调用者有意义,调用者根本就不需要关心底层错误的细节,那方法干吗还要声明那些底层抛的异常呢?一定是自己在方法里面消化掉了嘛!(需要调用者作为错误处理的异常合并抛一个异常)

checked Exception 总是要throw到控制层的,不管你是直接throw,还是转换后throw,还是组合一个新异常throw,因为checked Exception是天灾人祸,你必须捕捉到给用户一个交代。试想,作为一个优秀的楼房架构设计师,他必须考虑到各种异常(天灾人祸)并作出预防措施:雷击怎么办,发洪水怎么办,地震怎么办,飞机撞又怎么办,还包括对各种底层异常的处理,有白蚁了怎么办,某处发生火灾了怎么办,这是对用户生命的负责。同样,我们做程序架构设计的同样要给用户一个交代,login时口令错了怎么办,ip地址非法怎么办,用户在他处已登录怎么办,黑客重复提交又怎么办,这同样也是对用户的负责。
再来看看Anders举的例子,这是绝对可能的。如果你的login中用到的所有类throw了40个checked Exception ,那么你绝对应该把40个异常全部捕捉到,并一一给用户反馈,这是一个优秀架构师的责任。但是因为异常(广义的,天灾人祸)的处理是随业务的不同而不同的,不能写在异常类里,所以Anders必须得在控制层捕捉到所有的异常并作处理,如果他不用catch捕捉异常的话难道还用switch,呵呵,拭目以待吧。
0 请登录后投票
   发表时间: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继承体系。而这样的继承体系,除了名字的不同,基本上没有任何区别。
0 请登录后投票
   发表时间:2004-03-16  
pufan 写道
checked Exception 总是要throw到控制层的,不管你是直接throw,还是转换后throw,还是组合一个新异常throw,因为checked Exception是天灾人祸,你必须捕捉到给用户一个交代。试想,作为一个优秀的楼房架构设计师,他必须考虑到各种异常(天灾人祸)并作出预防措施:雷击怎么办,发洪水怎么办,地震怎么办,飞机撞又怎么办,还包括对各种底层异常的处理,有白蚁了怎么办,某处发生火灾了怎么办,这是对用户生命的负责。同样,我们做程序架构设计的同样要给用户一个交代,login时口令错了怎么办,ip地址非法怎么办,用户在他处已登录怎么办,黑客重复提交又怎么办,这同样也是对用户的负责。....

你大概忽略了系统的“层次”对于异常设计的影响,Robbin和我在前面都提到过这个问题:
robbin 写道
异常不异常的界定取决于你所关注的软件层面,例如你是应用软件开发人员,你关心的是业务流程,那么你就应该捕获底层异常,你就应该定义业务层异常,向上抛出业务层异常。如果是底层程序员,你就应该定义和抛出底层异常。要不要抛出异常和抛出什么异常取决你站在什么软件层面了,离开这个前提,空谈异常不异常是没有意义的。

另外,也建议你去参考一下《Effective Java》中关于“异常转义”的论述,个人认为那是非常精辟的见解,如果你手头找不到这本书的话我可以改天抄到这里。

庄表伟质疑了异常的继承,我也这么想过,异常之间的继承关系代表了什么语义呢?我认为异常继承的设计除了给API使用者添加理解的困难以外,并不会有什么积极的意义。不过,我不同意用一个Exception类取代所有异常类的说法,不用继承关系并不足以让我们摒弃异常类的设计,使用单独的类表示不同的异常可以使API的文档更加友好,更容易编写,捕捉异常的代码更易读。
0 请登录后投票
   发表时间:2004-03-16  
在anders看来,大多数程序员只需要最后提示一个错误对话框,然后停下来或者继续。我好像听谁说过,anders是语言大师,但是对企业应用却不是他的长项。看来确实如此,
0 请登录后投票
   发表时间: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。
0 请登录后投票
   发表时间:2004-03-16  
庄表伟 写道
8、如果我们关注异常的本质--(函数的非正常返回值)的话,那么我们就有理由相信,一个函数应该只返回一种异常,而且在它的子类的覆盖版本中,也不应该被改变。
9、进一步说,用一个不断继承的,没有任何实质上的功能代码的异常体系中,唯一有意义的,就是这个类的名称,我认为这是对面向对象概念的滥用。正确的用法是,一个系统中,只应该有一个Exception对象,这个对象应该设计为final。

异常类可以定义方法来返回与错误相关的信息(还是参见《Effective Java》),要知道面向对象不只有继承这一个特性。

我想引出一个问题,喜欢设计的朋友可以一起探讨一下:
无论是Server side system还是Desktop system,都需要在特定的情况下显示错误信息给用户看,那么你的系统如何显示错误信息呢?异常能起到什么作用?
0 请登录后投票
   发表时间:2004-03-16  
muziq 写道
异常类可以定义方法来返回与错误相关的信息(还是参见《Effective Java》),要知道面向对象不只有继承这一个特性。

我想引出一个问题,喜欢设计的朋友可以一起探讨一下:
无论是Server side system还是Desktop system,都需要在特定的情况下显示错误信息给用户看,那么你的系统如何显示错误信息呢?异常能起到什么作用?


一个异常类的确可以这样做,但是真的有人这么用了吗?

大多数人对于异常的使用,不过是因为可以有一个不同的异常类名。
有没有考虑过,为什么会出现这样的使用状况呢?

我认为Exception应该设计为final,就是因为目前的这个对象,已经基本上够用了。而且大多数人,也都认为够用了。
0 请登录后投票
   发表时间: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,尤其是循环里面的。

打个比方,你要省钱,最先要省的是不买五千块钱的皮鞋,八万块钱的领带,而不是每顿饭尽力省下五毛钱,对不对?
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics