Java 中的Exception 有什么用?

Exception 的作用,这个问题是一个开放性的问题,没有标准的答案,不同经历的人可能会有不同的答案,也就是说经历过惨痛的经历后,才会对Java Exception 有深刻的认识,单纯理论学习,按部就班的按所谓的规范使用,无法准确的表达出Java Exception 的内在逻辑。

Exception 含义

Exception,顾名思义,代表着程序运行的过程中出现一种不正常的状态,需要中止程序的持续运行,同时又能快速的发现程序为什么会出现错,通过异常的信息能够快速定位,所以异常需要提供错误的上下文的状态信息,尤其是应用系统自身抛出的异常,需要将上下文状态尽可能多的输出,有助于排查和定位。Java 中的NullPointerException 对外提供的信息极少,所以应用系统应该尽可能catch 这类异常,包装为信息量更加丰富的异常抛出。

Exception 也是一种程序逻辑控制的方式,是程序健壮性的一种表现。其实,大都数异常,程序都应该有一定的相应的处理逻辑,例如,ArrayIndexOutOfBoundsException,这类异常在编码过程中应该能够被预见,并需要做出现相应的逻辑控制,有经验的程序员,在编码的过程中就已经考虑到各种异常的情况,待整个系统上线后,出现的问题相对较少,而普通的程序员往往只保证程序在Happy Case 时能够正常运行,却忽略了各种异常情况,往往是在出现问题后进行补救,导致程序在反复补救后,逻辑混乱,这也就是很多项目早期的代码质量高,经过一段时间后,代码惨不忍睹的根源。

Exception 的处理方式

什么时候需要catch Exception?什么时候需要throw 呢?这也是不会有标准答案的过程,Java 本身也没有给出标准答案,也没有指导原则。但经过无数次实践的经验后,你就能体会对异常的不同处理方式,对系统逻辑的稳定性性有着截然不同的影响,往往出现很诡异的Bug时,同时可参考的日志或其它关键丢失,导致故障不能及时解决,才会发现关键的位置异常被人为的隐藏是多么愚蠢的行为。

我在使用一个开源的框架时,时常会发现重要的异常信息被隐藏,导致花费大量的时间去跟踪代码。异常在什么时候需要throw,什么时候需要catch,原则其实很简单:

  1. 异常在能够完全掌控的情况下,而且也有明确的逻辑处理时,需要catch,并进行相应的处理。同时,也需要关注外部程序是否需要感知异常,如果需要,则封装新的异常后重新throw;
  2. 无法决定处理逻辑的情况下,需要将所有异常throw,让外部程序决定处理逻辑。同时,针对内部的信息含量较少的Runtime 型的异常,需要额外封装,避免在同一行代码中,无法确定哪一个对象为null。

Exception 定义

什么时候定义Exception 同样是没有任何指导原则,不同的技术框架中对异常定义的方式和方法都不太一样,毕竟每个应用系统的逻辑结构和复杂度都不太一定,我根据自身的经验整理了两个指导原则:

  1. 程序需要中止,自身又无法完整的处理逻辑转折,只能由外部调用者根据异常的信息,作为合理的逻辑处理,例如:数据采集时,出现了非法的数据或字符串,采集程序是无法判断逻辑转折后,就如何处理,同时又需要明确区分非常数据,甚至需要定义出错误的类型,由外部程序决定是忽略还是终止执行,在这种情况下需要定义明确的异常,用于标识错误的类型。
  2. 内部程序处理过程中出现太多异常,并且这类异常具有共同的特性(例如:ClassNotFountException, NoSuchMehtodException 等),不需要外部程序对这类异常分别处理时,需要定义一个异常用于包装内部异常,提升代码的可读性。
  3. 错误信息的补充,例如上文所提到的NullPointerException,应用系统程序需要为此异常补充更完整的信息,便于外部程序或日志系统提供足够的信息,便于故障的解决;

Exception 和 RuntimeException 有什么区别,在什么场景下定义,也是一个仁者见仁,智者见智的问题,Java 没有任何指导建议,按我个人的判断,JDK中也有很多的定义也不是特别合理,例如:IndexOutOfBoundsException 应该是一个Exception, JDK 却定义为RuntimeExcption 这类错误需要明确的提醒外部程序对数组进行逻辑限制,很多应用系统在总是在出现错误后,才能主动Cache 这类异常;然而像 ClassNotFoundException 这类异常在极少数状态下才会进行逻辑处理JDK 却定义为Exception 导致很多反射类的项目的处理逻辑变得复杂,NullPointerException 也是经常出现的,但定义为RuntimeException 是合理的,可能因为JDK 程序从逻辑上根本无法捕捉NullPointerException,应该是在JVM 执行过程中才能够进行逻辑判断,具体并未研究。

经过上面的两个示例也很容易看出Exception 和RuntimeException 有什么区别,JDK 的设计应该从外部程序使用的角度进行异常设计,由于外部程序导致的异常,并且外部程序应当有逻辑处理异常状态,这类异常理应定义为Exception,而其它由于JVM 在编码期无法判断,也无法从语法层面提供解释的Exception 应该定义为RuntimeException。

结论

针对Java 异常的解读是我个人的见解,就像古诗词一样,不同人有不同的解读,相信JDK 的设计者也无法给 Java Exception 一个明确的解释和原则,只能靠历史经验的积累和不断的错误中,逐形成相对完整的理论,供后人学习。

guest
1 评论
Inline Feedbacks
View all comments
pzw

Exception的使用确确实实让我感到一些迷惑:
Exception的作用是什么?假设:
a是为了提醒方法调用者,规避异常产生。一个方法需要维护它抛出的异常列表,而这个异常列表可以用来提醒方法的调用者“可能会出现这些问题”,由调用者来决定遇到这些异常是catch还是throw。而由于java作为面向对象语言,具有封装特性,方法的具体实现对于调用者而言是一个黑箱,调用时并不知道具体的产生这些异常的原因,所以无法完全规避这些异常的产生。如果异常时为了提醒调用者的话,那它的设计思想和“封装”本身具有矛盾。

b.如果不是为了规避异常产生的话,那就是提醒调用者,如果有异常,要处理。比如读写文件时,关闭流。然而当一个方法的异常列表比较长时,通常会放弃对这个列表的维护,而取这几个异常的共同父类异常,如:void mathod(String x) throws IOException, JAXBException {} 改为 void mathod(String x) throws Exception {},这种情况就让方法的调用者会更加迷惑。

但jdk源码中显然不是这么用的,尤其是早期的一些代码。在常用的函数式接口中如“java.util.function”中的方法并没有throw任何异常,推测:java标准的定制者们似乎正在放弃checkException。UncheckedIOException这个类的出现似乎也佐证了这一点。目前没有在官方文档中见到对于Exception明确的使用标准,在自定义一个异常时,什么时候定义一个Exception,什么时候定义RuntimeException,没有明确的标准。希望大佬不吝赐教。

Last edited 2 月 之前 by pzw
1
0
如果你有话对我说,请点击x
()
x