Java异常处理最佳实践20强

这篇文章是此博客中可用的最佳实践系列的另一篇文章。在这篇文章中,我将介绍一些众所周知的和鲜为人知的实践,您在处理下一个Java编程任务中的异常时必须考虑这些实践。单击此链接以阅读有关Java中异常处理的更多信息。

目录

异常类型
用户定义的自定义异常

您必须考虑并遵循的最佳做法

永远不要吞没catch块中的异常
声明方法可以抛出的特定已检查异常
不要捕获Exception类而是捕获特定的子类
永远不要捕获Throwable类
总是正确地将异常wrap在自定义异常中,以便不会丢失堆栈跟踪
记录异常或将其引发,但不要两者都执行
从不从finally块引发任何异常
始终仅捕获您可以实际处理的异常
不要使用printStackTrace()语句或类似方法
如果您不打算使用finally块而不是catch块处理异常
请记住“尽早捕获”原则
,请在处理异常后进行清理
仅从方法中抛出相关异常
从不在程序中使用异常进行 Stream 控制
验证用户输入以在请求处理的早期就捕获不利条件
始终在单个日志消息中
包含有关异常的所有信息将所有相关信息传递给异常以使它们具有信息性尽可能
总是终止被中断的线程
使用模板方法进行反复尝试捕获
在javadoc中记录应用程序中的所有异常

在深入探讨异常处理最佳实践的深入概念之前,让我们从最重要的概念之一入手,这是要理解Java中存在三种可抛出类的常规类型:已检查的异常,未检查的异常和错误。

1。 Java中的异常类型

Java中的异常层次结构
Java中的异常层次结构

检查异常

这些是必须在方法的throws子句中声明的异常。它们扩展了Exception,旨在成为“面对您”的异常类型。Java希望您处理它们,因为它们某种程度上取决于程序之外的外部因素。已检查的异常指示正常系统运行期间可能发生的预期问题。通常,当您尝试通过网络或文件系统使用外部系统时,会发生这些异常。通常,对检查到的异常的正确响应应该是稍后重试,或者提示用户修改其输入。

未经检查的异常

这些是不需要在throws子句中声明的异常。JVM不会强迫您处理它们,因为它们大多数是由于程序错误在运行时生成的。它们扩展了RuntimeException。最常见的例子是NullPointerException[相当吓人。不是吗?]。未经检查的异常可能不应该重试,正确的操作通常应该是什么也不做,并让它从方法中出来并通过执行堆栈。在高级别执行时,应记录此类异常。

失误

是严重的运行时环境问题,几乎可以肯定无法解决。例如OutOfMemoryError,LinkageError和StackOverflowError。它们通常会使您的程序或程序的一部分崩溃。只有良好的日志记录做法才能帮助您确定错误的确切原因。

2.用户定义的自定义异常

每当用户出于某种原因而感觉到要使用自己的应用程序特定异常时,他都可以创建一个扩展适当超类(主要是的超类Exception)的新类,并在适当的地方开始使用它。这些用户定义的异常可以两种方式使用:

  1. 当应用程序出现问题时,直接抛出自定义异常
    抛出新的DaoObjectNotFoundException(“找不到ID为” + id“的dao;
  2. 或者将原始异常wrap在自定义异常中并抛出
    catch(NoSuchMethodException e){
      抛出新的DaoObjectNotFoundException(“找不到ID为” + id,e的dao“;
    }

wrap异常可以通过添加您自己的消息/上下文信息来向用户提供额外的信息,同时仍保留原始异常的堆栈跟踪和消息。它还允许您隐藏代码的实现细节,这是wrap异常的最重要原因。

现在让我们开始探索在异常处理方面明智的最佳实践。

3.您必须考虑并遵循的Java异常处理最佳实践

3.1。永远不要在catch块中吞下异常

catch (NoSuchMethodException e) {
   return null;
}

这样做不仅返回“ null”,而不是处理或重新引发异常,它完全吞没了异常,永远失去了错误原因。当您不知道失败的原因时,将来如何预防呢?永远不要这样做!

3.2。声明您的方法可以抛出的特定检查异常

public void foo() throws Exception { //Incorrect way
}

始终避免像上面的代码示例中那样进行操作。它根本无法达到检查异常的全部目的。声明您的方法可以抛出的特定已检查异常。如果此类检查的异常太多,则可能应将它们warp在您自己的异常中,并在异常消息中添加信息。如果可能,您还可以考虑代码重构。

public void foo() throws SpecificException1, SpecificException2 { //Correct way
}

3.3。不捕获Exception类,而是捕获特定的子类

try {
   someMethod();
} catch (Exception e) {
   LOGGER.error("method has failed", e);
}

捕获Exception的问题是,如果稍后调用的方法在其方法签名中添加了一个新的经过检查的异常,则开发人员的意图是应处理特定的新异常。如果您的代码仅捕获到Exception(或Throwable),则您永远不会知道更改以及您的代码现在是错误的,并且可能在运行时的任何时间中断的事实。

3.4。永不赶上可投掷类

好吧,这是更严重的麻烦了。因为Java错误也是Throwable的子类。错误是不可逆的条件,JVM本身无法处理。对于某些JVM实现,JVM甚至可能实际上不会在Error上调用您的catch子句。

3.5。始终正确地将异常wrap在自定义异常中,以便不会丢失堆栈跟踪

catch (NoSuchMethodException e) {
   throw new MyServiceException("Some information: " + e.getMessage());  //Incorrect way
}

这破坏了原始异常的堆栈跟踪,并且总是错误的。正确的方法是:

catch (NoSuchMethodException e) {
   throw new MyServiceException("Some information: " , e);  //Correct way
}

3.6。记录异常还是抛出异常,但不要两者都做

catch (NoSuchMethodException e) {
   LOGGER.error("Some information", e);
   throw e;
}

与上面的示例代码一样,由于代码中的单个问题,日志记录和抛出将导致日志文件中出现多条日志消息,并使试图挖掘日志的工程师费尽心思。

3.7。永远不要从finally块中抛出任何异常

try {
  someMethod();  //Throws exceptionOne
} finally {
  cleanUp();    //If finally also threw any exception the exceptionOne will be lost forever
}

只要cleanUp()永远不会抛出任何异常,就可以了。在上面的示例中,如果someMethod()引发异常,并且在finally块中,cleanUp()引发异常,则第二个异常将从方法中消失,原始的第一个异常(正确原因)将永远消失。如果您在finally块中调用的代码可能会引发异常,请确保处理该异常或将其记录下来。永远不要让它脱离最后的障碍。

3.8。始终仅捕获您可以实际处理的异常

catch (NoSuchMethodException e) {
   throw e; //Avoid this as it doesn't help anything
}

好吧,这是最重要的概念。不要仅仅为了捕获异常就捕获任何异常。仅在您要处理任何异常或要在该异常中提供其他上下文信息时才捕获任何异常。如果您不能在catch块中处理它,那么最好的建议就是不要仅将其重新抛出就捕获它。

3.9。不要使用printStackTrace()语句或类似方法

完成代码后,切勿离开printStackTrace()。很有可能是您的同事之一,最终将获得这些堆栈跟踪中的一个,并且对如何处理它的知识完全为零,因为它不会附加任何上下文信息。

3.10。如果您不打算处理异常,请使用finally块而不是catch块

try {
  someMethod();  //Method 2
} finally {
  cleanUp();    //do cleanup here
}

这也是一个好习惯。如果在您的方法内部访问某个方法2,并且方法2抛出一些您不想在方法1中处理的异常,但是仍然希望进行一些清除以防万一发生异常,那么请在finally块中进行此清除。不要使用挡块。

3.11。记住“早起早退”的原则

这可能是有关异常处理的最著名的原理。它基本上说您应该尽快抛出异常,并尽可能晚地捕获它。您应该等待,直到掌握了正确处理所有信息为止。

该原则隐含地表明,您将更有可能将其扔到低级方法中,在这种方法中,您将检查单个值是否为null或不合适。并且您将使异常爬升到堆栈跟踪相当多个级别,直到达到足够的抽象级别以能够解决问题为止。

3.12。处理异常后请务必清理

如果您正在使用数据库连接或网络连接之类的资源,请确保清理它们。如果要调用的API仅使用未经检查的异常,则仍应在使用后使用try – finally块清理资源。在try块内部访问资源,最后在内部关闭资源。即使在访问资源时发生任何异常,资源也将正常关闭。

3.13。仅抛出方法中的相关异常

相关性对于保持应用程序清洁很重要。一种尝试读取文件的方法;如果抛出NullPointerException,则不会向用户提供任何相关信息。相反,如果将此类异常wrap在自定义异常(例如NoSuchFileFoundException)中会更好,那么它将对该方法的用户更加有用。

3.14。切勿在程序中使用异常进行 Stream 控制

我们已经阅读了很多次,但是有时我们会在项目中不断看到代码,在这些代码中,开发人员试图将异常用于应用程序逻辑。绝对不要那样做。它使代码难以阅读,理解和难看。

3.15。在请求处理的早期就验证用户输入以捕获不利条件

始终在很早的阶段就验证用户输入,甚至在输入到实际控制器之前。这将帮助您最大程度地减少核心应用程序逻辑中的异常处理代码。如果用户输入中存在一些错误,它还可以帮助您使应用程序保持一致。

例如:如果在用户注册应用程序中,您将遵循以下逻辑:

1)验证用户
2)插入用户
3)验证地址
4)插入地址
5)如果有问题,请回滚所有内容

这是非常不正确的方法。在各种情况下,它会使数据库处于不一致状态。而是首先验证所有内容,然后在dao层中获取用户数据并进行数据库更新。正确的方法是:

1)验证用户
2)验证地址
3)插入用户
4)插入地址
5)如果有问题,请回滚所有内容

3.16。始终在单个日志消息中包含有关异常的所有信息

LOGGER.debug("Using cache sector A");
LOGGER.debug("Using retry sector B");

不要这样

在测试用例中,将多行日志消息与多次调用LOGGER.debug()一起使用可能看起来不错,但是当它显示在并行运行400个线程的应用程序服务器的日志文件中时,所有信息都将转储到同一线程日志文件中,即使两条日志消息出现在代码的后续行中,它们最终也可能在日志文件中以1000行隔开。

像这样做:

LOGGER.debug("Using cache sector A, using retry sector B");

3.17。将所有相关信息传递给异常,以使它们尽可能多地提供信息

这对于使异常消息和堆栈跟踪有用和提供信息也非常重要。如果您无法从中确定任何内容,则日志的用途是什么。这些类型的日志仅存在于您的代码中用于装饰。

3.18。总是终止被中断的线程

while (true) {
  try {
    Thread.sleep(100000);
  } catch (InterruptedException e) {} //Don't do this
  doSomethingCool();
}

InterruptedException是您的代码的线索,它应该停止正在执行的任何操作。线程被中断的一些常见用例是活动事务超时或线程池被关闭。您的代码不应忽略InterruptedException,而应尽最大努力完成正在执行的工作,并完成当前的执行线程。因此,请更正上面的示例:

while (true) {
  try {
    Thread.sleep(100000);
  } catch (InterruptedException e) {
    break;
  }
}
doSomethingCool();

3.19。使用模板方法重复尝试捕获

在代码的100个地方没有使用相似的catch块是没有用的。它增加了代码重复性,无济于事。在这种情况下,请使用模板方法。

例如,下面的代码尝试关闭数据库连接。

class DBUtil{
    public static void closeConnection(Connection conn){
        try{
            conn.close();
        } catch(Exception ex){
            //Log Exception - Cannot close connection
        }
    }
}

这种类型的方法将在应用程序中的数千个位置中使用。不要将整个代码放在每个地方,而要定义上面的方法,并像下面这样在各处使用它:

public void dataAccessCode() {
    Connection conn = null;
    try{
        conn = getConnection();
		....
    } finally{
        DBUtil.closeConnection(conn);
    }
}

3.20。使用javadoc记录应用程序中的所有异常

练习Javadoc一段代码在运行时可能抛出的所有异常。还要尝试包括可能的措施,如果出现这些异常,用户应遵循。

我现在所想到的就是与Java异常处理最佳实践有关的所有内容。如果您发现任何缺失或与我的观点无关,请给我评论。我很乐意讨论。

saigon has written 1445 articles

Leave a Reply