在 Java 语言中, 异常从使用方式上可以分为两大类:
- CheckedException
- UncheckedException
在 Java 中类的异常结构图如下:
可检查异常需要在方法上声明, 一般要求调用者必须感知异常可能发生, 并且对可能发生的异常进行处理. 可以理解成系统正常状态下很可能发生的情况, 通常发生在通过网络调用外部系统或者使用文件系统时, 在这种情况下, 错误是可能恢复的, 调用者可以根据异常做出必要的处理, 例如重试或者资源清理等.
非检查异常是不需要在 throws 子句中声明的异常. JVM 根本不会强制您处理它们, 因为它们主要是由于程序错误而在运行时生成的. 它们扩展了 RuntimeException. 最常见的例子是 NullPointerException 可能不应该重试未经检查的异常, 并且正确的操作通常应该是什么都不做, 并让它从您的方法和执行堆栈中出来. 在高执行级别, 应记录此类异常.
Error 是最为严重的运行时错误, 几乎是不可能恢复和处理, 一些示例是 OutOfMemoryError,LinkageError 和 StackOverflowError. 它们通常会使程序或程序的一部分崩溃. 只有良好的日志记录练习才能帮助您确定错误的确切原因.
在异常处理时的几点建议:
1 永远不要 catch 中吞掉异常, 否则在系统发生错误时, 你永远不知道到底发生了什么
- catch (SomeException e) {
- return null;
- }
2 尽量使用特定的异常而不是一律使用 Exception 这样太泛泛的异常
- public void foo() throws Exception {
- // 错误的做法
- }
- public void foo() throws MyBusinessException1, MyBusinessException2 {
- // 正确的做法
- }
一味的使用 Exception, 这样就违背了可检查异常的设计初衷, 因为调用都不知道 Exception 到底是什么, 也不知道该如何处理. 捕获异常时, 也不要捕获范围太大, 例如捕获 Exception, 相反, 只捕获你能处理的异常, 应该处理的异常. 即然方法的声明者在方法上声明了不同类型的可检查异常, 他是希望调用者区别对待不同异常的.
3 Never catch Throwable class
永远不要捕获 Throwable, 因为 Error 也是继承自它, Error 是 Jvm 都处理不了的错误, 你能处理? 所以基于有些 Jvm 在 Error 时就不会让你 catch 住.
4 正确的封装和传递异常
不要丢失异常栈, 因为异常栈对于定位原始错误很关键
- catch (SomeException e) {
- throw new MyServiceException("Some information:" + e.getMessage()); // 错误的做法
- }
一定要保留原始的异常:
- catch (SomeException e) {
- throw new MyServiceException("Some information:" , e); // 正确的打开方式
- }
5 要打印异常, 就不要抛出, 不要两者都做
- catch (SomeException e) {
- LOGGER.error("Some information", e);
- throw e;
- }
这样的 log 没有任何意义, 只会打印出一连串的 error log, 对于定位问题无济于事.
6 不要在 finally 块中抛出异常
如果在 finally 中抛出异常, 将会覆盖原始的异常, 如果 finally 中真的可能会发生异常, 那一定要处理并记录它, 不要向上抛.
7 不要使用 printStackTrace
要给异常添加上有用的上下文信息, 单纯的异常栈, 没有太大意义
8 Throw early catch late
异常界著名的原则, 错误发生时及早抛出, 然后在获得所以全部信息时再捕获处理. 也可以理解为在低层次抛出的异常, 在足够高的抽象层面才能更好的理解异常, 然后捕获处理.
9 对于使用一些重量级资源的操作, 发生异常时, 一定记得清理
如网络连接, 数据库操作等, 可以用 try finally 来做 clean up 的工作.
10 不要使用异常来控制程序逻辑流程
我们总是不经意间这么做了, 这样使得代码变更丑陋, 使得正常业务逻辑和错误处理混淆不清; 而且也可能会带来性能问题, 因为异常是个比较重的操作.
11 及早校验用户的输入
在最边缘的入口校验用户的输入, 这样使得我们不用再更底层逻辑中处处校验参数的合法性, 能大大简化业务逻辑中不必要的异常处理逻辑; 相反, 在业务中不如果担心参数的合法性, 则应该使用卫语句抛出运行时异常, 一步步把对参数错误的处理推到系统的边缘, 保持系统内部的清洁.
12 在打印错误的 log 中尽量在一行中包含尽可能多的上下文
- LOGGER.debug("enter A");
- LOGGER.debug("enter B"); // 错误的方式
- LOGGER.debug("enter A, enter B");// 正确的方式
- Thanks all. Happy Learning!!
- SpringAutowired
一个有用的公众号
长按, 识别二维码, 加关注
来源: http://www.tuicool.com/articles/2u2Ez2Y