异常在我们的平时开发过程中是非常寻常并且经常会面对的,我们有很多方式来处理和使用异常。充分发挥异常的优点可以提高程序的可读性,可靠性和可维护性。但是如果使用不当,也会带来很多负面影响。


参考 effective java 第三版中对于异常的一些优秀实践来做一下总结:

No.1 只针对异常的情况才使用异常

异常应该只应用于异常的情况下,永远不要在正常的控制流中使用异常。

例如代码:

int index = 0;
try{
while(true){
System.out.println(strList.get(index++));
}
}catch (ArrayIndexOutOfBoundsException e){}

上图代码的功能是遍历输出strList集合中的全部元素,但其实我们知道只需要一个foreach就能遍历输出

集合中的所有元素。也许可能考虑到了使用正常的foreach会使得每次遍历的时候都要去检查当前遍历索引是否越界,以为该种方式在性能方面会更优于正常的处理方式。实际上该种方式比正常的处理要慢(其中涉及到jvm的优化)。

总而言之,异常是为了在异常情况下使用而设计的,不要在正常的控制流中使用异常。


No.2 对可恢复的情况使用受检异常,对编程错误使用运行时异常

如果期望调用者能够适当的恢复,就应该使用受检异常。

用运行时异常来表明编程的错误。

你所实现的未受检异常都应该是RunTimeException的子类。

要在受检异常上提供方法,以便协助恢复。

不要定义任何既不是受检异常也不是运行时异常的抛出类型。


No.3 避免不必要的使用受检异常

受检异常优点

不同于返回码和未受检异常,受检异常强迫程序员处理异常的条件,从而增加程序的可靠性

受检异常缺点

1.如果方法抛出受检异常,则调用该方法者就必须在一个try catch块中对异常进行处理,或者在调用方法中声明抛出异常并让他们传播出去。这给程序员带来了不少的负担。

2.抛出受检异常的方法无法直接在Stream中使用。

对于使用受检异常的情况应该同时满足两个条件:

1.正确的使用该方法或者api的情况下不能防止异常发生

2.一旦产生异常程序员可以采取有效的措施来处理异常

若这两点没有同时成立,则更适合使用未受检异常。

消除受检异常的方法:

1.方法返回一个optional类型的对象,若遇到异常,则只是返回一个0长度的optional(该方法的缺点是由于只返回一个optional,缺少其他信息,若发生异常追查原因会比较困难)

2.把抛出异常的方法拆分成两个方法,第一个方法返回一个boolean值,表明是否应该抛出异常,另一个方法则是该方法的处理逻辑。

3.如果程序员知道调用将会成功或者不介意由于异常导致线程被终止。

合理的使用受检异常可以增加程序的可读性和可靠性,如果过度使用受检异常将会给调用者带来很大的负担。如果调用者无法恢复异常则应该抛出未受检异常。如果希望调用者对异常进行处理,首选应该是返回optional值,只有万一失败时,这些无法提供足够的信息来描述异常则考虑使用受检异常。


No.4 优先使用标准的异常

异常重用,java平台类库提供了一组基本的非受检异常,他们满足了大多数api的异常抛出需求。

最常被使用到的两个异常类型:IllegalArgumentException和IllegalStateException。前者代表非法参数异常,后者表示非法状态异常。可以这么说,所有错误的方法调用都可以归结为非法参数和非法状态。另外还有其他异常类也可以表示为非法参数和非法状态异常

例如:NullPointException IndexOutOfBoundsException等等。

不要直接重用Exception,RunTimeException,Throwable 和 error。可以把这些类看成是抽象类,你无法可靠的测试这些异常,他们是一个方法可能抛出的异常的超类。

如果希望增加更多的失败捕获信息,可以子类化标准异常。没有正当的理由,不应该去编写额外的异常累,而应该使用java提供的标准异常类。

对某一个异常发生的情况可能同时满足多个标准异常规范的场合。比如 在长度为10的数组中去取第11个元素。显然这种情况可以理解参数数值太大(IllegalArgumentException),但是这种异常也可以理解为数组中的元素太少(IllegalStateException)。这里我们可以规范如果没有可用的参数值则使用后者异常类,否则使用前者。


No.5 抛出与抽象对应的异常

