乐趣区

关于后端:处理异常的标准姿势一定要学会

前言在 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、不要捕捉 ThrowableThrowable 是所有 Exception 和 Error 的父类。尽管能够在 catch 块中捕捉它,然而咱们不应该这样去做。因为如果应用了 Throwable,那么不仅会对所有抛出的 Exception 进行捕捉,还会捕捉所有的 Error。而当咱们的程序抛出 Error 时示意是一个无奈解决的重大问题,例如典型的 OutofMemoryError,StackOverflowError 等,这两个 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 更易用。通常异样不光是一种错误处理机制,同时还具备肯定的信息媒介作用。咱们应该遵循这些异样解决的规定和最佳实际,写出更标准,不让他人吐槽的好代码。

退出移动版