合理运用异常机制,能够显著提升代码的健壮性,确保程序在面对各种意外情况时仍能保持稳定运行。
我们一起来看下这9条建议。
1. 仅在异常情况下使用异常
避免将异常用于普通控制流。
例如,不应使用异常来终止循环控制流:
而应使用常规的集合迭代方式:
换句话说,不要故意写异常,该检查的时候先检查,比如必要的空值检查,不要出现NullPointerException。
2. 对可恢复的情况使用受检异常,对编程错误使用运行时异常
在大多数情况下,如果调用者能够恢复异常,则应使用受检异常。否则,应使用运行时异常。
运行时异常表示可通过检查某些前置条件(如数组边界和空值检查)来避免的编程错误。
在以下方法中,IllegalArgumentException是一个运行时异常,其用法表明存在编程错误。
通常可以通过检查前置条件来避免此类错误,例如在此处检查hasNext()方法。
3. 避免不必要地使用受检异常
受检异常会强制调用者处理异常情况,因为如果不处理,编译器会报错。
过度使用受检异常会给调用者带来处理异常情况的负担。
因此,应仅在必要时使用受检异常。
当无法通过检查前置条件来避免异常,并且调用者可以采取一些有用的操作来处理该异常时,使用受检异常。
常用的运行时异常本身就是不过度使用受检异常的示例。
常见的运行时异常包括:ArithmeticException、ClassCastException、IllegalArgumentException、IllegalStateException、IndexOutOfBoundExceptions、NoSuchElementException和NullPointerException。
在以下方法中,当propertyName不是目标情况之一时,调用者无能为力,因此抛出一个运行时异常。
4. 优先使用标准异常
常用的异常包括:
- java.io.IOException
- java.io.FileNotFoundException
- java.io.UnsupportedEncodingException
- java.lang.reflect.InvocationTargetException
- java.security.NoSuchAlgorithmException
- java.net.MalformedURLException
- java.text.ParseException
- java.net.URISyntaxException
- java.util.concurrent.ExecutionException
- java.net.UnknownHostException
标准异常是JDK提供给我们的小宝藏,根据名字我们就能够知道异常原因,而且,大家共用一套异常,也便于沟通。
5. 抛出与抽象级别相适应的异常
此条建议说的是异常转换(捕获一个异常并抛出另一个异常)和异常链接(将一个异常包装在新异常中以保持异常的因果链)。
上述方法捕获JAXBException和UnsupportedEncodingException,并重新抛出一个与方法抽象级别相适应的新异常。
新的BillingRunFailed异常包装了原始异常。异常链接的好处是保留了有助于调试问题的低级异常。
建议很多新手、老手听一下这条建议。异常转换是为了返回的异常更容易理解,明确异常本质;但是转换后不要丢弃了原始异常,在Debug或排错的时候,如果丢失了原始异常,很容易懵~~
6. 为每个方法抛出的所有异常编写文档
这一点被严重忽视。大多数公共API都缺少@throws Java文档来解释所抛出的异常。
下面这个是缺少关于在何种情况下抛出异常信息的坏示例。
这一条是个好建议,但是不容易实现。如果是想实现一些基础组件,或者是开源项目,就要有完善的文档了。
7. 在详细消息中包含故障捕获信息
在此方法中,IOException使用不同的字符串来传递不同的故障捕获信息。
这条建议同样适用于日志或接口异常信息,看过很多接口返回的是“服务异常,请稍后再试”,返回了一句没有太多帮助的信息。
理性的说,如果返回错误,那就是有异常了。很多时候,应该包含一些有用的信息,比如,缺少必填参数xxx。
8. 力求故障原子性
这条建议关于失败的。
一般来说,失败的方法不应更改方法中对象的状态。
为了尽早失败,一种方法是在执行操作之前检查参数的有效性,若无效则立即抛出异常,避免执行可能导致状态改变的操作。。比如:
如果无法前置检查,就在失败时将对象恢复到操作前的状态,避免产生不一致的数据。
9. 不要忽略异常
不要空 catch 异常块,应根据异常的性质进行适当处理,如记录日志、提供友好的错误提示给用户、进行错误恢复操作或重新抛出更合适的异常等。
该说不说,printStackTrace方法和空catch一样差劲。