热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

Java编程异常处理最佳实践【推荐】

这篇文章主要介绍了Java编程异常处理最佳实践【推荐】,具有一定参考价值,需要的朋友可以了解下。

Java中的异常处理不是一个简单的话题。初学者很难理解,甚至有经验的开发人员也会花几个小时来讨论应该如何抛出或处理这些异常。这就是为什么大多数开发团队都有自己的异常处理的规则和方法。如果你是一个团队的新手,你可能会惊讶于这些方法与你之前使用过的那些方法有多么不同。常见的异常类型:

NullPointerException -空指针引用异常
ClassCastException-类型强制转换异常
lllegalArgumentException-传递非法参数异常
ArithmeticException-算术运算异常
ArrayStoreException-向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException-下标越界异常
NegativeArraySizeException-创建一个大小为负数的数组错误异常
NumberFormatException-数字格式异常
SecurityException-安全异常
UnsupportedOperationException-不支持的操作异常
EOFException:文件已结束异常
FileNotFoundException:文件未找到异常
SQLException:操作数据库异常
IOException:输入输出异常
NoSuchMethodException:方法未找到异常

然而,有几种异常处理的最佳方法被大多数开发团队所使用。下面为常见的几种实用的异常处理方法!

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会抛出错误,这是应用程序不打算处理的严重问题。典型的例子是 OutOfMemoryError 或 StackOverflowError 。这两种情况都是由应用程序控制之外的情况引起的,无法处理。

所以,最好不要在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可用性。异常通常是一个错误处理机制和一个通信媒介。因此,你应该确保同事一起讨论想要应用的最佳实践和方法,以便每个人都理解通用概念并以相同的方式使用它们。

以上就是本文关于Java编程异常处理最佳实践的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:Java编程中的检查型异常与非检查型异常分析、Java多线程之线程通信生产者消费者模式及等待唤醒机制代码详解等,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!


推荐阅读
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 使用C#开发SQL Server存储过程的指南
    本文介绍如何利用C#在SQL Server中创建存储过程,涵盖背景、步骤和应用场景,旨在帮助开发者更好地理解和应用这一技术。 ... [详细]
  • 优化联通光猫DNS服务器设置
    本文详细介绍了如何为联通光猫配置DNS服务器地址,以提高网络解析效率和访问体验。通过智能线路解析功能,域名解析可以根据访问者的IP来源和类型进行差异化处理,从而实现更优的网络性能。 ... [详细]
  • PHP 5.2.5 安装与配置指南
    本文详细介绍了 PHP 5.2.5 的安装和配置步骤,帮助开发者解决常见的环境配置问题,特别是上传图片时遇到的错误。通过本教程,您可以顺利搭建并优化 PHP 运行环境。 ... [详细]
  • 本文详细介绍了HTML中标签的使用方法和作用。通过具体示例,解释了如何利用标签为网页中的缩写和简称提供完整解释,并探讨了其在提高可读性和搜索引擎优化方面的优势。 ... [详细]
  • 本文深入探讨 MyBatis 中动态 SQL 的使用方法,包括 if/where、trim 自定义字符串截取规则、choose 分支选择、封装查询和修改条件的 where/set 标签、批量处理的 foreach 标签以及内置参数和 bind 的用法。 ... [详细]
  • 本文探讨了适用于Spring Boot应用程序的Web版SQL管理工具,这些工具不仅支持H2数据库,还能够处理MySQL和Oracle等主流数据库的表结构修改。 ... [详细]
  • 本文详细介绍了如何通过多种编程语言(如PHP、JSP)实现网站与MySQL数据库的连接,包括创建数据库、表的基本操作,以及数据的读取和写入方法。 ... [详细]
  • 在当前众多持久层框架中,MyBatis(前身为iBatis)凭借其轻量级、易用性和对SQL的直接支持,成为许多开发者的首选。本文将详细探讨MyBatis的核心概念、设计理念及其优势。 ... [详细]
  • 在使用 DataGridView 时,如果在当前单元格中输入内容但光标未移开,点击保存按钮后,输入的内容可能无法保存。只有当光标离开单元格后,才能成功保存数据。本文将探讨如何通过调用 DataGridView 的内置方法解决此问题。 ... [详细]
  • 本文详细介绍了如何在 Linux 平台上安装和配置 PostgreSQL 数据库。通过访问官方资源并遵循特定的操作步骤,用户可以在不同发行版(如 Ubuntu 和 Red Hat)上顺利完成 PostgreSQL 的安装。 ... [详细]
  • 如何在PostgreSQL中查看数据表
    本文将指导您使用pgAdmin工具连接到PostgreSQL数据库,并展示如何浏览和查找其中的数据表。通过简单的步骤,您可以轻松访问所需的表结构和数据。 ... [详细]
  • 利用存储过程构建年度日历表的详细指南
    本文将介绍如何使用SQL存储过程创建一个完整的年度日历表。通过实例演示,帮助读者掌握存储过程的应用技巧,并提供详细的代码解析和执行步骤。 ... [详细]
author-avatar
我爱投资理财
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有