Java异常的选择:Checked Exception还是Unchecked Exception ?

曾经听到过关于老司机和新手程序员的区别,其中最大的一个区别就在于异常的处理。新手程序员总是天真得把世界想得太美好,基本上没想过会出现异常的情况,而一个经验丰富的老司机会把最坏的打算考虑进去,给出相应的解决办法,使得发生异常时对系统的影响降低到最小。对此,我深表认同。现实的情况总是复杂的,而且还有很多不怀好意的人时刻准备攻击你的系统。使用你系统的用户越多,这种潜在的风险也就越大。

异常处理是应对这些风险的最强有力的武器。在Java的世界里,异常有两种:受检异常(checked exception)和非受检异常(unchecked exception)。想必所有的Javaer都使用过这两种异常,但是何时使用哪个异常缺失经常困扰程序员的头疼问题。在此,我分享一下自己的看法,如果你有不同的意见,请留意探讨。

1.如果正常情况下会出现,那么使用Checked Exception;反之,则使用Unchecked Exception

这条准则是我在决定使用Checked Exception还是Unchecked Exception的第一原则。如果API的使用者在正常使用的过程中都会出现异常,那么这种异常就属于Checked Exception。因为这种异常时属于程序执行流程众多分支之一,API的使用者必须意识到这种情况,并做出相应的处理。

举个栗子:

我希望向zookeeper中创建一个节点,那么这种情况就隐含了两个前提条件:

  • 父节点已经被创建(如果有的话)
  • 本节点还未被创建

那么,这个API的签名大致应该是这样:

void createNode(String path,byte[] data) throws FatherNodeNotExist, NodeExist;

API的使用者看到这个签名的定义时就会得到一个强烈的心理暗示,我需要考虑父节点不存在和本节点已存在的情况,那么他就不得不显示的去处理这两种异常。

有的朋友可能会争论说,我正常的情况下不会出现这种情况,因为使用这个API的前提就是先创建好父节点,而后创建本节点,那我就不用抛出两种异常了,使用者也轻松了许多。但事实真的如此吗?我们想当然的认为了使用者是 自己人 ,他们会乖乖的按照我们的想法去先创建父节点,再创建本节点,如果是在一个很局限的使用场景下,每个人都说经过严格培训的,那么你可以去做这样的假设,但是我还是不推荐你这么做,因为这样设计使得系统是脆弱的,不稳定的。如果能通过系统能自己避免这些错误,为什么不呢?况且,如果你把这个API开放给第三方的使用者,那么情况会更糟糕,你根本不知道他们会怎样去使用API,这非常恐怖!

有时候情况会变得很复杂, 正常情况 的鉴定变得很困难,你肯定会遇到这种时候,此时就需要结合你的业务场景去权衡其中的利弊。这依赖与你的经验和对业务场景的理解,我无法给你一个绝对的建议,那样是不负责任的。

我再举个常见的栗子:用户修改他拥有的资源信息。在菜谱APP中给出一个接口,让用户修改他菜谱的信息。那么这里一个隐含的条件就是用户修改他自己的菜谱信息,他是无权限修改别人的菜谱信息的。那么这个API的签名可能是这样的:

void updateMenu(long menuId,long uid,String title,String description...);

如果用户尝试去修改不属于他的菜谱呢?我们是否需要throws UserPermissionException之类受检异常?我认为是不需要的。判断是这属于正常情况吗?我认为这不算是正常情况。正常情况下,客户端调用修改信息的接口,那么menuId一定是属于这个用户的。如果出现这种情况,要么是你系统设计的就有问题,要么就是不怀好意的人在破坏你的系统。前者需要重新设计我们的系统,而后者我们更不用关系,直接抛出一个RuntimeException就可以,因为他不算正常用户。

2. 调用者中能从异常中恢复的,推荐使用受检异常;反之,则使用非受检异常

注意这里的一个关键词是 推荐 ,决定使用哪种异常最为根本的还是第一条原则。如果第一条原则难以判断时,才考虑调用者。这条原则和 Effective Java 中的第58条很像,如果有这本书的朋友可以再拿出来读读。

我和 Effective Java #58不同的观点在于,这条原则只能是 推荐 ,另外,对于所有不能恢复的情况我都建议使用非受检异常。我对可恢复的理解是,如果API的调用者能够处理你抛出的异常,并给出积极的响应和反馈,并指导它的使用者做出调整,那么这就是可恢复的。不可恢复就是API的调用者无法处理你抛出的异常,或者仅仅只是打个LOG记录一下,不能对它的使用者做出提示,那么都可认为是不可恢复的。

还是最开始的栗子,如果调用 createNode 的调用者能响应 FatherNodeNotExist ,并把这种情况反应到终端上,那么使用受检异常是有积极意义的。对于不可恢复的情况,包括编程错误,我建议都是用非受检异常,这样系统能 fail fast ,把异常对系统的影响降到最低,同时你还能获得一个完整的异常堆栈信息,何乐而不为呢?!

基本上,这两条原则就能帮你决定到底是使用受检异常还是非受检异常了。当然,现实的情况很复杂,需要根据你所处的具体业务场景来判断,经验也是不可或缺的。在设计API的时候多问下自己这是正常情况下出现的吗,调用者可以处理这个异常吗,这会很有帮助的!

异常处理是一个非常大的话题,除了选择 checked exception 还是 unchecked exception 以外,还有一些一般的通用原装,例如:

  • 只抛出与自己有关的异常
  • 封装底层异常
  • 尽量在抛出异常的同时多携带上下文信息

这些在 Effective Java 中都有详细的介绍,朋友可以认真读一下这本书,写的非常好!

对异常处理有不同理解的朋友可以给我留言,一起讨论,共同进步!

参考文献:

Effective Java, 2nd Edition

This article used CC-BY-SA-3.0 license, please follow it.

 

 

 

  1. Checked异常必须被显式地捕获或者传递,如Basic try-catch-finally Exception Handling一文中所说。而unchecked异常则可以不必捕获或抛出。
  2. Checked异常继承java.lang.Exception类。Unchecked异常继承自java.lang.RuntimeException类。

我过去支持checked异常,但是最近我改变了我的观点。Rod Johnson(Spring Framework),Anders Hejlsberg(C#之父),Joshua Bloch(Effective Java,条目41:避免checked异常的不必要的使用)和其他一些朋友使我重新考虑了checked异常的真实价值。最近我们尝试在一个较大的项目中使用unchecked异常,效果还不错。错误处理被集中在了少数几个类中。会有需要本地错误处理的地方,而不是将异常传播给主错误处理代码。但是这种地方不会很多。由于代码中不会到处都是try-catch块,我们的代码变得可读性更好。换句话说,使用unchecked异常比使用checked异常减少了无用的catch-rethrow try-catch块。总之,我建议使用unchecked异常。至少在一个工程中尝试过。我总结了以下原因: Unchecked异常不会使代码显得杂乱,因为其避免了不必要的try-catch块。 Unchecked异常不会因为异常声明聚集使方法声明显得杂乱。 关于容易忘记处理unchecked异常的观点在我的实践中没有发生。 关于无法获知如何处理未声明异常的观点在我的实践中没有发生。 Unchecked异常避免了版本问题。

此处链接:http://blog.csdn.net/kingzone_2008/article/details/8535287。

 

此处貌似很不错,异常集中处理,加入返回信息。貌似更好哦。。。我也有点支持。。。

转载于:https://my.oschina.net/whiteInfo/blog/810459

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值