如果方法抛出的异常和他执行的任务没有明显的关联,这种情形会使人不知所措。方法将他调用的底层方法异常原封不动的向外抛出,例如在一个获取用户信息的方法中调用了手机号解码方法,而该解码方法刚好发生异常,用户信息方法将其捕捉之后直接抛出,这就会让调用获取用户信息方法的调用者很困惑,因为他们并不知道获取用户信息和解码异常之间的关系,从而导致问题并不好排查,到底是客户端传参数不对还是系统的异常。所有为了避免这个问题,更高层的异常应该捕获底层的异常,同时抛出可按照高层抽象进行解释的异常。这过程也叫异常转译。

有一种特殊的异常转译叫做异常链,即底层放入异常被传到高层的异常,高层的异常提供访问方法还获得底层的异常

try {
URLEncoder.encode("ds","utf-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}

如上,高层异常的构造器将原因传到支持链的构造器,从而当异常发生时高层调用者可以调用异常的相关方法来看到底层的异常,另外在打印异常堆栈信息的时候,这样就可以把底层的异常信息给集成到高层异常中。

异常转译相对于直接将底层异常进行抛出会好很多,但我们不应该滥用。对于底层方法的异常我们首先要做的就是应该避免会发生这种异常,例如在调用之前进行参数校验从而防止异常发生。当然底层方法发生异常时,我们其次想到的应该是在高层方法中悄悄的处理异常,从而将高层方法的调用者和异常进行隔离,使用log对异常进行记录,这样有助于排查问题,又可以将客户端代码和最终用户与异常隔离开来。

如果不能阻止并且处理底层异常的发生,我们应该使用异常转译,只有底层抛出的异常恰好能表述高层执行任务的情况下,可以将底层异常直接进行抛出。


No.6 每个方法抛出的所有异常都要建立文档

始终要单独的申明每一个受检异常,并且利用javadoc的@throws标签,准确的记录下每个异常抛出的条件。并且需要抛出具体的异常类而非异常的基类exception或者throwable。

使用javadoc的@throws标签记录一个方法可能会抛出的未受检异常,但不要使用throws关键字将未受检异常包含在方法申明中。

如果一个类中的许多方法在同样的情况下都会抛出一致的异常,那么在该类的文档注释中应该对这个异常建立文档,而不是为每一个方法建立文档。


No.7 在细节消息中包含失败-捕获信息

为了捕获失败,异常的细节信息应该包含对异常有贡献的所有参数和域的值。不过千万不要在细节中包含密码,密钥等敏感信息。


No.8 努力保持失败的原子性

一般而言,失败的方法调用应该使对象保持在被调用之前的状态,具有这种属性的方法被称为具有失败的原子性。

保持失败的原子性:

1.设计一个不可变的对象。

2.在执行操作之前进行必要的参数有效性校验,这使得对象状态被修改之前先抛出适当的异常(调整计算处理的顺序,使得任何可能会失败的计算部分都在对象被修改前发生)。

3.在对象的一份临时拷贝上进行操作,当操作完成后在用临时的拷贝来替换原有的对象。

4.写一段恢复的代码,让他来拦截过程中发生的失败,让对象回到被调用前的状态。

错误通常是不可恢复的,不要在方法抛出assertionError时,不需要努力的去保持失败的原子性。


No.9 不要忽略异常

不要忽略异常,忽略异常很简单,使用一个try 并利用空的catch块就能忽略异常,但是空的catch达不到应有的目的。我们可以把异常认为是火灾而trycatch就像是火警器。当异常发生时我们没有做任何的处理而是将他忽略。这将导致没人会在意到这个异常。当真正有一条异常被注意到的时候也许这个异常影响的范围已经非常大了。

也有一些异常我们是可以忽略的。类似文件读取,fileStream关闭操作,当我们对文件没有做任何处理,并且已经读取到文件中的信息,此时我们可以将异常进行忽略。但是忽略的时候我们应该作出必要的注释说明。并且将catch中的类变量名设为ignore。

总之,通过忽略异常来解决异常是一件极具风险的事情,我们需要认真对待异常,并且十分清楚问题的原因以及产生的影响。

java异常有效实践的更多相关文章

  1. JAVA异常的最佳工程学实践探索

    此文已由作者占金武授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 先说明一下背景: 项目日志中的Exception会被哨兵统一监控并报警 比较多的项目基于dubbo在做服务化 ...

  2. Java 理论与实践: 处理 InterruptedException

    捕捉到它,然后怎么处理它? 很多 Java™ 语言方法,例如 Thread.sleep() 和 Object.wait(),都可以抛出InterruptedException.您不能忽略这个异常,因为 ...

  3. 10个关于Java异常的常见问题

    这篇文章总结了十个经常被问到的JAVA异常问题: 1.检查型异常VS非检查型异常 简单的说,检查型异常是指需要在方法中自己捕获异常处理或者声明抛出异常由调用者去捕获处理: 非检查型异常指那些不能解决的 ...

  4. Java异常错误的面试题及答案

    1) Java中什么是Exception? 这个问题经常在第一次问有关异常的时候或者是面试菜鸟的时候问.我从来没见过面高级或者资深工程师的 时候有人问这玩意,但是对于菜鸟,是很愿意问这个的.简单来说, ...

  5. java异常面试常见题目

    在Java核心知识的面试中,你总能碰到关于 处理Exception和Error的面试题.Exception处理是Java应用开发中一个非常重要的方面,也是编写强健而稳定的Java程序的关键,这自然使它 ...

  6. Java 理论与实践: 处理 InterruptedException(转)

    很多 Java™ 语言方法,例如 Thread.sleep() 和 Object.wait(),都可以抛出InterruptedException.您不能忽略这个异常,因为它是一个检查异常(check ...

  7. 最重要的 Java EE 最佳实践

    參考:IBM WebSphere 开发人员技术期刊: 最重要的 Java EE 最佳实践 IBM WebSphere 开发人员技术期刊: 最重要的 Java EE 最佳实践 2004 年 IBM® W ...

  8. Java 理论与实践: 并发集合类

    Java 理论与实践: 并发集合类 DougLea的 util.concurrent 包除了包含许多其他有用的并发构造块之外,还包含了一些主要集合类型 List 和 Map 的高性能的.线程安全的实现 ...

  9. Java Servlet DAO实践(二)

    Java Servlet DAO实践(二) DAO连接类 package com.seller.servlets.dao; import java.sql.*; public class DataBa ...

随机推荐

  1. 【Error】Maven Dependency 下载失败问题

    原文 前言 在使用Maven私服Sonatype Nexus的时候,经常会出现依赖包找不到的问题. 此时通过浏览器去私服页面查看,发现依赖包坐标是存在的,对应的文件(比如jar文件). 或者私服上面也 ...

  2. wait()、notify、notifyAll()的使用

    wait().notify.notifyAll()的使用 参考:https://www.jianshu.com/p/25e243850bd2?appinstall=0 一).java 中对象锁的模型 ...

  3. 勾股数专题-SCAU-1079 三角形-18203 神奇的勾股数(原创)

    勾股数专题-SCAU-1079 三角形-18203 神奇的勾股数(原创) 大部分的勾股数的题目很多人都是用for来便利,然后判断是不是平方数什么什么的,这样做的时候要对变量类型和很多细节都是要掌握好的 ...

  4. Mysql数据库调优和性能优化的21条最佳实践

    Mysql数据库调优和性能优化的21条最佳实践 1. 简介 在Web应用程序体系架构中,数据持久层(通常是一个关系数据库)是关键的核心部分,它对系统的性能有非常重要的影响.MySQL是目前使用最多的开 ...

  5. linux中Nginx安装

    linux中Nginx安装 编译安装 ​ Nginx的优点太多,这里不再赘述,详情请看这篇博客深入理解nginx. ​ Nginx的安装有rpm包安装.编译安装和docker安装.本文将介绍编译安装方 ...

  6. Linux 基本命令操作 (文件共享) 一

    前言:在学习Linux过程中,遇到一些经典而又基本的命令操作,想记录下来去帮助刚学Linux的同学.下面是有关相关的操作,我会进行详细的分解步骤:希望能够帮助到你们.由于时间仓促,再加上笔者的能力有限 ...

  7. SQL Server2017 安装完成后找不到启动项解决方案

    很多用于当SQL Server2017 安装完成后开始菜单找不到启动项无法启动SQL Server2017 其实你只需要安装一下SSMS-Setup-CHS就可以了 安装完成之后就有了 SSMS-Se ...

  8. LNMP 源码发布Thinksaas论坛

    第一步:搭建LNMP架构 LNMP架构 注意:搭建php服务时,初始化 ./configure --prefix=/usr/local/php5 \ --enable-fpm \ --enable-d ...

  9. AutoCad 二次开发 jig操作之标注跟随线移动

    AutoCad 二次开发 jig操作之标注跟随线移动 在autocad当中,我认为的jig操作的意思就是即时绘图的意思,它能够实时的显示出当前的操作,以便我们直观的感受到当前的绘图操作是什么样子会有什 ...

  10. mac安装jupyter

    SaintKings-Mac-mini:.pip saintking$ pip install jupyter --user Collecting jupyter Downloading jupyte ...