java异常处理规范(代码片段)

快乐崇拜234 快乐崇拜234     2022-11-30     708

关键词:

正文在下面,先打个广告:

文章目录

前面介绍了 日志打印规范, 如果想打印出合格的日志,还需要了解Java对异常处理的一些知识。

java 异常简介

先来看一下Java中异常的类图,该类图只是把常见的异常列了一下。更详细的请读者去看jdk源码。

先看一下Throwable ,它是所有errors和exceptions的超类。

 * The @code Throwable class is the superclass of all errors and
 * exceptions in the Java language. Only objects that are instances of this
 * class (or one of its subclasses) are thrown by the Java Virtual Machine or
 * can be thrown by the Java @code throw statement. Similarly, only
 * this class or one of its subclasses can be the argument type in a
 * @code catch clause.

再看一下Error
从Error类的注释中可以清晰的看到,Error是不应该try-catch的,Error的异常时不应该发生的系统性错误。它是unchecked exceptions

/**
 * An @code Error is a subclass of @code Throwable
 * that indicates serious problems that a reasonable application
 * should not try to catch. Most such errors are abnormal conditions.
 * The @code ThreadDeath error, though a "normal" condition,
 * is also a subclass of @code Error because most applications
 * should not try to catch it.
 * <p>
 * A method is not required to declare in its @code throws
 * clause any subclasses of @code Error that might be thrown
 * during the execution of the method but not caught, since these
 * errors are abnormal conditions that should never occur.
 *
 * That is, @code Error and its subclasses are regarded as unchecked
 * exceptions for the purposes of compile-time checking of exceptions.
 *
 */
public class Error extends Throwable 

再看一下Exception
Exception是我们平时工作中最常遇到的异常,是需要try-catch的。Exception和其所有子类(除RuntimeException及其子类)是checked exceptions

 * The class @code Exception and its subclasses are a form of
 * @code Throwable that indicates conditions that a reasonable
 * application might want to catch.
 *
 * <p>The class @code Exception and any subclasses that are not also
 * subclasses of @link RuntimeException are <em>checked
 * exceptions</em>.  Checked exceptions need to be declared in a
 * method or constructor's @code throws clause if they can be thrown
 * by the execution of the method or constructor and propagate outside
 * the method or constructor boundary.
 */
public class Exception extends Throwable 

最后看一下RuntimeException
RuntimeException及其子类是unchecked exceptions

/**
 * @code RuntimeException is the superclass of those
 * exceptions that can be thrown during the normal operation of the
 * Java Virtual Machine.
 *
 * <p>@code RuntimeException and its subclasses are <em>unchecked
 * exceptions</em>.  Unchecked exceptions do <em>not</em> need to be
 * declared in a method or constructor's @code throws clause if they
 * can be thrown by the execution of the method or constructor and
 * propagate outside the method or constructor boundary.
 */
public class RuntimeException extends Exception 

异常处理规范

  1. 【强制】不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如:IndexOutOfBoundsException / NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。
    正例: if(obj != null) …
    反例: try obj.method() catch(NullPointerException e)…

  2. 【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
    更加不要使用异常的msg信息来做业务校验,因为Java有实时的JIT编译优化,当频繁的报同一个异常时,异常msg信息会被忽略,导致判断有误。详见博客:JIT实时编译优化带来的问题:几千次异常以后取不到错误信息了

  3. 【强制】对大段代码进行 try-catch,这是不负责任的表现。 catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。
    正例:

map.put("status", 500);   
try
    //代码省略
    map.put("message", "success!");   
    map.put("status", 200);   
 catch (UnknownHostException e) 
    //域名错误
    map.put("message", "请输入正确的网址");
    log.error("*** 网址异常[]", url, e);
 catch (SocketTimeoutException e) 
    //超时
    map.put("message", "请求地址超时");
    log.error("*** 请求地址超时[]", url, e);
 catch (Exception e) 
    //其他异常
    map.put("message", "请求出现未知异常,请重试!\\r\\n" + e.getMessage());
    log.error("*** 请求出现未知异常,请重试![]", url, e);

return map;

反例, 要么什么都没做,要么只catch了Exception:

try 
   //此处省略1024行代码
 catch(Exception e)
   //TODO

  1. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
    反例:
