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


参考 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. python高阶函数的使用

    目录 python高阶函数的使用 1.map 2.reduce 3.filter 4.sorted 5.小结 python高阶函数的使用 1.map Python内建了map()函数,map()函数接 ...

  2. 在lldb调试中调用c++函数

    在lldb调试时,调用oc对象的方法不足为奇,因为msgSend是有原型导出的,oc对象的方法都运行期绑定的,绑定信息都在objc_class中.只要在调试中[receiver sel]之类,lldb ...

  3. gRPC asp.net core自定义策略认证

    在GitHub上有个项目,本来是作为自己研究学习.net core的Demo,没想到很多同学在看,还给了很多星,所以觉得应该升成3.0,整理一下,写成博分享给学习.net core的同学们. 项目名称 ...

  4. 关于Java 值传递深度分析

    首先说观点:java只有值传递没有引用传递 然后再来看看值传递与引用传递两者的定义 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改, ...

  5. thinking in JAVA 编译记录

    编辑/编译<thinking in JAVA>源代码 一.下载源代码 首先,我阅读的是<thinking in JAVA>第四版,因此按照书中提供的链接找到了mindview主 ...

  6. Javascript ----函数表达和形参实参

    1.函数是对象,函数名实际上是函数对象的指针 1.函数声明方式 (函数声明提前) function sum(num1,num2){return num1+num2;} 2.函数表达式 var sums ...

  7. .NET高级特性-Emit(2)类的定义

    在上一篇博文发了一天左右的时间,就收到了博客园许多读者的评论和推荐,非常感谢,我也会及时回复读者的评论.之后我也将继续撰写博文,梳理相关.NET的知识,希望.NET的圈子能越来越大,开发者能了解/深入 ...

  8. 带你涨姿势的认识一下 Kafka 消费者

    之前我们介绍过了 Kafka 整体架构,Kafka 生产者,Kafka 生产的消息最终流向哪里呢?当然是需要消费了,要不只产生一系列数据没有任何作用啊,如果把 Kafka 比作餐厅的话,那么生产者就是 ...

  9. SQL语句实用技巧1

    --显示行号 select *, ROW_NUMBER() OVER(Order by TYPENAME ) AS RowNumber from ( select distinct TYPENAME ...

  10. Maven搭建SpringMvc

    Maven搭建SpringMvc,只需跟着一步步操作 项目结构 1 创建Maven项目 index,jsp报错不用管,配置完pom就好了,也可以直接删除掉 2 pom.xml添加依赖 <depe ...