1、优先使用更明确的异常
如果我们的方法需要向外抛出异常,那么异常类型越具体越好。因为其他人再调用我们API时对我们内部的实现逻辑可能并不是很清楚,所以当抛出异常时要尽可能地提供给他人更多的信息,以便更好地理解和处理抛出的异常。
比如,在你的方法内容抛出NumberFormatException比抛出IllegalArgumentException或者直接抛出Exception,所代表的含义就会更明确.
2、使用finally关闭资源
如果在try代码块中需要使用到一些资源,比如InputStream
,在使用完之后我们需要将资源关闭。
一个错误示例
public void test() {
FileInputStream inputStream = null;
try {
File file = new File("./text.txt");
inputStream = new FileInputStream(file);
// 使用inputStream读取文件
// 不要这样做
inputStream.close();
} catch (FileNotFoundException e) {
logger.error("文件未找到", e);
} catch (IOException e) {
logger.error("文件读取异常", e);
}
}
在上面这段代码中,只要在文件读取时没有出现异常,这段代码是可以正常工作的,但是只要在try块中的方法中抛出异常,资源就不会被关闭。
所以这种情况我们应该将资源关闭的代码放在finally中。
使用finally
在finally块中的代码不管是否出现异常,都会被执行,因此可以确保资源对象被关闭
public void closeResourceInFinally() {
FileInputStream inputStream = null;
try {
File file = new File("./test.txt");
inputStream = new FileInputStream(file);
// 使用inputStream读取文件
} catch (FileNotFoundException e) {
logger.error("文件未找到", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
logger.error("资源关闭异常", e);
}
}
}
}
使在java7之后可以使用try-with-resource语法来关闭
在try子句中打开资源,将会在try代码块执行完毕或异常处理后自动关闭资源对象。
public static void main(String[] args) {
try (FileInputStream inputStream = new FileInputStream(new File("./test.txt"))) {
System.out.println(inputStream.read());
} catch (FileNotFoundException e) {
log.error("文件未找到", e);
} catch (IOException e) {
log.error("文件读取异常", e);
}
}
3、在异常中携带足够的描述信息
在异常中携带足够的描述信息,是为了在出现该异常时,能够在日志文件中查看异常信息时,能看到更有用的信息。所以我们应该尽可能准确地描述出为什么抛出了这个异常,并提供最相关的数据信息让别人定位。
当然这里也不能提供太多冗余信息,应该使用简短的一段信息描述,能更轻松地分析问题所在。比如当你再创建一个Long对象时如果传入一个字符串,就会抛出NumberFormatException。
public static void testLong() {
try {
Long abc = new Long("ABC");
} catch (NumberFormatException e) {
logger.error("格式异常", e);
}
}
NumberFormatException
的类名已经告诉我们出现的是数字格式化异常,所以在message
中只需要提供输入的字符串。如果你定义的异常类名不能很明确的表达出是什么异常,比如BusinessException
,你就应该在message
中表达出更多的信息。
4、先捕获更明确的异常
一般在我们使用的IDE中,如果当你在做异常捕获时,先捕获了不太具体的异常比如Exception,然后再捕获更具体的异常如IOException,都会提示我们后面的catch块无法到达。所以我们应该先捕获最具体的异常类,将不太具体的异常类的捕获放在最后。
public void catchExceptions() {
try {
doSomething("测试Text");
} catch (NumberFormatException e) {
logger.error("格式异常", e);
} catch (Exception e) {
logger.error("Exception", e);
}
}
5、不要将异常忽略
在你开发的时候可能非常确定不会抛出异常,并且在你开发时确实没有发生过抛出异常的情况,所以在catch块中没有对异常做任何处理。
public void doNotIgnoreExceptions() {
try {
// 一些业务代码
} catch (NumberFormatException e) {
// 认为永远不会执行到这里
}
}
但是,你其实不确定在将来会不会有人在你的try块中添加新的代码,并且他可能也不会意识到他添加的代码会导致有异常抛出,这将会导致在线上真的有异常产生,但是没有一个人知道。
所以,至少应该在catch中打印一行日志,提示这里出现了一个异常。
public void doNotIgnoreExceptions() {
try {
// 一些业务代码
} catch (NumberFormatException e) {
logger.error("警报,这里出现了一个异常", e);
}
}
6、不要打印日志后又将异常抛出
一个错误示例
public void testCatchEx() {
try {
new Long("ABC");
} catch (NumberFormatException e) {
logger.error("数字格式异常", e);
throw e;
}
}
你可能会认为这样做很直观,也没什么错,让调用你方法的人去处理就好了。但是这样一来,在日志中会对抛出的一个异常打印多条错误信息。
重复的日志并没有带来任何有价值的信息,在异常信息中应该携带足够的信息,但是要做到精简。如果需要在添加其他信息,可以将捕获到的异常封装在你的自定义异常中再进行抛出。
public void wrapException(String input) throws BusinessException {
try {
// do something
} catch (NumberFormatException e) {
throw new BusinessException("一段对异常的描述信息.", e);
}
}
所以,我们应该只有在想对异常进行处理时捕获,否则就应该在抛出去,并且在方法前面上加以说明,让调用方去处理。
7、不要捕获Throwable
Throwable是所有Exception和Error的父类。
虽然可以在catch
块中捕获它,但是我们不应该这样去做。因为如果使用了Throwable
,那么不仅会对所有抛出的Exception
进行捕获,还会捕获所有的Error
。
而当程序抛出Error
时表示是一个无法处理的严重问题,例如典型的OutofMemoryError
,StackOverflowError
等,都是由程序无法控制并且不能处理的情况引起的。所以说,最好不要在catch
中捕获Throwable
,除非你非常确定try
块中的代码抛出的是可以处理的异常情况。
public void catchThrowable() {
try {
// 一些业务代码
} catch (Throwable t) {
// 不要这样做
}
}
总结
在抛出或者捕获异常时,我们应该考虑很多不同的事情,上面所说的大多数都是为了提高代码的可读性和提供给别人的API更易用。