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


参考 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. 怎样用JS给,option添加“选中”属性

    <html> <head> <script> window.onload = function(){ var opts = document.getElementB ...

  2. nyoj 61-传纸条(一)(双向dp)

    61-传纸条(一) 内存限制:64MB 时间限制:2000ms Special Judge: No accepted:8 submit:37 题目描述: 小渊和小轩是好朋友也是同班同学,他们在一起总有 ...

  3. python: __future__的介绍

    __future__ 给旧版本python提供新版本python的特性例如: 在python2.X中可以使用print"" 也可以使用print() 但是加载这个print的新特性 ...

  4. 【Oracle】Oracle数据库基本指标查看

    目录 1.查看表空间 2.查看用户 3.查看数据库内存 4.查看数据库版本 5.oracle归档情况 6.查看redo log日志位置 7.查看数据库的控制文件 8.查看RMAN的备份情况 9.FRA ...

  5. ArcGIS Desktop10.4安装教程

    准备内容 安装环境:win10*64位专业版 安装文件:ArcGIS_Desktop_1041_150996.iso 破解文件:10.4.1crackOnly 请都以管理员身份运行安装程序 安装步骤 ...

  6. MySQL基础知识面试与答案

    1.Mysql 的存储引擎,myisam和innodb的区别. 答: 1.MyISAM 是非事务的存储引擎,适合用于频繁查询的应用.表锁,不会出现死锁,适合小数据,小并发. 2.innodb是支持事务 ...

  7. react create-react-app使用less 及关闭eslint

    使用less和关闭eslint都需要先运行命令 npm run  eject 来暴露配置文件,(不可逆的) 一.less使用 运行命令安装less npm install less less-load ...

  8. python3 之 变量作用域详解

    作用域: 指命名空间可直接访问的python程序的文本区域,这里的 ‘可直接访问’ 意味着:对名称的引用(非限定),会尝试在命名空间中查找名称: L:local,局部作用域,即函数中定义的变量: E: ...

  9. 图解 Spring:HTTP 请求的处理流程与机制【2】

    2. HTTP 请求在 Web 容器中的处理流程 Web 容器以进程的方式在计算机上运行,我们知道进程是系统资源分配的最小单元,线程是系统任务执行的最小单元.从这个角度看,Web 容器就像是邮包收件人 ...

  10. MovibleNet

    MobileNet MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications MobileN ...