首页 > 其他分享 >处理异常的13条军规

处理异常的13条军规

时间:2024-10-24 18:48:18浏览次数:5  
标签:fis 13 doSomething Long try 军规 catch 异常

前言

在我们日常工作中,经常会遇到一些异常,比如:NullPointerException、NumberFormatException、ClassCastException等等。

那么问题来了,我们该如何处理异常,让代码变得更优雅呢?

1 不要忽略异常

不知道你有没有遇到过下面这段代码:

反例:

Long id = null;
try {
   id = Long.parseLong(keyword);
} catch(NumberFormatException e) {
  //忽略异常
}

用户输入的参数,使用Long.parseLong方法转换成Long类型的过程中,如果出现了异常,则使用try/catch直接忽略了异常。

并且也没有打印任何日志。

如果后面线上代码出现了问题,有点不太好排查问题。

建议大家不要忽略异常,在后续的工作中,可能会带来很多麻烦。

正例:

Long id = null;
try {
   id = Long.parseLong(keyword);
} catch(NumberFormatException e) {
  log.info(String.format("keyword:{} 转换成Long类型失败,原因:{}",keyword , e))
}

后面如果数据转换出现问题,从日志中我们一眼就可以查到具体原因了。

2 使用全局异常处理器

有些小伙伴,经常喜欢在Service代码中捕获异常。

不管是普通异常Exception,还是运行时异常RuntimeException,都使用try/catch把它们捕获。

反例:

try {
  checkParam(param);
} catch (BusinessException e) {
  return ApiResultUtil.error(1,"参数错误");
}

在每个Controller类中都捕获异常。

在UserController、MenuController、RoleController、JobController等等,都有上面的这段代码。

显然这种做法会造成大量重复的代码。

我们在Controller、Service等业务代码中,尽可能少捕获异常。

这种业务异常处理,应该交给拦截器统一处理。

在SpringBoot中可以使用@RestControllerAdvice注解,定义一个全局的异常处理handler,然后使用@ExceptionHandler注解在方法上处理异常。

例如:

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 统一处理异常
     *
     * @param e 异常
     * @return API请求响应实体
     */
    @ExceptionHandler(Exception.class)
    public ApiResult handleException(Exception e) {
        if (e instanceof BusinessException) {
            BusinessException businessException = (BusinessException) e;
            log.info("请求出现业务异常:", e);
            return ApiResultUtil.error(businessException.getCode(), businessException.getMessage());
        } 
        log.error("请求出现系统异常:", e);
        return ApiResultUtil.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务器内部错误,请联系系统管理员!");
    }

}

有了这个全局的异常处理器,之前我们在Controller或者Service中的try/catch代码可以去掉。

如果在接口中出现异常,全局的异常处理器会帮我们封装结果,返回给用户。

3 尽可能捕获具体异常

在你的业务逻辑方法中,有可能需要去处理多种不同的异常。

你可能你会觉得比较麻烦,而直接捕获Exception。

反例:

try {
   doSomething();
} catch(Exception e) {
  log.error("doSomething处理失败,原因:",e);
}

这样捕获异常太笼统了。

其实doSomething方法中,会抛出FileNotFoundException和IOException。

这种情况我们最好捕获具体的异常,然后分别做处理。

正例:

try {
   doSomething();
} catch(FileNotFoundException e) {
  log.error("doSomething处理失败,文件找不到,原因:",e);
} catch(IOException e) {
  log.error("doSomething处理失败,IO出现了异常,原因:",e);
}

这样如果后面出现了上面的异常,我们就非常方便知道是什么原因了。

4 在finally中关闭IO流

我们在使用IO流的时候,用完了之后,一般需要及时关闭,否则会浪费系统资源。

我们需要在try/catch中处理IO流,因为可能会出现IO异常。

反例:

try {
    File file = new File("/tmp/1.txt");
    FileInputStream fis = new FileInputStream(file);
    byte[] data = new byte[(int) file.length()];
    fis.read(data);
    for (byte b : data) {
        System.out.println(b);
    }
    fis.close();
} catch (IOException e) {
    log.error("读取文件失败,原因:",e)
}

上面的代码直接在try的代码块中关闭fis。

假如在调用fis.read方法时,出现了IO异常,则可能会直接抛异常,进入catch代码块中,而此时fis.close方法没办法执行,也就是说这种情况下,无法正确关闭IO流。

正例:

FileInputStream fis = null;
try {
    File file = new File("/tmp/1.txt");
    fis = new FileInputStream(file);
    byte[] data = new byte[(int) file.length()];
    fis.read(data);
    for (byte b : data) {
        System.out.println(b);
    } 
} catch (IOException e) {
    log.error("读取文件失败,原因:",e)
} finally {
   if(fis != null) {
      try {
          fis.close();
          fis = null;
      } catch (IOException e) {
          log.error("读取文件后关闭IO流失败,原因:",e)
      }
   }
}

在finally代码块中关闭IO流。

但要先判断fis不为空,否则在执行fis.close()方法时,可能会出现NullPointerException异常。

需要注意的地方时,在调用fis.close()方法时,也可能会抛异常,我们还需要进行try/catch处理。

5 多用try-catch-resource

前面在finally代码块中关闭IO流,还是觉得有点麻烦。

因此在JDK7之后,出现了一种新的语法糖try-with-resource。

上面的代码可以改造成这样的:

File file = new File("/tmp/1.txt");
try (FileInputStream fis = new FileInputStream(file)) {
    byte[] data = new byte[(int) file.length()];
    fis.read(data);
    for (byte b : data) {
        System.out.println(b);
    }
} catch (IOException e) {
    e.printStackTrace();
    log.error("读取文件失败,原因:",e)
}

try括号里头的FileInputStream实现了一个AutoCloseable接口,所以无论这段代码是正常执行完,还是有异常往外抛,还是内部代码块发生异常被截获,最终都会自动关闭IO流。

我们尽量多用try-catch-resource的语法关闭IO流,可以少写一些finally中的代码。

而且在finally代码块中关闭IO流,有顺序的问题,如果有多种IO,关闭的顺序不对,可能会导致部分IO关闭失败。

而try-catch-resource就没有这个问题。

6 不在finally中return

我们在某个方法中,可能会有返回数据。

反例:

public int divide(int dividend, int divisor) {
    try {
        return dividend / divisor;
    } catch (ArithmeticException e) {
        // 异常处理
    } finally {
        return -1;
    }
}

上面的这个例子中,我们在finally代码块中返回了数据-1。

这样最后在divide方法返回时,会将dividend / divisor的值覆盖成-1,导致正常的结果也不对。

我们尽量不要在finally代码块中返回数据。

正解:

public int divide(int dividend, int divisor) {
    try {
        return dividend / divisor;
    } catch (ArithmeticException e) {
        // 异常处理
        return -1;
    }
}

如果dividend / divisor出现了异常,则在catch代码块中返回-1。

7 少用e.printStackTrace()

我们在本地开发中,喜欢使用e.printStackTrace()方法,将异常的堆栈跟踪信息输出到标准错误流中。

反例:

try {
   doSomething();
} catch(IOException e) {
  e.printStackTrace();
}

这种方式在本地确实容易定位问题。

但如果代码部署到了生产环境,可能会带来下面的问题:

  1. 可能会暴露敏感信息,如文件路径、用户名、密码等。
  2. 可能会影响程序的性能和稳定性。

正解:

try {
   doSomething();
} catch(IOException e) {
  log.error("doSomething处理失败,原因:",e);
}

我们要将异常信息记录到日志中,而不是保留给用户。

8 异常打印详细一点

我们在捕获了异常之后,需要把异常的相关信息记录到日志当中。

反例:

try {
   double b = 1/0;
} catch(ArithmeticException e) {
    log.error("处理失败,原因:",e.getMessage());
}

这个例子中使用e.getMessage()方法返回异常信息。

但执行结果为:

doSomething处理失败,原因:

这种情况异常信息根本没有打印出来。

我们应该把异常信息和堆栈都打印出来。

正例:

try {
   double b = 1/0;
} catch(ArithmeticException e) {
    log.error("处理失败,原因:",e);
}

执行结果:

doSomething处理失败,原因:
java.lang.ArithmeticException: / by zero
	at cn.net.susan.service.Test.main(Test.java:16)

将具体的异常,出现问题的代码和具体行数都打印出来。

9 别捕获了异常又马上抛出

有时候,我们为了记录日志,可能会对异常进行捕获,然后又抛出。

反例:

try {
  doSomething();
} catch(ArithmeticException e) {
  log.error("doSomething处理失败,原因:",e)
  throw e;
}

在调用doSomething方法时,如果出现了ArithmeticException异常,则先使用catch捕获,记录到日志中,然后使用throw关键抛出这个异常。

这个骚操作纯属是为了记录日志。

但最后发现日志记录两次。

因为在后续的处理中,可能会将这个ArithmeticException异常又记录一次。

这样就会导致日志重复记录了。

10 优先使用标准异常

在Java中已经定义了许多比较常用的标准异常,比如下面这张图中列出的这些异常:

反例:

public void checkValue(int value) {
    if (value < 0) {
        throw new MyIllegalArgumentException("值不能为负");
    }
}

自定义了一个异常表示参数错误。

其实,我们可以直接复用已有的标准异常。

正例:

public void checkValue(int value) {
    if (value < 0) {
        throw new IllegalArgumentException("值不能为负");
    }
}

11 对异常进行文档说明

我们在写代码的过程中,有一个好习惯是给方法、参数和返回值,增加文档说明。

反例:

/*  
 *  处理用户数据
 *  @param value 用户输入参数
 *  @return 值 
 */
public int doSomething(String value) 
     throws BusinessException {
     //业务逻辑
     return 1;
}

这个doSomething方法,把方法、参数、返回值都加了文档说明,但异常没有加。

正解:

/*  
 *  处理用户数据
 *  @param value 用户输入参数
 *  @return 值
 *  @throws BusinessException 业务异常
 */
public int doSomething(String value) 
     throws BusinessException {
     //业务逻辑
     return 1;
}

抛出的异常,也需要增加文档说明。

12 别用异常控制程序的流程

我们有时候,在程序中使用异常来控制了程序的流程,这种做法其实是不对的。

反例:

Long id = null;
try {
   id = Long.parseLong(idStr);
} catch(NumberFormatException e) {
   id = 1001;
}

如果用户输入的idStr是Long类型,则将它转换成Long,然后赋值给id,否则id给默认值1001。

每次都需要try/catch还是比较影响系统性能的。

正例:

Long id = checkValueType(idStr) ? Long.parseLong(idStr) : 1001;

我们增加了一个checkValueType方法,判断idStr的值,如果是Long类型,则直接转换成Long,否则给默认值1001。

13 自定义异常

如果标准异常无法满足我们的业务需求,我们可以自定义异常。

例如:

/**
 * 业务异常
 *
 * @author 苏三
 * @date 2024/1/9 下午1:12
 */
@AllArgsConstructor
@Data
public class BusinessException extends RuntimeException {

    public static final long serialVersionUID = -6735897190745766939L;

    /**
     * 异常码
     */
    private int code;

    /**
     * 具体异常信息
     */
    private String message;

    public BusinessException() {
        super();
    }

    public BusinessException(String message) {
        this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
        this.message = message;
    }
}

对于这种自定义的业务异常,我们可以增加code和message这两个字段,code表示异常码,而message表示具体的异常信息。

BusinessException继承了RuntimeException运行时异常,后面处理起来更加灵活。

提供了多种构造方法。

定义了一个序列化ID(serialVersionUID)。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。

标签:fis,13,doSomething,Long,try,军规,catch,异常
From: https://www.cnblogs.com/12lisu/p/18500229

