百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

处理异常的标准姿势,你学废了吗?

qihemm 2025-06-13 09:30 5 浏览 0 评论

前言

在Java中应该如何处理异常,这个话题看似简单,不就是try...catch嘛,但是往往BUG更容易出现在一些简单的、容易忽略的地方。大多数成熟的开发团队对于如何进行异常处理都有一套规范和最佳实践。

本期内容我整理了一些在我的团队正在使用的9个最佳实践,希望能让你对异常处理有所帮助。

1、使用finally或try...with...resource关闭资源

如果我们在try代码块中需要使用到一些资源,比如InputStream,在使用完之后我们需要将资源关闭。

这是一个错误示例

public void tryResource() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./小黑说Java.txt");
        inputStream = new FileInputStream(file);

        // 使用inputStream读取文件

        // 不要这样做
        inputStream.close();
    } catch (FileNotFoundException e) {
        log.error("文件未找到", e);
    } catch (IOException e) {
        log.error("文件读取异常", e);
    }
}

在上面这段代码中,只要在文件读取时没有出现异常,这段代码是可以正常工作的,但是只要在try块中的close()方法中抛出异常,资源就不会被关闭。

所以这种情况我们应该将资源关闭的代码放在finally中或者使用try...with...resource语句。

使用finally

在finally块中的代码不管是否出现异常,都会被执行,因此可以确保资源对象被关闭。

public void closeResourceInFinally() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./小黑说Java.txt");
        inputStream = new FileInputStream(file);
        // 使用inputStream读取文件
    } catch (FileNotFoundException e) {
        log.error("文件未找到", e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error("资源关闭异常", e);
            }
        }
    }
}

使用try...with...resource

如果你使用的JDK版本是1.7+,那么也可以选择使用try...with...resource语句。如果你使用的资源类实现AutoCloseable接口,则可以使用这种方式。

Java中的大多数标准资源类API都实现了这个接口。在try子句中打开资源,将会在try代码块执行完毕或异常处理后自动关闭资源对象。

public void useTryWithResource() {
    File file = new File("./小黑说Java.txt");
    try (FileInputStream inputStream = new FileInputStream(file);) {
        // 使用inputStream读取文件

    } catch (FileNotFoundException e) {
        log.error("文件未找到", e);
    } catch (IOException e) {
        log.error("文件读取异常", e);
    }
}

2、使用更明确的异常

如果我们的方法需要向外抛出异常,那么异常类型越具体越好。因为在外部调用你代码的其他人对你内部的实现逻辑可能并不清楚,所以要确保能提供给他尽可能多的信息,可以让别人在使用你的方法时更容易理解,这样调用方可以更好地处理抛出的异常。

比如,在你的方法内容抛出NumberFormatException比抛出IllegalArgumentException或者直接抛出Exception,所代表的含义就会更明确。

3、方法注释中对异常进行说明

如果你的方法声明了可能会抛出异常,那么在方法的文档注释中,应该对异常进行说明。这和上一条的目的一样,都是为了让方法的调用者能提前获得更多的信息,方便他避免在调用你方法时出现异常,或者更明确如果进行异常处理。

所以,我们应该在方法的文档注释中添加@throws声明,并说明什么情况下会抛出对应的异常。

/**
 * 这个方法内部做了什么什么事情...
 *
 * @param input
 * @throws BusinessException 如果出现xxx情况,则会抛出这个异常
 */
public void doSomething(String input) throws BusinessException {
    ...
}

4、在异常中携带足够的描述信息

这一点和前两条做法的目的类似。在异常中携带足够的描述信息,是为了在出现该异常时,能够在日志文件中查看异常信息时,能看到更有用的信息。

所以我们应该尽可能准确地描述出为什么抛出了这个异常,并提供最相关的数据信息让别人定位。

当然这里也不能太极端,你洋洋洒洒写一篇小作文,应该使用简短的一段信息描述,让运维同事能了解到这个问题的严重性,更轻松地分析问题所在。

也不用提供一堆额外的冗余信息,尽量做到足够精准。比如当你再创建一个Long对象时如果传入一个字符串,就会抛出NumberFormatException

public static void testLong() {
    try {
        Long abc = new Long("ABC");
    } catch (NumberFormatException e) {
        log.error("格式异常", e);
    }
}

NumberFormatException的类名已经告诉我们出现的是数字格式化异常,所以在message中只需要提供输入的字符串。如果你定义的异常类名不能很明确的表达出是什么异常,比如BusinessException,你就应该在message中表达出更多的信息。

5、先捕获更明确的异常

