像冠军一样处理异常。
哦,请不要这样写……
// 写一句注释跳过异常
try {
throw new IOException("Made up");
} catch (IOException e) {
// 跳过
}
// 记到日志里,继续处理
try {
throw new IOException("Made up");
} catch (IOException e) {
log.error("blah blah blah", e);
}
// 标记 TODO,不做任何处理
try {
throw new IOException("Made up");
} catch (IOException e) {
// TODO - 处理异常 (;
}
我在各种项目中发现了这种 catch 语句。这是一种“好办法”,可以在短期内掩盖问题。然而几周或几个月后,这些代码将成为开发人员的噩梦。绝大多数人可不想读日志查问题。因此,还是让我们避免这种情况。
规则一:catch 语句是用来处理异常的,把异常记到日志里然后继续执行不算处理。唯一的例外是,在发生异常后关闭资源(本文不讨论这种情况;如果感兴趣,可以参考这篇 McDowell 的博客,虽然写的时间比较早,但内容很不错)。
有三种处理异常的基本模式:转换(translate)、重试(retry)和恢复(recover)。
转换经常用于处理受检异常(checked exception),在方法中异常无法抛出,并且无法恢复时使用。在这种情况下,将其转换为运行时异常(runtime exception)而后抛出是最合适的做法。接下来,运行时异常通常由框架处理。在处理不可靠的服务时,重试非常有用,前提是重新尝试有意义。一个很好的例子就是网络中断重试。如果定义了这种策略,那么就能够恢复到正常状态。例如,如果通过网络发送数据失败,可以将数据写入本地存储。当然,这时就必须定义如何处理该文件。
此外,上面提到的模式可以组合,比如像下面这个例子如下。
// 转换
try {
throw new IOException("Made up");
} catch (IOException e) {
throw new RuntimeException(e);
}
// 重试5次后放弃
boolean end = false;
int count = 0;
while (end == false) {
try {
// 发送信息
if (true) {
throw new MessagingException("Made up");
}
end = true;
} catch (MessagingException e) {
if (count >= 5) {
// 尝试5次放弃。
throw new RuntimeException("was not able to send message even after five tries", e);
}
++count;
try {
Thread.sleep(30000);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
throw new RuntimeException(e1);
}
}
}
// 恢复:如果传输失败记录到文件
try {
// 发送信息
throw new MessagingException("Made up");
} catch (MessagingException e) {
try {
// 写文件
throw new IOException("Made up");
} catch (IOException e1) {
// 如果写文件失败,不再进行恢复
throw new RuntimeException(e1);
}
}
如果一切都失败了,那么上面这种方法至少可以确保你能意识到问题所在。此外,它还提供了问题的真正原因,从而让你能快速定位问题。
祝编程快乐!