相关文章

  • CF1139C. Edgy Trees 题解 并查集
    题目链接:https://codeforces.com/problemset/problem/1139/C视频讲解:https://www.bilibili.com/video/BV1tZ1FYPELp?p=3我们可以求总方案数-不满足条件的方案数。设一个不包含黑色边的极大连通块的大小为\(sz_i\)。则答案为\[n^k-\sum\{sz_i^k\}\]示例程序:#include......
  • JavaWeb合集14-全局异常处理器
    十四、全局异常处理器全局异常处理器(GlobalExceptionHandler)是指一种机制,用于集中处理应用程序中未被捕获的异常。全局异常处理器可以用来统一处理整个应用程序中可能出现的异常,从而确保在出现未预期的错误时,程序能够以一种优雅的方式处理这些错误,并提供一致的错误响应......
  • WinDbg快速分析异常情况Dump文件
     https://syxdevcode.github.io/2017/12/04/WinDbg%E5%BF%AB%E9%80%9F%E5%88%86%E6%9E%90%E5%BC%82%E5%B8%B8%E6%83%85%E5%86%B5Dump%E6%96%87%E4%BB%B6/ WinDbg快速分析异常情况Dump文件生产环境偶尔会出现一些异常问题,WinDbg或GDB就是解决此类问题的利器。调试工具Win......
  • MT1371-MT1380 码题集 (c 语言详解)
    目录        MT1371·所有路径        MT1372·矩阵清零        MT1373·亲和数         MT1374·Pronic数         MT1375·4和7的序列        MT1376·小码哥的数学        MT1377·模乘逆元      ......
  • 数据库事务耗时过长导致Could not retrieve transaction read-only status from serve
    背景 [11-0602:02:09:005][ERROR]-DruidDataSource-discardconnectionjava.sql.SQLException:Couldnotretrievetransactionread-onlystatusfromserverCausedby:com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:Communicationslinkfailure......
  • 蓝桥杯EDA赛道经验分享(一)&12、13、14届省赛客观题知识点
    一、经验分享1.文件提取离线模式——>文件——>(大压缩包)导入专业版——>导入文件;(小压缩包)提取库文件。2.布线规则先根据参赛文件改布线规则(间距,线宽)。3.PCBlayout注意事项(1)避免重叠:确保元件间无物理重叠,为布线留出足够空间。(2)元件放置:大功率元件及发热元件应分散布局......
  • 0基础学java之Day13
    static理解:静态的作用:1.修饰属性类加载到方法区时,JVM会扫描该类的所有属性并把静态属性加载到静态区中,静态属性属于类属性,该类所有的对象都共享该属性静态属性直到项目结束时才会被回收2.修饰方法属于类方法,直接用类名调用应用场景:工具类3.修饰代码块静态代......
  • P3547 [POI2013] CEN-Price List
    很不错的图论题。考虑对\(a,2a,b\)大小进行讨论。\(2a\leb\),这种情况是简单的,根本不会走\(b\)边,直接bfs即可,此时答案为\(d*a\)。\(a\leb<2a\),这种情况下能走两条\(a\)边就会用\(b\)边替换掉,同时不用担心三元环的情况(因为三元环会出现三个点最短路都是\(1......
  • Java异常的处理:
    Java异常分成两个部分:“抛出异常”和"捕获异常"    Java异常处理机制是:将可能发生异常的语句写入try{}中,当try语句中发生异常时,系统会生成一个异常对象,该异常对象会提交给Java运行时环境,这个过程称为“抛出异常”。    当出现异常时,会去匹配可以处理异常的c......
  • 计算机毕业设计项目推荐,基于协同过滤算法的短视频推荐系统设计与实现30213(开题答辩+程
    摘 要现阶段,社会的发展和科技的进步,以及大数据时代下纷繁数据信息的融合,使得人们在生产及生活过程中,都将会接收到各种类型的数据信息,而通过计算机技术与网络技术,则能够将众多人们所不了解或不常用的信息,以简单的模式转化并传递给人们,使得人们的生产及生活质量得以显著提升......