一般在我们使用的IDE中,如果当你在做异常捕获时,先捕获了不太具体的异常比如Exception,然后再捕获更具体的异常如IOException,都会提示我们后面的catch块无法到达。所以我们应该先捕获最具体的异常类,将不太具体的异常类的捕获放在最后。

public void catchExceptions() {
    try {
        doSomething("小黑说Java");
    } catch (NumberFormatException e) {
        log.error("格式异常", e);
    } catch (IllegalArgumentException e) {
        log.error("非法参数", e);
    }
}

6、不要捕获Throwable

Throwable是所有ExceptionError的父类。

虽然可以在catch块中捕获它,但是我们不应该这样去做。因为如果使用了Throwable,那么不仅会对所有抛出的Exception进行捕获,还会捕获所有的Error

而当我们的程序抛出Error时表示是一个无法处理的严重问题,例如典型的OutofMemoryErrorStackOverflowError等,这两个Error都是由程序无法控制并且不能处理的情况引起的。所以说,最好不要在你的catch中捕获Throwable,除非你非常确定try块中的代码抛出的是可以处理的异常情况。

public void catchThrowable() {
    try {
  // 一些业务代码
    } catch (Throwable t) {
        // 不要这样做
    }
}

7、不要将异常忽略

在你开发的时候可能非常确定不会抛出异常,并且在你开发时确实没有发生过抛出异常的情况,所以你在catch块中没有对异常做任何处理。

public void doNotIgnoreExceptions() {
    try {
  // 一些业务代码
    } catch (NumberFormatException e) {
        // 认为永远不会执行到这里
    }
}

但是,你其实不确定在将来会不会有人在你的try块中添加新的代码,并且他可能也不会意识到他添加的代码会导致有异常抛出,这将会导致在线上真的有异常产生,但是没有一个人知道。

所以,你至少应该在catch中打印一行日志,告诉运维同事,“警报,这里出现了一个不可能会出现的异常”。

public void doNotIgnoreExceptions() {
    try {
  // 一些业务代码
    } catch (NumberFormatException e) {
  log.error("警报,这里出现了一个不可能会出现的异常", e);
    }
}

8、不要打印日志后又将异常抛出

这一条可能绝大多数人都会犯过,我见过非常多别人的代码在异常处理时,先打印了一行异常日志,然后将异常抛出,或者转成一个RuntimeException抛出。

甚至在一些开源框架中都有出现过。

public void testCatchEx() {
    try {
        new Long("heihei");
    } catch (NumberFormatException e) {
        log.error("数字格式异常", e);
        throw e;
    }
}

你可能会认为这样做很直观,也没什么错,让调用你方法的人去处理就好了。但是这样一来,在日志中会对抛出的一个异常打印多条错误信息。

重复的日志并没有带来任何有价值的信息,参考上面第4条中描述,在异常信息中应该携带足够的信息,并且要做到精准。如果需要在添加其他信息,你应该将捕获到的异常封装在你的自定义异常中再进行抛出。

public void wrapException(String input) throws BusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new BusinessException("一段对异常的描述信息.", e);
    }
}

所以,我们应该只有在想对异常进行处理时捕获,否则就应该在抛出去,并且在方法前面上加以说明,让调用方去处理。

9、在包装异常时使用原始异常

通常在项目开发时,都会有一套自定义的异常,用于将API中的标准异常封装到自定义异常中,可以用于在外层做一些统一的异常处理。

但是我们在使用自定义异常对原始异常进行封装时,需要确保将原始异常作为cause保存在自定义异常中,否则你在外层将会丢失原始异常的堆栈跟踪信息,到你你无法通过异常信息分析抛出异常的具体原因。

public void wrapException(String input) throws BusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        // 将e作为构造参数中的cause
        throw new MyBusinessException("一段对异常的描述信息.", e);
    }
}

总结

在抛出或者捕获异常时,我们应该考虑很多不同的事情,上面所说的大多数都是为了提高代码的可读性和提供给别人的API更易用。

异常不仅是一种错误处理机制,同时还具备一定的信息传递作用。我们应该遵循这些异常处理的规则和最佳实践,写出更规范、不被别人吐槽的好代码。

相关推荐

VLOOKUP的18种高阶用法大公开!99%的人都不知道的神操作!

作为被头条用户催更的Excel课代表,今天带来让HR追着要模板、让老板主动加薪的VLOOKUP终极指南!从基础到高阶一网打尽,文末送36个行业专用模板!一、为什么你的VLOOKUP总报错?血泪大数据...

Vlooup公式,2种模糊查找匹配,1分钟学会

