无论你是新手还是资深程序员,复习下异常处理的实践总是一件好事,因为这能确保你与你的团队在遇到问题时能够处理得了它。
在 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 语句,在我的这篇Java 异常处理入门中有更为详细的介绍。
如果你在资源中实现了 AutoCloseable 接口的话,就可以使用 try-with-resource 语句了,这也是大多数 Java 标准资源的做法。如果你在 try-with-resource 中打开了一个资源,在 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 (注:例如将参数转换为数值出错时,应该抛出具体的 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、将描述信息与异常一同抛出
这个方法背后的思想和前两个是类似的。但这一次,你不必给你的方法调用者提供信息。对于任何遭遇异常错误并需要搞清楚错误原因的人来说,异常信息总是在异常出现的同时,被记录在了日志中,或打印在了屏幕上。
因此,请尽可能精确地描所以,最好不要在 catch 中使用 Throwable ,除非你能确保自己处于一些特定情况下,比如你自己足以处理错误,又或被要求处理错误时。述异常事件,并提供最相关的信息以令其他人能够理解发生了什么异常时。
别误会我的意思了。你没必要去写上一大段的文字,但你应该用一两句简短的话来解释一下异常发生的原因。这能让你的开发团队明白问题的严重性,也能让你更容易地分析服务事故。
如果你抛出了一个特定的异常,它的类名很可能就已经描述了这是什么类型的错误了。所以,你不需要提供很多额外的描述信息。一个很好的例子是,当你提供了一个错误格式的 String 类型参数时,java.lang.Long 构造函数就会