try
    //代码省略
 catch (Exception e) 
    System.out.println("插入异常");//调用者对异常没有任何感知。而且日志输出只可以使用log框架,不要用sout


try
    //代码省略
 catch (UnknownHostException e) 
    throw new RuntimeException("500");//调用者对异常无法定位和判断

  1. 【强制】有 try 块放到了事务代码中, catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。当然,特殊的业务可能会要求有异常也要提交事务。不管怎么样,都要主动提交或回滚事务。
    曾经遇到过这样一个错误: 事务时开发人员自己控制的,但是没有处理好事务的提交与回滚,导致依赖了数据库驱动的默认提交策略。注意:当有未提交或为回滚的事务时,有的数据库连接池会默认回滚,有的(如Tomcat JDBC Pool)会默认提交。虽然说修改数据库连接池的默认策略可以解决一定的问题,但是千万不可强依赖。一定要手动提交或回滚事务·
try
    //代码省略
    conn.commit();
 catch (Exception e) 
	log.error("业务信息,错误信息,参数信息", e);
    conn.rollback();

如果你使用的是spring管理的事务,则务必添加上@Transactional(rollbackFor = Exception.class)

根据最开始的异常源码,可以将所有异常分为两类:

  • 可查的异常(checked exceptions):Exception下除了RuntimeException外的异常
  • 不可查的异常(unchecked exceptions):RuntimeException及其子类和错误(Error)
/**
 * <p>This annotation type is generally directly comparable to Spring's
 * @link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
 * class, and in fact @link AnnotationTransactionAttributeSource will directly
 * convert the data to the latter class, so that Spring's transaction support code
 * does not have to know about annotations. If no rules are relevant to the exception,
 * it will be treated like
 * @link org.springframework.transaction.interceptor.DefaultTransactionAttribute
 * (rolling back on @link RuntimeException and @link Error but not on checked
 * exceptions)
 */