工作中,VLOOKUP公式使用频率是很高的,用来各种查找匹配问题今天我们分享两种模糊查找匹配问题,一种是文本的模糊查找匹配,一种是数字的模糊查找匹配问题1、文本模糊查找匹配使用模拟数据举个例子,原始数...

与vlookup功能相似的函数,照样搞定表格数据查询,简单还实用

在日常表格数据处理工作,说到数据查询,很多小伙伴首先想到的是Vlookup函数,老师的教程中也多次讲到Vlookup函数的用法和实例。其实在Excel中还有其他的数据查询函数公式或技巧,今天我们先来学...

别再折腾VLOOKUP了!DGET逆向查找10秒通关,小白必看

今天要掀翻一个“过气网红”——VLOOKUP!你是不是也经历过这些崩溃瞬间:逆向查找要交换列顺序,复制粘贴到手软!多条件查找要嵌套MATCH,公式长到怀疑人生!别忍了!今天教你用DGET函数一键封...

职场新人必学!VLOOKUP函数10分钟速成指南

正文:"今天来讲解办公人入职期初函数VLOOKUP,这是所有职场人最重要也是最基础的技能。掌握它,90%的数据查找再不用求人!特别献给刚入职场的你——别让Excel成为加班理由。"——...

巧用Vlookup函数揪出“第三者”(vlookup第三个参数是什么)

在一张Excel表格的重复记录中,让你快速列出每种不同物品第2次或第n次出现的记录,你会怎么做?Vlookup函数就有这个本事。举例来说,产品或者物流表格中往往会记录有同一货物的多笔数据(如下图的今日...

分享12个VLOOKUP超经典用法(vlookup通俗易懂)

刚毕业那会,面试的时候经常会被问到会不会用Excel?我就理直气壮地回答:“会啊。”毕竟,简历上可是写着熟练。接着面试官扔出一句“那你会VLOOKUP吗?”我还是会一口咬定:“我会。“其实,我都没用过...

查找匹配别只知道Vlookup,Sumifs也可以!

工作中遇到查找匹配问题的时候,大家第一反应是不是都想到的Vlookup公式呢,有没有小伙伴们给Sumifs一点点机会的呢,有时候Sumifs比Vlookup更好用1、Vlookup公式举个例子,左边是...

Excel函数讲解:VLOOKUP函数,轻松玩转数据查找

常用函数系列教学:VLOOKUP函数讲解(46)。不懂VLOOKUP函数怎么高效查找数据?闲话少叙直接开讲。基本含义:VLOOKUP函数用于在表格按垂直方向(到)上查找返回行数据。如何使用及注意事项?...

CHOOSEROWS+CHOOSECOLS原来是一个超级查找函数组合!

场景一:要在学生名册中,抽查一名学生成绩。公式:=CHOOSEROWS(A1:D5,2)解析:第一参数A1:D5为数据区域,第二参数2表示提取第2行数据。把数据区域改为A2:D5,结合RANDBETW...

数据查询不止有vlookup函数,自定义zlookup函数查询操作更高效

Excel数据查询,相信大家首先会想到vlookup函数。毋庸置疑vlookup函数在Excel数据查询中作用是非常的强大。但是它也有一些不能实现的数据查询。如上图所示,我们需要根据人员的出现次数,提...

「EXCEL进阶」VLOOKUP函数怎么查询一个值返回多个结果

前言:VLOOKUP函数一般一次只能返回一个结果,本例介绍通过辅助列的方法使VLOOKUP函数查询一个值,返回这个值对应的多个结果。使用场景举例:根据表格中同一数值,返回对应值的多个结果。比如这张数据...

WPS查找能手VLOOKUP函数使用方法讲解

各位同学好!今天我们来深度剖析WPS最实用的查找工具——VLOOKUP函数。这个函数能帮你在表格中快速定位并提取所需数据,可以帮你快速核对两批数据差异,还可以合并多个表格的关联信息,甚至可以帮你制作动...

Excel常用10个函数:跨表查找Vlookup,适用于大数据中查找精确值

Hello大家好,我是Office米,今天,我们将和大家一起分享交流,常用的10个函数之一:查找引用函数VLOOKUP。在说VLOOKUP函数之前,我们要先了解,平时Excel日常工作中会遇到哪些问题...

掌握了这个套路,无论用 Excel vlookup 函数查找第几次结果都很轻松

用vlookup查找默认情况下是一对一出结果,如果要一对多查找,就需要用到各种技巧,具体方法我写过非常多了,可以搜索一下历史记录。只要掌握了今天这个套路,无论你想查找第几次重复值,都易如反掌。案例...

取消回复欢迎 发表评论: