该如何良好的实践Java中的Exception机制
首先,我先声明一点,我讨论的仅限于互联网数据产品,当然可能会涉及到一些其他的抽象,但是所有的结论不代表能复用到所有场景。
几乎每个Java程序员都清楚知道Java的异常和错误机制,无论是在面试过程中,还是在学习中,你看到Exception,无非就是了解一下继承关系、子类、和Error的关系等等。当然这些知识点是基础,那么在实践中,用到了吗?你确定你使用Exception时没有偷懒?我的经验告诉我,良好的使用Exception能让你的程序bug更少,或者至少能保证你的程序更容易被理解和跟踪。
先回到老的知识点吧——Java的异常机制,我们知道Java里的异常是完全继承Throwable的,正如java doc里注释的,无论你throw的还是JVM throw的,抑或是你想catch的,都必须继承Throwable。我这里帮助大家纠正的第一个点就是:java.lang.Throwable是一个class,不是一个interface,千万别被名字欺骗。优秀的程序员有好的命名习惯,当Java程序员默认认为带有-able后缀的都该是接口时, Josh Bloch给大家上了一课——Throwable就是类。回到正题,Throwable有两大子类,一个是java.lang.Error,一个是java.lang.Excpetion,Error是错误,一般多用于系统异常和底层错误,Error抛出就会导致程序终止;而Exception是异常,有程序引起,又分为受检的checked和非受检的unchecked(不知道哪本书这么翻译的来着),受检的异常是普通异常,就是你需要catch的来打日志或者补救的(这种做法叫吞掉异常),非受检的多数是RuntimeException,就是运行时异常,这类异常不建议catch,因为这个是程序bug,需要被人发现,所以建议抛出引起程序终止。Blah~blah~ 这些都是老生常谈的话题,需要深入的点是有几个:
第一,Error是建议到系统级别的错误的,包括虚拟机的,我们常见的就是java.lang.VirtualMachineError,这是一个JVM级别的抽象Error,如果你觉得没见过?那么不用奇怪,它的两个儿子你肯定见过:OutOfMemoryError和StackOverflowError。Error其实真没好说的,一般情况不建议捕获,程序员也用的较少,但是你看很多基础框架或者系统软件,都是有自定义Error的,所以当你的工作层次或者范围你能确定比较底层时,其实是可以自定义一些Error来控制程序的错误的。我这样说可能也不是很好理解,换个简单的话:你的架构设计中需要考虑到异常的处理,那么首先要对异常定级别,如果有可能有偏底层的异常时,或者是本不该出现且不建议用户(多数是依赖你的库进行开发的其他程序员)捕获时,定义为Error是个不错的选择。当然也不是说做上层开发的程序员就不能使用Error,只要你设计合理,你可以在必要时抛出Error来终止程序——比如提醒你的老板再不加薪就Error到死:)
第二,Exception分两类这事几乎人人皆知,受检的异常往往是web后端开发或者服务开发自定义的业务异常比如BizServiceException或者常见的DAOException,这些异常在开发定义时总是直接extends Exception,而忽视了究竟这些异常我们对它们的期望是什么,这里我想强调一点,我们在业务系统架构中考虑到异常机制,自定义的异常不是为了有异常而定义异常,一定对它本身是有期望的。我们对异常的一个基本期望是异常究竟该被谁捕获,如果被你的服务下游捕获,那么这必须是一个受检的异常,如果是系统自身需要,那么这个我个人认为是分阶段设计的,在初期,也就是未发布状态,这些Exception应该总是被抛出的,因为这样可以快速的让测试和质量控制人员发现系统崩溃的点。在发布阶段,异常可能需要被内部消化,这时受检异常就要提供给业务系统,让业务开发自行捕获异常。当然,好的系统架构可能会把Exception作为一个内部可见外部不可见的内容,而基于此完全封装一套error code对外,这应该算是比较友好的做法了,也是很多API设计时的标准规范。毕竟对外部透明,不要让用户看到你的Exception,这是非常友好的做法。
第三,关于catch,就针对上面的第二点讲,吞异常这事不是没人干过,我们往往担心系统错误而一个try catch 捕获所有Exception,有的甚至不够,还升一级,捕获Throwable,这应该是最糟糕的代码设计(但不幸的是在我现在开发的系统和曾经开发过的业务系统中,这类代码非常普遍)。开发人员不应该因为时间紧、赶进度等接口而忽视Exception,就拿最上层的业务开发举例,开发可能会调用各类服务、访问数据库、访问缓存和文件系统等等,而这些服务必然包含了各种异常,而catch一个Exception,试图通过吞噬异常保护系统或者页面正常访问,而打日志到后台,通过分析日志来偷偷的解决bug……说起来真是汗毛倒竖。我的观点:如果有错误,那么让它尽早暴露出来,我们应该通过尽量多的测试和优化来避免错误,而不是偷偷的隐藏。事实也证明,日志里大量的NPE或者其他RuntimeException存在,但是无人问津,“系统不是好好的吗?”,“页面不是没问题吗”这样的说辞可以让开发看起来毫无责任,但是这为系统长期的维护和后续的扩展都带来了无尽的烦恼和坑。
综上,我个人的经验告诉我几点对待Exception的方法:
1,花时间了解涉及到的每个服务和方法所可能抛出的异常。事实往往是理解异常的关系和机制其实不花时间,了解后再开发,你会比别人知道更多的异常点,这能保证你程序的健壮性;
2,无论你在服务开发还是服务使用层级,都要尝试或者想到封装异常,提供友好错误设计的方案,最简单的是自定义一个业务Exception来封装。
3,不要在你的方法开始try,结束时catch,这防御性太强了,很美品位。
4,前三点可能都是错的,因为我自己也没有完全实践:)