首先,我先声明一点,我讨论的仅限于互联网数据产品,当然可能会涉及到一些其他的抽象,但是所有的结论不代表能复用到所有场景。
几乎每个 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 ,前三点可能都是错的,因为我自己也没有完全实践:)