阅读更多

0顶
0踩

编程语言

原创新闻 Java中处理异常的9个最佳实践

2017-08-25 17:14 by 副主编 jihong10102006 评论(0) 有8161人浏览
引用
原文:9 Best Practices to Handle Exceptions in Java
作者:Thorben Janssen
译者:Teixeira10

【译者注】在本文中,作者介绍了9个处理异常的最佳方法与实践,以举例与代码展示结合的方式,让开发者更好的理解这9种方式,并指导读者在不同情况下选择不同的异常处理方式。
以下为译文:

Java中的异常处理不是一个简单的话题。初学者很难理解,甚至有经验的开发人员也会花几个小时来讨论应该如何抛出或处理这些异常。

这就是为什么大多数开发团队都有自己的异常处理的规则和方法。如果你是一个团队的新手,你可能会惊讶于这些方法与你之前使用过的那些方法有多么不同。

然而,有几种异常处理的最佳方法被大多数开发团队所使用。下面是帮助改进异常处理的9个最重要的方法。

1. 在Finally中清理资源或者使用Try-With-Resource语句

通常情况下,你在try中使用了一个资源,比如InputStream,之后需要关闭它。在这种情况下,一个常见的错误是在try的末尾关闭了资源。
public void doNotCloseResourceInTry() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        // use the inputStream to read a file
        // do NOT do this
        inputStream.close();
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}


问题是,只要不抛出异常,这种方法就可以很好地运行。try内的所有语句都将被执行,资源也会被关闭。

但是你在try里调用了一个或多个可能抛出异常的方法,或者自己抛出异常。这意味着可能无法到达try的末尾。因此,将不会关闭这些资源。

所以应该将清理资源的代码放入Finally中,或者使用Try-With-Resource语句。

使用Finally

相比于try,无论是在成功执行try里的代码后,或是在catch中处理了一个异常后,Finally里的内容是一定会被执行的。因此,可以确保清理所有已打开的资源。
public void closeResourceInFinally() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error(e);
            }
        }
    }
}

Java 7的Try-With-Resource语句

另一个选择是Try-With-Resource语句,在introduction to Java exception handling中更详细地说明了这一点。

如果你的资源实现了AutoCloseable接口,就可以使用它,这正是大多数Java标准资源所做的。当你在try子句中打开资源时,它将在try被执行后自动关闭,或者处理一个异常。
public void automaticallyCloseResource() {
    File file = new File("./tmp.txt");
    try (FileInputStream inputStream = new FileInputStream(file);) {
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}

2. 给出准确的异常处理信息

你抛出的异常越具体越好。一定要记住,一个不太了解你代码的同事,也许几个月后,需要调用你的方法,并且处理这个异常。

因此,请确保提供尽可能多的信息,这会使你的API更容易理解。因此,你方法的调用者将能够更好地处理异常,或者通过额外的检查来避免它。

所以,要尽量能更好地描述你的异常处理信息,比如用NumberFormatException代替IllegalArgumentException,避免抛出一个不具体的异常。
public void doNotDoThis() throws Exception {
    ...
}
public void doThis() throws NumberFormatException {
    ...
}

3. 记录你所指定的异常

当你在方法中指定一个异常时,你应该在Javadoc中记录下它。这与前面提到的方法有着相同的目标:为调用者提供尽可能多的信息,这样他们就可以避免异常或者更容易地处理异常。

因此,请确保在Javadoc中添加一个@throws 声明,并描述可能导致的异常情况。
/**
 * This method does something extremely useful ...
 *
 * @param input
 * @throws MyBusinessException if ... happens
 */
public void doSomething(String input) throws MyBusinessException {
    ...
}

4. 使用描述性消息抛出异常

这一最佳实践的理念与前两个相似。但这一次,你不用给调用方法的人提供信息。异常消息会被所有人读取,同时必须了解在日志文件或监视工具中报告异常时发生了什么。

因此,应该尽可能准确地描述问题,并提供相关的信息来了解异常事件。

别误会,你不需要写一段文字,而是应该用1-2个简短的句子解释异常的原因。这可以帮助开发团队理解问题的严重性,同时也使你能够更容易地分析任何服务事件。

如果抛出一个特定的异常,它的类名很可能已经描述了这种类型的错误。所以,你不需要提供很多额外的信息。一个很好的例子就是,当你以错误的格式使用字符串时,如NumberFormatException,它就会被类 java.lang.Long的构造函数抛出。
try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
}

NumberFormatException已经告诉你问题的类型,所以只需要提供导致问题的输入字符串。如果异常类的名称不具有表达性,那么就需要提供必要的解释信息。
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"

5. 最先捕获特定的异常

大多数IDE都可以帮助你做到这点,当你试图捕获不确定的异常时,它会报告一个不可到达的代码块。

问题是只有第一个匹配到异常的catch语句才会被执行,所以,如果你最先发现IllegalArgumentException,你将永远不会到达catch里处理更具体的NumberFormatException,因为它是IllegalArgumentException的一个子类。

所以要首先捕获特定的异常类,并在末尾添加一些处理不是很具体异常的catch语句。

你可以在下面的代码片段中看到这样一个try-catch语句的示例。第一个catch处理所有NumberFormatExceptions异常,第二个catch 处理NumberFormatException异常以外的illegalargumentexception异常。
public void catchMostSpecificExceptionFirst() {
    try {
        doSomething("A message");
    } catch (NumberFormatException e) {
        log.error(e);
    } catch (IllegalArgumentException e) {
        log.error(e)
    }
}

6. 不要在catch中使用Throwable

Throwable是exceptions 和 errors的父类。当然,你可以在catch子句中使用它,但其实你不应该这样做。

如果你在catch子句中使用Throwable,它将不仅捕获所有的异常,还会捕获所有错误。JVM会抛出错误,这是应用程序不打算处理的严重问题。典型的例子是OutOfMemoryErrorStackOverflowError。这两种情况都是由应用程序控制之外的情况引起的,无法处理。

所以,最好不要在catch中使用Throwable,除非你完全确定自己处于一个特殊的情况下,并且你需要处理一个错误。
public void doNotCatchThrowable() {
    try {
        // do something
    } catch (Throwable t) {
        // don't do this!
    }
}

7. 不要忽略Exceptions

你是否曾经分析过只有用例的第一部分才被执行的bug报告吗?

这通常是由一个被忽略的异常引起的。开发人员可能非常确信它不会被抛出,并添加一个无法处理或无法记录它的catch语句。当你发现它的时候,你很可能就会明白一句著名的话“This will never happen”。
public void doNotIgnoreExceptions() {
    try {
        // do something
    } catch (NumberFormatException e) {
        // this will never happen
    }
}

是的,你可能在分析一个不可能发生的问题。

所以,请千万不要忽略一个例外。你不会知道代码在将来会发生什么变化。有些人可能会删除阻止异常事件的验证,而没有意识到这造成了问题。或者抛出异常的代码被更改,现在抛出了同一个类的多个异常,而调用的代码并不能阻止所有这些异常。

你至少应该写一个日志信息,告诉每个人,需要检查一下这个问题。
public void logAnException() {
    try {
        // do something
    } catch (NumberFormatException e) {
        log.error("This should never happen: " + e);
    }
}

8. 不要记录和抛出一个异常

这可能是最常被忽略的。你可以在许多代码片段或者库文件里发现,有异常会被捕获、记录和重新抛出。
try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
    throw e;
}

当它发生时记录一个异常,然后重新抛出它,以便调用者能够适当地处理它,这可能会很直观。但是它会为同一个异常写多个错误消息。
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)

不添加任何额外的信息。正如在上述第4个中所解释的那样,异常消息应该描述异常事件。堆栈会告诉你在哪个类、方法和行中异常被抛出。

如果你需要添加额外的信息,应该捕获异常并将其包装在一个自定义的信息中。但要确保遵循下面的第9条。
public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

因此,只需要捕获一个你想要处理的异常,在方法中指定它,并让调用者处理它。

9. 包装异常

有时最好捕获一个标准异常并将其封装到一个定制的异常中。此类异常的典型例子是应用程序或框架特定的业务异常。这允许你添加额外的信息,并且也可以为异常类实现一个特殊的处理。

当你这样做时,确保引用原始的异常处理。Exception类提供了一些特定的构造函数方法,这些方法可以接受Throwable作为参数。否则,你将丢失原始异常的堆栈跟踪和消息,这将使你很难分析导致异常的事件。
public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

总结

正如你所看到的,在抛出或捕获异常时,有许多不同的事情需要考虑。以上大多数方法都可以提高代码可读性或API可用性。

异常通常是一个错误处理机制和一个通信媒介。因此,你应该确保同事一起讨论想要应用的最佳实践和方法,以便每个人都理解通用概念并以相同的方式使用它们。
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • ChatGPT技术原理解析:从RL之PPO算法、RLHF到GPT4、instructGPT

    一方面,对于想了解ChatGPT背后原理和如何发展而来的,逐一阐述从GPT/GPT2/GPT3到强化学习、PPO算法,最后再到instructGPT、ChatGPT、SeqGAN 且本文之前,99%的文章都不会把PPO算法从头推到尾,本文会把PPO从零推到...

  • LLaMA的解读与其微调(含LLaMA 2):Alpaca-LoRA/Vicuna/BELLE/中文LLaMA/姜子牙

    还开始研究一系列开源模型(包括各自对应的模型架构、训练方法、训练数据、本地私有化部署、硬件配置要求、微调等细节)​该项目部分一开始是作为此文《》的第4部分,但但随着研究深入 为避免该文篇幅又过长,将把『第...

  • NLP之GPT-3:《 Language Models are Few-Shot Learners》的翻译与解读

    NLP之GPT-3:《 Language Models are Few-Shot Learners》的翻译与解读 目录 相关文章 《GPT-3: Language Models are Few-Shot Learners》的翻译与解读 Abstract 摘要 1 Introduction 介绍 2 Approach方法 ...

  • [C++]-日志记录库SPDLog简介

    文章目录spdlog库日志记录槽sink日志记录器logger输出格式pattern对齐方式截断字符串格式化fmtFormat Specificationspdlog使用异常处理logger基础用法stdout日志文件日志基本文件循环文件每日文件示例 spdlog是一款...

  • react 图片剪切(react-easy-crop)

    return ( , width: 300, height: 300 }}> <Cropper image={yourImage} crop={crop} zoom={zoom} aspect={4 / 3} onCropChange={setCrop} onCropComplete={onCropComplete} ...

  • DC-1靶机渗透测试记录

    攻击机 kali-linux-2020.4-vbox-i386 设置 USB设备关闭,网络连接方式仅主机(Host-Only)网络。 攻击机IP 192.168.56.103 步骤1 靶机目标发现 因为靶机和攻击机在同一个网络内,所以使用KALI上arp-scan -l 进行...

  • LOG4J2 异步日志

    Making All Loggers Asynchronous Requires disruptor-3.0.0.jar or higher on the classpath. Future versions of Log4j 2 will require disruptor-3.3.3.jar or higher. This is simplest to configure and

  • 分布式事物框架Easy-Transaction--使用入门介绍

    分布式事物框架Easy-Transaction--使用入门介绍   The origin This framework is inspired by a PPT (<大规模SOA系统的分布式事务处理>) written by Cheng Li who works in Alipay This ...

  • 【愚公系列】2023年05月 攻防世界-MOBILE(easy-dex)

    // [sp+124h] [bp-3Ch] destLen = 0x100000; dest = (Bytef *)malloc(0x100000u); v2 = off_43A18; v3 = (char *)malloc((size_t)off_43A18); qmemcpy(v3, &unk_7004, (size_t)v2); *(_DWORD *)filename = -...

  • Unity插件学习(五) ------ 本地存储Easy Save3

    使用方法1.[Easy Save3存储支持的类型](https://docs.moodkie.com/easy-save-3/es3-supported-types/)2.设置3.Keys, Paths and LocationsFilePlayerPrefsResourcesMemory5.加密6.保存并加载字符串和字节到文件7.保存...

  • Yocto系列讲解[入门篇] 1 - 快速入门熟悉Yocto的构建

    utils debianutils iputils-ping python3-git python3-jinja2 libegl1-mesa libsdl1.2-dev pylint3 xterm python3-subunit mesa-common-dev zstd liblz4-tool -y 下载yocto的poky项目: 环境准备好之后开始下载源...

  • Log4j2介绍和特性实例(一)

    Log4j是Apache的著名项目,随着...在log4j2发布前,应经有了logback和SLF4J,功能也是非常强大,那么为什么作者还要发布log4j2呢?在《log4j-users-guide》对这个问题做了解释。 Its alternative, SLF4J/Logback made

  • Centos7下的NS-3安装与配置总结(超详细!超完整!)

    1 NS-3概述 2 平台安装 2.1安装ns-3的依赖环境 2.2Downloading ns-3 3 Eclipse配置 3.1 安装Eclipse 3.2 安装cdt 3.3 配置 4 脚本运行 4.1 终端编译运行 4.2 Eclipse编译运行 5 可视化界面 5.1 PyViz...

  • vue 项目进行直播视频 vue-video-player

    值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3") playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度 0.7倍... sources: [ // 播放的资源列表,虽然不知为何是个数组,可能是,轮流播放? { type: '...

  • js-beautify-html配置,js-beautify

    beautifier.io for nodeJS Beautifier This little beautifier will reformat and re-indent bookmarklets, uglyJavaScript, unpack scripts packed by Dean Edward’s popular packer,as well as partly ...

  • docker-compose启动服务

    it is possible to specify # it in the usual form of 1k 5GB 4M and so forth: # # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 1000000000 bytes # 1gb => ...

  • LARC DL笔记(三):finetune googlenet on food-101 VS baseline

    0x00 objectfood101数据集使用Googlenet train好了与baseline比较0x01 有用信息(1)loss很低,但是accuracy一直在50%左右一般是什么原因 如果loss一直降低而你的validation accuracy不升高的话,就是overfit了 (2...

  • [HTB]HackTheBox-Easy-Secret国外渗透实战靶场

    29 0-4e5547295cfe456d8ca7005cb823e1101fd1f9cb drwxr-xr-x 7 root root 4096 Mar 13 21:29 1-55fe756a29268f9b4e786ae468952ca4a8df1bd8 drwxr-xr-x 7 root root 4096 Mar 13 21:30 2-67d8da7a0e53d8fadeb6b36396d...

  • BITCOIN AND CRYPTOCURRENCY TECHNOLOGIES - chapter 1 -Introduction

    Chapter 1 In fiat currencies, law enforcement is necessary for stopping people from breaking the rules of the system.Howerver,Cryptocurrencies need to be enforced purely technologically and without ...

  • k-approximation algorithm k-近似算法是什么 k代表什么

    ApproximationAlgorithms Note:You are looking at a static copy of the former PineWiki site, ... Many mathematical formulas are broken, and there are likely to be other bugs as well. These will mo...

Global site tag (gtag.js) - Google Analytics