随笔-204  评论-149  文章-0  trackbacks-0

唉,今天ibm面试看来是被鄙视了,问及异常不知道是要问CheckedException和UncheckedException
thinking in java看的早忘了。
看了好几篇帖子,还是理解不够深刻
http://www.javaeye.com/topic/2038       主题:为什么 Java 中要使用 Checked Exceptions
http://www.javaeye.com/topic/14082     主题:我对Checked Exceptions的看法 (续为什么java引入Checked Ex...
http://www.javaeye.com/topic/10482     主题:我对Checked Exception观点的变化
http://www.javaeye.com/topic/72170     主题:J2EE项目异常处理
http://www.ibm.com/developerworks/cn/java/j-jtp05254/index.html

http://www.onjava.com/pub/a/onjava/2006/01/11/exception-handling-framework-for-j2ee.html?page=6&x-order=date



http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html?page=2

本文是Exception处理的一篇不错的文章,从Java Exception的概念介绍起,依次讲解了Exception的类型(Checked/Unchecked),Exception处理的最佳实现:
1. 选择Checked还是Unchecked的几个经典依据
2. Exception的封装问题
3. 如无必要不要创建自己的Exception
4. 不要用Exception来作流程控制
5. 不要轻易的忽略捕获的Exception
6. 不要简单地捕获顶层的Exception
原文地址:
http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html


关于异常处理的一个问题就是要对何时(when)和如何(how)使用它们做到了然于心。在本文中我将介绍一些关于异常处理的最佳实践,同时我也会涉及到最近争论十分激烈的checked Exception的使用问题。

作为开发员,我们都希望能写出解决问题并且是高质量的代码。不幸的是,一些副作用(side effects)伴随着异常在我们的代码中慢慢滋生。无庸置疑,没有人喜欢副作用(side effects),所以我们很快就用我们自己的方式来避免它,我曾经看到一些聪明的程序员用下面的方式来处理异常:

public void consumeAndForgetAllExceptions(){
try {
...some code that throws exceptions
} catch (Exception ex){
ex.printStacktrace();
}
}

上边的代码有什么问题么?

在回答以前让我们想想怎样才是正确的?是的,一旦程序碰到异常,它就该挂起程序而"做"点什么。那么上边的代码是这样子的么?看吧,它隐瞒了什么?它把所有的"苦水"往肚里咽(在控制台打印出异常信息),然后一切继续,从表面上看就像什么都没有发生过一样......,很显然,上边代码达到的效果并不是我们所期望的。

后来又怎样?

public void someMethod() throws Exception{
}

上边的代码又有什么问题?

很明显,上边的方法体是空的,它不实现任何的功能(没有一句代码),试问一个空方法体能抛出什么异常?当然Java并不阻止你这么干。最近,我也遇到类似的情景,方法声明会抛出异常,但是代码中并没有任何"机会"来"展示"异常。当我问开发员为什么要这样做的时候,他回答我说"我知道,它确实有点那个,但我以前就是这么干的并且它确实能为我工作。"

在C++社区曾经花了数年实践来实践如何使用异常,关于此类的争论在 java社区才刚刚开始。我曾经看到许多Java程序员针对使用异常的问题进行争论。如果对于异常处理不当的话,异常可以大大减慢应用程序的执行速度,因为它将消耗内存和CPU来创建、抛出并捕获异常。如果过分的依赖异常处理,代码对易读和易使用这两方面产生影响,以至于会让我们写出上边两处"糟糕"代码。

异常原理

大体上说,有三种不同的"情景"会导致异常的抛出:

l 编程错误导致异常(Exception due Programming errors): 这种情景下,异常往往处于编程错误(如:NullPointerException 或者 IllegalArgumentException),这时异常一旦抛出,客户端将变得无能为力。

l 客户端代码错误导致异常(Exception due client code errors): 说白点就是客户端试图调用API不允许的操作。

l 资源失败导致异常(Exception due to resource failures): 如内存不足或网络连接失败导致出现异常等。这些异常的出现客户端可以采取相应的措施来恢复应用程序的继续运行。

Java中异常的类型

Java 中定义了两类异常:

l Checked exception: 这类异常都是Exception的子类

l Unchecked exception: 这类异常都是RuntimeException的子类,虽然RuntimeException同样也是Exception的子类,但是它们是特殊的,它们不能通过client code来试图解决,所以称为Unchecked exception

举个例子,下图为NullPointerException的继承关系:


图中,NullPointerException继承自RuntimeException,所以它是Unchecked exception.

以往我都是应用checked exception多于Unchecked exception,最近,在java社区激起了一场关于checked exception和使用它们的价值的争论。这场争论起源于JAVA是第一个拥有Checked exception的主流OO语言这样一个事实,而C++和C#都是根本没有Checked exception,它们所有的异常都是unchecked。

一个checked exception强迫它的客户端可以抛出并捕获它,一旦客户端不能有效地处理这些被抛出的异常就会给程序的执行带来不期望的负担。
Checked exception还可能带来封装泄漏,看下面的代码:

public List getAllAccounts() throws
FileNotFoundException, SQLException{
...
}

上边的方法抛出两个异常。客户端必须显示的对这两种异常进行捕获和处理即使是在完全不知道这种异常到底是因为文件还是数据库操作引起的情况下。因此,此时的异常处理将导致一种方法和调用之间不合适的耦合。



接下来我会给出几种设计异常的最佳实践 (Best Practises for Designing the API)

1. 当要决定是采用checked exception还是Unchecked exception的时候,你要问自己一个问题,"如果这种异常一旦抛出,客户端会做怎样的补救?"
如果客户端可以通过其他的方法恢复异常,那么这种异常就是checked exception;如果客户端对出现的这种异常无能为力,那么这种异常就是Unchecked exception;从使用上讲,当异常出现的时候要做一些试图恢复它的动作而不要仅仅的打印它的信息,总来的来说,看下表:

Client's reaction when exception happens
Exception type

Client code cannot do anything
Make it an unchecked exception

Client code will take some useful recovery action based on information in exception
Make it a checked exception


此外,尽量使用unchecked exception来处理编程错误:因为unchecked exception不用使客户端代码显示的处理它们,它们自己会在出现的地方挂起程序并打印出异常信息。Java API中提供了丰富的unchecked excetpion,譬如:NullPointerException , IllegalArgumentException 和 IllegalStateException等,因此我一般使用这些标准的异常类而不愿亲自创建新的异常类,这样使我的代码易于理解并避免的过多的消耗内存。

2. 保护封装性(Preserve encapsulation)

不要让你要抛出的checked exception升级到较高的层次。例如,不要让SQLException延伸到业务层。业务层并不需要(不关心?)SQLException。你有两种方法来解决这种问题:

l 转变SQLException为另外一个checked exception,如果客户端并不需要恢复这种异常的话;

l 转变SQLException为一个unchecked exception,如果客户端对这种异常无能为力的话;

多数情况下,客户端代码都是对SQLException无能为力的,因此你要毫不犹豫的把它转变为一个unchecked exception,看看下边的代码:
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
ex.printStacktrace();
}
}


上边的catch块紧紧打印异常信息而没有任何的直接操作,这是情有可原的,因为对于SQLException你还奢望客户端做些什么呢?(但是显然这种就象什么事情都没发生一样的做法是不可取的)那么有没有另外一种更加可行的方法呢?

public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}

上边的做法是把SQLException转换为RuntimeException,一旦SQLException被抛出,那么程序将抛出RuntimeException,此时程序被挂起并返回客户端异常信息。

如果你有足够的信心恢复它当SQLException被抛出的时候,那么你也可以把它转换为一个有意义的checked exception, 但是我发现在大多时候抛出RuntimeException已经足够用了。

3. 不要创建没有意义的异常(Try not to create new custom exceptions if they do not have useful information for client code.)

看看下面的代码有什么问题?

public class DuplicateUsernameException
extends Exception {}


它除了有一个"意义明确"的名字以外没有任何有用的信息了。不要忘记Exception跟其他的Java类一样,客户端可以调用其中的方法来得到更多的信息。

我们可以为其添加一些必要的方法,如下:

public class DuplicateUsernameException
extends Exception {
public DuplicateUsernameException
(String username){....}
public String requestedUsername(){...}
public String[] availableNames(){...}
}



在新的代码中有两个有用的方法:reqeuestedUsername(),客户但可以通过它得到请求的名称;availableNames(),客户端可以通过它得到一组有用的usernames。这样客户端在得到其返回的信息来明确自己的操作失败的原因。但是如果你不想添加更多的信息,那么你可以抛出一个标准的Exception:

throw new Exception("Username already taken");
更甚的情况,如果你认为客户端并不想用过多的操作而仅仅想看到异常信息,你可以抛出一个unchecked exception:

throw new RuntimeException("Username already taken");

另外,你可以提供一个方法来验证该username是否被占用。

很有必要再重申一下,checked exception应该让客户端从中得到丰富的信息。要想让你的代码更加易读,请倾向于用unchecked excetpion来处理程序中的错误(Prefer unchecked exceptions for all programmatic errors)。

4. Document exceptions.

你可以通过Javadoc's @throws 标签来说明(document)你的API中要抛出checked exception或者unchecked exception。然而,我更倾向于使用来单元测试来说明(document)异常。不管你采用哪中方式,你要让客户端代码知道你的API中所要抛出的异常。这里有一个用单元测试来测试IndexOutOfBoundsException的例子:

public void testIndexOutOfBoundsException() {
ArrayList blankList = new ArrayList();
try {
blankList.get(10);
fail("Should raise an IndexOutOfBoundsException");
} catch (IndexOutOfBoundsException success) {}
}



上边的代码在请求blankList.get(10)的时候会抛出IndexOutOfBoundsException,如果没有被抛出,将fail ("Should raise an IndexOutOfBoundsException")显示说明该测试失败。通过书写测试异常的单元测试,你不但可以看到异常是怎样的工作的,而且你可以让你的代码变得越来越健壮。


下面作者将介绍界中使用异常的最佳实践(Best Practices for Using Exceptions)
1. 总是要做一些清理工作(Always clean up after yourself)

如果你使用一些资源例如数据库连接或者网络连接,请记住要做一些清理工作(如关闭数据库连接或者网络连接),如果你的API抛出Unchecked exception,那么你要用try-finally来做必要的清理工作:
public void dataAccessCode(){    
Connection conn 
= null;    
try{    
conn 
= getConnection();    
..some code that 
throws SQLException    
}
catch(SQLException ex){    
ex.printStacktrace();    
}
 finally{    
DBUtil.closeConnection(conn);    
}
    
}
    
   
class DBUtil{    
public static void closeConnection    
(Connection conn)
{    
try{    
conn.close();    
}
 catch(SQLException ex){    
logger.error(
"Cannot close connection");    
throw new RuntimeException(ex);    
}
    
}
    
}
    

DBUtil是一个工具类来关闭Connection.有必要的说的使用的finally的重要性是不管程序是否碰到异常,它都会被执行。在上边的例子中,finally中关闭连接,如果在关闭连接的时候出现错误就抛出RuntimeException.



2. 不要使用异常来控制流程(Never use exceptions for flow control)

下边代码中,MaximumCountReachedException被用于控制流程:

public void useExceptionsForFlowControl() {    
try {    
while (true{    
increaseCount();    
}
    
}
 catch (MaximumCountReachedException ex) {    
}
    
//Continue execution    
}
    
   
public void increaseCount()    
throws MaximumCountReachedException {    
if (count >= 5000)    
throw new MaximumCountReachedException();    
}
     

上边的useExceptionsForFlowControl()用一个无限循环来增加count直到抛出异常,这种做法并没有说让代码不易读,但是它是程序执行效率降低。

记住,只在要会抛出异常的地方进行异常处理。



3. 不要忽略异常

当有异常被抛出的时候,如果你不想恢复它,那么你要毫不犹豫的将其转换为unchecked exception,而不是用一个空的catch块或者什么也不做来忽略它,以至于从表面来看象是什么也没有发生一样。



4. 不要捕获顶层的Exception

unchecked exception都是RuntimeException的子类,RuntimeException又继承Exception,因此,如果单纯的捕获Exception,那么你同样也捕获了RuntimeException,如下代码:

try{
..
}catch(Exception ex){
}
一旦你写出了上边的代码(注意catch块是空的),它将忽略所有的异常,包括unchecked exception.

5. Log exceptions just once

Logging the same exception stack trace more than once can confuse the programmer examining the stack trace about the original source of exception. So just log it once.

posted on 2009-06-18 23:36 Frank_Fang 阅读(3262) 评论(1)  编辑  收藏 所属分类: Java编程

评论:
# re: Java Exception小结 2009-06-23 21:36 | Frank_Fang
从帖子一中截取的自认为比较有用的

这是个很好的话题,我是开发应用系统的,更喜欢Unchecked Exception,但我知道对于JDBC/Hibernate这些底层类库来说,Checked Exception也是必不可少的。

抽象一点说,Checked Exception是一种好的设计,可以让代码更加健壮。但XP里也有一条,不要设计过度。

对于应用系统来说,大多数Exception情况下,只需要中断操作,提示用户错误信息,很少有需要外部代码捕捉甚至进行处理的,所以绝大多数Exception应该是UnChecked Exception (RuntimeException),捕捉到的底层系统错误也应该封装成Unchecked Exception抛出。

UncheckedException带来的不仅是代码简洁,而且给代码更大的复用性。

-----------------------------------------------------------------------------------------
ajoo>>
http://ajoo.javaeye.com/
我是异常的坚定支持者。对为了效率而放弃异常的行为嗤之以鼻。
异常出现的几率是很小的,所以根本没有必要担心效率。

不过checkUser还是返回boolean顺眼。
毕竟,异常表示的是“异常”,不是用在正常逻辑之中。

我的checked和nonchecked异常的标准:
1。如果一个异常的出现意味着bug,debug好的系统不应该出现这个问题(比如很多输入不符合要求什么的),那么就是runtime.
2。如果一个异常的出现是合理的,并且用户需要对这个异常进行恢复,那么就用checked exception。比如IOException, FileNotFoundException等等。
3。一个比较一般性的异常,用户拿到了也基本上没有恢复的能力的异常,用runtime。这点上,感觉SQLException, RemoteException设计的不好。

Anders的观点我还是部分认同的。
所谓的异常会累积,我想主要出现在分层次的系统中。
比如一个经典的设计:
ui层 - 业务层 - 持久层 - 物理层。

物理层会throw SQLException,而这个exception明显不应该被任何中间层所截获处理,而应该一直抛到最上层。
但是,如果你在持久层声明了throws SQLException,你的使用jdbc的实现细节就暴露了。
所以,最好的办法是catch住,然后封装成一个持久层的exception。同样如果物理层还会抛FileNotFoundException, RemoteException等等,这些都是物理层的实现细节,都要在持久层封装。
如果持久层封装的时候继续用checked exception,基于同样的道理上面一层还是要继续封装。

这样的层层封装累不累呀?有必要吗?
??????spring中这个部分因而也变成了一个特色,大部分包中的异常通常再封装一次?????


我想anders的另一个观点在于:exception spec复杂化了接口签名,复杂化了语言。很明显的一点,引入了generics的话,exception spec就让情况复杂很多。
总而言之,在有得有失,利弊难说的时候,保持简单也许是一个从长远角度考虑的正确决定。加一个东西永远比去掉一个东西容易。(当然,眼前确实可能会让软件不那么健壮了)

-----------------------------------------------------------------------------------------
ajoo>>
更正一下,没有看清楚robbin的上下文就胡乱评价了. 该死.

我完全同意robbin的关于loginUser的设计方法.

如果只有用户存在不存在的简单情况,自然用返回值多快好省.

但是,当可能的错误很多的时候,比如:
用户不存在,
用户存在但是被封
密码错误

等等等等, 自然是用exception更好.
用exception的好处是错误信息是结构化的,并且错误是可以任意扩展的.

比如,用户被删除了, 我甚至可以报告用户被删除了几天.
用户被封了,我甚至可以报告用户被哪个该死的斑竹封的,什么时候封的,封了几天等等.
这些,你拿返回值来玩?

这个exception是否是runtime的倒是值得商讨.
runtime的缺点是无法强制客户代码处理.
checked的缺点是如果加入了新的exception种类,客户代码必须马上改动, 如果中间有很多层,就要层层上报,即使这个exception暂时可能不会出现.
(比如系统封人制度还没有启动)

各有利弊. 一般来说我会让UserNotExistException之类的作为checked exception, 一些不那么重要的exception也许就可以作为runtime.

---------------------------------------------------------------------------------------
robbin
使用Checked Exception还是UnChecked Exception的原则,我的看法是根据需求而定。

如果你希望强制你的类调用者来处理异常,那么就用Checked Exception;
如果你不希望强制你的类调用者来处理异常,就用UnChecked。

那么究竟强制还是不强制,权衡的依据在于从业务系统的逻辑规则来考虑,如果业务规则定义了调用者应该处理,那么就必须Checked,如果业务规则没有定义,就应该用UnChecked。

还是拿那个用户登陆的例子来说,可能产生的异常有:

IOException (例如读取配置文件找不到)
SQLException (例如连接数据库错误)
ClassNotFoundException(找不到数据库驱动类)

NoSuchUserException
PasswordNotMatchException

以上3个异常是和业务逻辑无关的系统容错异常,所以应该转换为RuntimeException,不强制类调用者来处理;而下面两个异常是和业务逻辑相关的流程,从业务实现的角度来说,类调用者必须处理,所以要Checked,强迫调用者去处理。

在这里将用户验证和密码验证转化为方法返回值是一个非常糟糕的设计,不但不能够有效的标示业务逻辑的各种流程,而且失去了强制类调用者去处理的安全保障。

至于类调用者catch到NoSuchUserException和PasswordNotMatchException怎么处理,也要根据他自己具体的业务逻辑了。或者他有能力也应该处理,就自己处理掉了;或者他不关心这个异常,也不希望上面的类调用者关心,就转化为RuntimeException;或者他希望上面的类调用者处理,而不是自己处理,就转化为本层的异常继续往上抛出来。


-----------------------------------------------------------------------------------
ajoo>>
今天和workmate也有了关于异常的争论.

问题是这样:

我们需要实现一个根据一些规则验证url输入的正确性.

我的workmate设计成这样:

bool validateUrl(HttpServletRequest);

我说: 你只能判断输入是否正确, 却不能说哪里出错啊. 为什么不用exception呢? 不管有什么错误, 用exception表示出来, 什么信息都不会丢失.

他说: exception不好. 因为exception不应该参与正常control flow.

我说: 这是你自己的意见. 看你怎么定义control flow了.

他说, 90%的人都这样认为.

我说, 返回bool失去了对错误的fine control.我们只知道是否错了, 不知道错在哪里.

他说, 我们可能不一定需要fine control.

然后争论不休.

最后, 我问他, 那么你怎么告诉别人到底出了什么错呢?

他说: 我可以提供一个getError()函数嘛.

我说: 那么, 你的getError()是否要重新validate一遍输入呢?

他说:那... 我可以返回String

String validateUrl(...);

我说, 光string怎么够呢? 万一我需要在html上面显式错误信息, 我需要格式化的, 如果你的error message有回车, 你还要换成<br>呢.

他说, 不用担心, 我会处理br的. (估计是自己把回车替换成<br>吧)

唉, 我还没敢说, 万一我们需要对同一个错误生成不同的东西呢? 比如, 在数据库里log东西啦; 在html上用各种不同颜色,字体显式不同级别的错误; 给mainframe发送xml信息之类的.
他肯定会说: 没那么复杂的.


我说: 如果你throw exception, adapt成bool是举手之劳. 而如果你返回bool,则不可能转换成throw exception.

反正,这老哥本着一个:不能用exception来处理control flow的教条, 就是不同意用exception.

没办法.


----------------------------------------------------------------------------------------
ajoo>>
无数人说异常不应该控制程序流程。
谁仔细想过为什么吗?只怕都是人云亦云罢了。

知道,异常效率低嘛。可是程序流程不见得就是都要高效率的呀。
要真是效率决定一切,java程序员都去要饭得了,连c++扇子们都知道什么2/8原则,你们这里拿效率这么个鸡毛还当令箭耍什么劲?

没人什么地方都用异常。但是有些场合用异常就是方便,错误种类可扩展,错误信息详细,错误处理结构化,可以集中处理错误,不复杂化函数接口等等等等,这些都是用异常表达部分程序逻辑的好处。

你说不应该控制程序流程,好办。给个替代品啊。

Java代码
UserInfo login(String username, String pwd);  
throws AuthencationException; 

UserInfo login(String username, String pwd);
throws AuthencationException;
我这里如果login失败,用异常清晰简单。您给个其它的好用的解决方案先?

也不知道哪个弱人脑子都不过就给出这么个教条来。scheme还有continuation呢,那可是超级异常,不是一样用来处理程序流程?
具体情况具体分析,哪个方案最好用就用哪个,哪来那么多教条?

----------------------------------------------------------------------------------------
checked Exception只不过实现了一个自动文档化的功能,再就是提醒调用者去处理这个错误。
但是,为了这点好处把方法签名和exception绑在一起,其实是个得不偿失的错误。这会使接口无谓复杂化,还增加了代码的耦合程度。
举个例子来说,比如有一个接口I 的某个方法F throws ExceptionA,它的一个实现类里面,F方法里实现里调用了一个类的方法,这个类的方法throws Exception B,这个时候,如果你有三种选择
1、把Exception B用catch 吃掉
2、改变接口的声明,这个显然行不通
3、catch之后抛出一个unchecked Exception,Exception B就这样被强暴了,变成了一个unchecked Exception。
可见,checked Exception 如果没有unchecked Exception的帮忙,连最基本的任务都做不好,这个小小的例子,已经充分证明了checked Exception自身就不是一个完备的异常体系,只不过是当时设计者头脑一发热,做出了一个自以为漂亮的愚蠢设计。
C#取消掉checked Exception,完全是合理的。

------------------------------------------------------------------------------------------
robbin>>
异常类层次设计的不好带来的结果就是非常糟糕,例如JTA的异常类层次,例如EJB的异常类层次,但是也有设计的很好的,例如Spring DataAccessException类层次结构。

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

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

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

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

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

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

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

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

-----------------------------------------------------------------------------------------

第一部分 选择checked or unchecked

这里需要对异常的理解。什么算异常?java的异常处理机制是用来干什么的?异常和错误有什么区别?

异常机制就是java的错误处理机制!java中的异常意味着2点:第一,让错误处理代码更有条理。这使得
正常代码和错误处理代码分离。第二,引入了context的概念,认为有些错误是可以被处理的。问题就出在这儿了。

java的checked异常指的就是在当前context不能被处理的错误!

这句话其实是对上面2点的总结。首先checked异常是一种错误,其次这种错误可以被处理(或修复)。

checked异常就是可以被处理(修复)的错误,unchecked异常其实就是无法处理(修复)的错误。

说到这儿,应该清楚了。别的语言没有checked异常,就是说它们认为错误都无法被修复,至少在语言级
不提供错误修复的支持。java的catch clause干的就是错误修复的事。

我的理解是,用好java的异常,其实就是搞清楚什么时候该用checked异常。应该把unchecked异常当作
缺省行为。unchecked异常的意思是:当我做这件事时,不可思议的情况发生了,我没办法正常工作下去!
然后抛出一个unchecked异常,程序挂起。而checked异常的意思是:当我做这件事时,有意外情况发生,
可以肯定的是,活是没法干了,但是要不要挂起程序,我这个函数没法做主,我只能汇报上级!

其实,从上面的分析可以看出,java引入checked异常只是让程序员多了一个选择,它并不强迫你使用checked异常。

如果你对什么时候应该使用checked异常感到迷惑,那么最简单的办法就是,不要使用checked异常!这里包括2个
方面:

第一,你自己不必创建新的异常类,也不必在你的代码中抛出checked异常,错误发生后只管抛出unchecked异常;
第二,对已有API的checked异常统统catch后转为unchecked异常!

使用unchecked异常是最省事的办法。用这种方法也可以享受“正常代码和错误处理代码分离”的好处。因为我们在调用方法时,
不用根据其返回值判断是否有错误出现,只管调用,只管做正事就ok了。如果出现错误,程序自然会知道并挂起。这样的效果是怎样
的呢?

第一,我们的业务代码很清晰,基本都是在处理业务问题,而没有一大堆判断是否有错的冗余代码。(想想看,如果没有
throw异常的机制,你只能通过函数的返回值来判断错误,那么你在每个调用函数的地方都会有判断代码!)
第二,我们的代码假设一切正常,如果确实如此,那么它工作良好。但是一旦出现任何错误,程序就会挂起停止运行。当然,你可以查看
日志找到错误信息。

那么使用checked异常又是怎样的呢?

第一,你需要考虑更多的问题。首先在设计上就会更加复杂,其次就是代码更加冗长。设计上复杂
体现在以下方面:

1 对异常(错误)的抽象和理解。你得知道什么情况才能算checked异常,使得上级确实能够处理(修复)这种异常,并且让整个程序
从这种设计中确实得到好处。

2 对整个自定义checked异常继承体系的设计。正如那篇文章所说,总不能在一个方法后面抛出20个异常吧!设计自定义checked异常,
就要考虑方法签名问题,在合适的时候抛出合适的异常(不能一味的抛出最具体的异常,也不能一味抛出最抽象的异常)

第二,业务代码相比较使用unchecked的情况而言,不够直接了当了。引入了throws签名和catch clause,代码里有很多catch,方法
签名也和异常绑定了。

第三,有了更强的错误处理能力。如果发生了checked异常,我们有能力处理(修复)它。表现在不是任何错误都会导致程序挂起,出现了
checked异常,程序可能照样运行。整个程序更加健壮,而代价就是前面2条。


第二部分 使用checked异常的最佳实践

现在假设有些错误我们确定为checked异常,那么我们针对这些checked异常要怎样编码才合理呢?

1 不要用checked异常做流程控制。无论如何,checked异常也是一种错误。只是我们可以处理(修复)它而已。这种错误和普通业务
流程还是有区别的,而且从效率上来说,用异常控制业务流程是不划算的。其实这个问题有时候很难界定,因为checked异常“可以修复”,
那么就是说修复后程序照常运行,这样一来真的容易跟普通业务流程混淆不清。比如注册用户时用户名已经存在的问题。这个时候我们要考虑,
为什么要用checked异常?这和使用业务流程相比,给我带来了什么好处?(注意checked异常可以修复,这是和unchecked异常本质的区别)
照我的理解,checked异常应该是介于正常业务流程和unchecked异常(严重错误)之间的一种比较严重的错误。出现了这种错误,程序无法
完成正常的功能是肯定的了,但我们可以通过其他方式弥补(甚至修复),总之不会让程序挂起就是。其实这一点也是设计checked异常时要考虑
的问题,也是代价之一吧。

2 对checked异常的封装。这里面包括2个问题:

第一,如果要创建新的checked异常,尽量包含多一点信息,如果只是一条message,那么用Exception好了。当然,用Exception会
失去异常的型别信息,让客户端无法判断具体型别,从而无法针对特定异常进行处理。

第二,不要让你要抛出的checked exception升级到较高的层次。例如,不要让SQLException延伸到业务层。这样可以避免方法
签名后有太多的throws。在业务层将持久层的所有异常统统归为业务层自定义的一种异常。

3 客户端调用含有throws的方法要注意:

第一,不要忽略异常。既然是checked异常,catch clause里理应做些有用的事情——修复它!catch里为空白或者仅仅打印出错信息都是
不妥的!为空白就是假装不知道甚至瞒天过海,但是,出来混迟早要还的,迟早会报unchecked异常并程序挂起!非典就是个例子。
打印出错信息也好不到哪里去,和空白相比除了多几行信息没啥区别。如果checked异常都被这么用,那真的不如当初都改成unchecked好了,
大家都省事!

第二,不要捕获顶层的Exception。你这么干,就是在犯罪!因为unchecked异常也是一种Exception!你把所有异常都捕获了——不是我
不相信你的能力,你根本就不知道该如何处理!这样做的直接的后果就是,你的程序一般来说是不会挂起了,但是出现错误的时候功能废了,
表面上却看不出什么!当然,深究起来,这也不是什么罪大恶极,如果你在catch里打印了信息,这和上面那条的情况是差不多的。而这2条
的共同点就是,没有正确使用checked异常!费了那么大劲设计的checked异常就是给你们上级(客户端)用的,结果你们不会用!真的
不如用unchecked干脆利落了!

上面的最佳实践是引用前面回帖中那篇翻译的文章,再加上自己的一些理解写成。ajoo对“不要用异常处理业务流程”提出异议,我是无法辩驳的,毕竟水平不够。但我想,对于很多如我这样尚在初级阶段的程序员们,把前辈的话奉为教条也无可厚非吧? 至少这样会少犯错误吧,呵呵。

以上观点均是自己的理解,水平所限,请不吝指正。

  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: