Java异常处理的九个最佳实践

1、确保在Finally程序块中完成资源释放或者使用Try-With语句

比如对于InputStream,当我们使用完毕,我们要确保资源被正确关闭,比如下面我们常见的错误用法,不要在try模块中关闭资源,因为一旦try语句块中的其他方法发生异常,很有可能无法执行到inputStream.close()方法的。

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);
    }
}

推荐做法,使用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 语句来确保资源文件可以正常关闭

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、抛出异常越准确越好


public void doNotDoThis() throws Exception {
    ...
}
public void doThis() throws NumberFormatException {
    ...
}

3、对所抛出的异常进行必要的文字说明

/**
 * This method does something extremely useful ...
 *
 * @param input
 * @throws MyBusinessException if ... happens
 */
public void doSomething(String input) throws MyBusinessException {
    ...
}

4、抛出的异常应包含有描述性的信息

比如,对于NumberFormatException这类异常,异常本身已经告诉你错误类型,所以只需要传入的转换字符串,然而,对于一些无法帮助我们定位错误信息的异常,必要的描述性信息还是很有必要的。

try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
}

17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: “xyz”

5、在最开始捕获绝大部分特定异常

public void catchMostSpecificExceptionFirst() {
    try {
        doSomething("A message");
    } catch (NumberFormatException e) {
        log.error(e);
    } catch (IllegalArgumentException e) {
        log.error(e)
    }
}

6、不要去试图捕获Throwable

注意,Throwable 是所有异常(Exception)和错误(Error)的父类,如果在try-catch语句中使用了Throwable,你不仅捕获了所有的异常,还包括所有的错误error,一旦JVM抛出了Error,就意味着程序发生了严重的错误,应用程序本身已无法自行处理,比如我们常见的OutOfMemoryErrorStackOverflowError,此种类型错误的发生意味者已经超出应用程序本身所能处理的范畴,所以,不要在代码中试图捕获Throwable,除非你十二分确认异常情况,并且有能力自行处理。

public void doNotCatchThrowable() {
    try {
        // do something
    } catch (Throwable t) {
        // don't do this!
    }
}

7、不要忽略异常

某些编程人员可能会盲目笃信彼时彼地某个代码段永远都不会发生异常,并且很自信的加上了一个catch代码块,但在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、不要同时日志记录并抛出异常

这可能是我们最容易忽视的一种代码实践,即:不要同时进行日志记录并抛出异常。

下面是我们最常见的一种异常代码书写方式,这也是我们异常处理的最常见方式,首先记录日志,然后重新抛出异常,但其实这并不是一种好的代码实践:

ry {
    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);
    }
}

英文原文

https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java

你可能感兴趣的:(Java,架构,中间件)