@Target(ElementType.METHOD, ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional 

我们再来看一下Transactional的源码,从注释中可以清晰的看出,默认策略是所有的unchecked exceptions会回滚。

也就是说,当抛出checked exceptions时,事务是不会回滚的。如以下两个实例。虽然下面这两种给写法都是不合理的,尤其在事务中读文件,事务的范围应该尽可能小。但是一个团队中的开发人员能力肯定是参差不齐的,谁也无法保证不会写出这样的代码。这种代码应该在CR过程中发现,而不是为此导致事务不回滚,这可是大BUG。

@Transactional
public void demo() throws Exception 
    this.userRepository.save(new User(USERNAME));
    throw new Exception("No Rollback");


@Transactional
public void demo() throws FileNotFoundException
    new FileInputStream("文件地址");

结论: 如果方法没有声明异常,则不需要配置在 Spring 事务注解中配置 rollbackFor。但是为了保证万无一失,还是建议所有Transactional注解都指定rollbackFor = Exception.class

另外注意:如果异常被try{}catch{}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{}catch{throw Exception}。

  1. 【强制】 finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
    说明: 如果 JDK7,可以使用 try-with-resources 方法。
File file = new File("文件");
try (FileInputStream inputStream = new FileInputStream(file);) 
    // use the inputStream to read a file    
 catch (FileNotFoundException e) 
    log.error("业务标识, 文件[文件名称]不存在", filename);//这种非常明确的异常,有时候是可以不打印堆栈的
 catch (IOException e) 
    log.error("业务标识, 读取文件[文件名称]失败", filename, e);

  1. 【强制】不能在 finally 块中使用 return, finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。

  2. 【强制】捕获异常与抛出的异常,必须是完全匹配,或者捕获异常是抛异常的父类。而且打印的日志也要与对应异常内容相匹配。

  3. 【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。调用方需要进行 null 判断防止 NPE 问题。说明: 本规约明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败,运行时异常等场景返回 null 的情况。

  4. 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:

  • Java自动拆箱装箱的过程可能产生空指针
  • 据库的查询结果可能为 null。
  • 判断字符串为空使用isBlank,除非你认为空字符串是非空。
String str = "  ";
System.out.println(StringUtils.isEmpty(str));//false
System.out.println(StringUtils.isBlank(str));//true
  • 远程调用返回对象,一律要求进行 NPE 判断。
  • 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
  • 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
  1. 【推荐】在代码中使用“ 抛异常” 还是“ 返回错误码” ?对于公司外的 http/api 开放接口必须使用“ 错误码” ;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封装 isSuccess、 “ 错误码” 、 “ 错误简短信息” 。

说明: 关于 RPC 方法返回方式使用 Result 方式的理由:

  • 用抛异常返回方式,调用方如果没有捕获到就会产生运行时错误。
  • 如果不加栈信息,只是 new 自定义异常,加入自己的理解的 error message,对于调用端解决问题的帮助不会太多,result可以包含更多的信息。如果加了栈信息,在频繁调用出错的情况下,数据序列化和传输的性能损耗也是问题。
  1. 【推荐】定义时区分 unchecked / checked 异常,避免直接使用 RuntimeException 抛出,更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如: DaoException / ServiceException 等。

  2. 【参考】复制代码时,记得修改异常打印信息。

  3. 优先使用明确的异常。这样调用者可以更加清晰的了解你这个方法或者接口可能抛出的异常类型。不要什么都抛出exception异常。

public void doThis() throws NumberFormatException  ... 
  1. 对外的接口描述里,要写清楚什么情况会抛出什么样的异常,注释要完善
  2. 当有多个异常抛出的时候,有限捕获范围更小的,更明确的异常。
public void catchMostSpecificExceptionFirst() 
   try 
       doSomething("A message");
     catch (NumberFormatException e) 
        //记录日志
     catch (IllegalArgumentException e) 
        //记录日志
    

  1. 不要捕获Throwable。 Throwable是所有异常(Exception)和错误(Error)的父类,虽然它能在catch从句中使用,但永远都不要这样做!错误通常时有JVM抛出的,是致命的错误。
  2. 不要忽略异常。即使当前代码你需要cache住所有异常让其继续执行下去,也一定不要什么都不做,至少打印出日志,方便后续有问题排查。如下反例
public void doNotIgnoreExceptions() 
    try 
        // do something
     catch (NumberFormatException e) 
        // this will never happen
    

  1. 包裹某个异常的同时不要丢弃它原本的信息。很多时候,我们抛出的异常时自定义的异常。此时,务必将原异常的信息作为msg的一部分抛出。这样调用方才能知道到底是什么原因出错的。
    这一点也不是绝对的。这一条主要适用于系统内部,如果是对外提供的接口,那么有时候就需要进行一些异常的转换,典型的例子,我们都不希望将DB的错误抛给用户。

异常错误码

通常会把返回值封装成result, 类似这样的:


  "status": 200,
  "errorCode": "OK",
  "errorMsg": "OK",

我们就需要为其定义错误码。如

200 成功
400 参数错误
403 无权限
500 服务器异常
501数据获取失败
......
  • status: 通常参考 HTTP Status,这样门槛相对较低。熟悉 HTTP 的都知道,4xx 代表什么, 500 代表什么。这样可以复用 status,例如:400 代表参数错误,user.name.invalid 也是参数错误, user.email.invalid 也是参数错误,这样能够有效避免status的增长。
  • 同一类的status,统一处理。例如:400 为参数错误,前端可以将这一类状态码统一处理,例如:展示 errorMsg 内容。如需特殊处理,也可通过 errorCode 进行判断。
  • errorCode: 编写具体的错误信息,如:user.name.invalid (用户名无效) user.email.invalid (用户邮箱无效)。有的业务会有很多很多的errorcode , 客服或者开发人员在排查问题的时候首先就是要根据这个错误码来缩小排查范围,这是非常重要的,比如银行系统。
  • errorMsg:这里是提示给用户的文案,我们可以在controller response时通过 errorCode 作为key 配置在国际化文件中动态给 errorMsg 赋值。这样能够满足不同语言的文案要求。

参考

异常--常见处理方式,异常安全规范(代码片段)

异常1.C语言传统的处理错误的方式2.C++异常概念3.异常的使用3.1异常的抛出和捕获3.2异常的重新抛出3.3异常安全3.4异常规范4.自定义异常体系5.异常的优缺点1.C语言传统的处理错误的方式传统的错误处理机制:终止程序&#x... 查看详情

java处理exception的9个最佳实践,你做对了吗(代码片段)

在Java中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多数开发团队都会制定一些... 查看详情

java异常处理和最佳实践(含案例分析)(代码片段)

读完本文应该了解Java异常处理机制,当一个异常被抛出时,JVM会在当前的方法里寻找一个匹配的处理,如果没有找到,这个方法会强制结束并弹出当前栈帧,并且异常会重新抛给上层调用的方法(在调用方法帧)。作者:王迪(... 查看详情

异常处理(代码片段)

异常处理主要有两种形式的语法:(1)try---except---else(2)try--except--finally 类型一:#语法规范#当try语句执行出现错误,会自动匹配错误,如果匹配成功,会执行该条的语句。#如果没匹配到错误,就执行else‘‘‘try:语句1except... 查看详情

深入理解java虚拟机——异常处理指令(代码片段)

目录一、异常处理指令的概述二、异常处理指令(示例1)三、异常处理指令(示例2)一、异常处理指令的概述在Java程序中显示抛出异常的操作(throw语句)都由athrow指令来实现,除了用throw语句显示抛... 查看详情

项目总结项目开发规范(代码片段)

...ff09;4.4如何使用Http状态码和自定义code5.接口访问权限二、异常处理规范1.使用异常处理的方式(暂定)2.什么是使用异常处理?三、事务规范1.什么时候使用事务?四、日志规范1.什么时候使用日志?2.如何使用... 查看详情

java异常处理(代码片段)

查看详情

java初学——面向对象异常(代码片段)

一、异常  1.什么是异常    异常是指程序在运行是发生的不正常事件,会中断运行的程序称之为异常  2.什么是异常处理    Java编程语言使用异常处理机制为程序提供了错误处理的能力。一般处理顺序为:程序中... 查看详情

java中的异常处理机制(代码片段)

文章目录聊聊Java中的异常处理机制概念异常处理抛出异常异常跟踪栈:异常处理步骤:1.捕获异常2.处理异常3.回收资源Java的异常处理接口关于finallyfinally是无条件执行的吗?在finally中return会发生什么?聊聊Java中... 查看详情

处理exception的几种实践,很优雅,已被很多团队采纳!(代码片段)

来源:http://ww7.rowkey.me/在Java中处理异常并不是一个简单的事情。不仅仅初学者很难理解,即使一些有经验的开发者也需要花费很多时间来思考如何处理异常,包括需要处理哪些异常,怎样处理等等。这也是绝大多... 查看详情

java异常处理机制(代码片段)

目录1.1Java异常的概念与分类1.1.1引入异常(数组越界)1.1.2 Throwable1.2Java异常捕获处理1.3异常处理finally语句使用1.3.1引入finally关键字1.3.2 采用finally释放Scanner资源1.3Java异常抛出处理1.3.1主动抛出异常1.4自定义异常类1.5综... 查看详情

java异常处理机制(代码片段)

目录1.1Java异常的概念与分类1.1.1引入异常(数组越界)1.1.2 Throwable1.2Java异常捕获处理1.3异常处理finally语句使用1.3.1引入finally关键字1.3.2 采用finally释放Scanner资源1.3Java异常抛出处理1.3.1主动抛出异常1.4自定义异常类1.5综... 查看详情

java的异常处理机制(代码片段)

Java语言在设计之初就提供了相对完善的异常处理机制。我们首先介绍一下Java中的异常。介绍Java中的异常异常是程序在运行过程中出现的程序异常事件,异常会中断正在执行的正常指令流。Java中的异常分为两大类:Exception和Error... 查看详情

java异常处理总结(代码片段)

一、Java异常继承框架Error:Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。Exception:在Exc... 查看详情

重学springboot系列之统一全局异常处理(代码片段)

重学SpringBoot系列之统一全局异常处理设计一个优秀的异常处理机制异常处理的乱象例举该如何设计异常处理开发规范自定义异常和相关数据结构该如何设计数据结构枚举异常的类型自定义异常请求接口统一响应数据结构使用示... 查看详情

java异常处理(代码片段)

Java异常处理  异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。  比如说,你的代码少了一个分号,那么运行出来结果是提示是错误java.lang.Error;如果你用System.out.println(11/0),那么... 查看详情

java异常处理机制(代码片段)

1.异常是指在程序的运行过程中所发生的不正常的情况或者发生错误,导致中断正在运行的程序。2.异常处理语句:try-catch,如果try代码块中捕获到异常,则到catch代码块中处理,否则跳过忽略catch代码块。(良好的编程习惯,在异... 查看详情

java休息javaee异常处理(代码片段)

查看详情