Throwing an Exception Without Proper Context Is a Bad Habit

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

我一遍又一遍地重复着同样的错误。所以是时候停下来,制定一条规则,以防止这种情况再次发生。这个错误并不致命,但非常令人恼火。当我查看生产日志时,经常会看到类似于”文件不存在”的信息,然后我会问自己:是哪个文件?它应该存在在哪里?服务器试图对其执行什么操作?在崩溃之前的一秒钟内发生了什么?日志中没有答案,这完全是我的错。我要么1)不重新抛出异常,要么2)重新抛出异常但不提供上下文。这两种做法都是错误的。

这是代码的样子:

if (!file.exists()) {
  throw new IllegalArgumentException(
    "File doesn't exist"
  );
}

它也可能是这个样子:

try {
  Files.delete(file);
} catch (IOException ex) {
  throw new IllegalArgumentException(ex);
}

这两个例子都展示出了一种不充分的处理异常情况和报告异常的方式。问题出在哪里?异常信息不够详尽。它们只是简单地没有包含来自异常发生位置的任何信息。

这是它们应该呈现的样子:

if (!file.exists()) {
  throw new IllegalArgumentException(
    String.format(
      "User profile file %s doesn't exist",
      file.getAbsolutePath()
    )
  );
}

第二个例子应该像这样:

try {
  Files.delete(file);
} catch (IOException ex) {
  throw new IllegalArgumentException(
    String.format(
      "Can't delete user profile data file %s",
      file.getAbsolutePath()
    ),
    ex
  );
}

看到了区别吗?这段代码看起来可能是多余的,但实际上不是。当然,在我写这些代码的时候,我并不真的在意日志和异常。我并不真的期望这个文件不存在。

But I should.

应该有一个规则:每次我们抛出或重新抛出时,异常信息必须尽可能详细地描述问题。

当然,我们不能忘记安全性和风险,在异常消息中插入任何敏感信息,比如密码、信用卡号等等。除此之外,尽可能多的信息必须暴露给更高级别的异常捕获器。

抛出异常字面上是将问题升级到更高层次的管理。想象一下,我的老板要求我安装一个新的服务器。几个小时后,我回到他面前说:“我失败了,对不起。”那听起来很奇怪。他会要求更多细节。我为什么失败了?到底出了什么问题?是否有其他方法可以做到?等等。

这样的代码实际上是对客户的不尊重的表现。

throw new IllegalArgumentException(
  "File doesn't exist"
);

我需要更加详细地描述并提供更多细节。

而且我在这个错误中并不孤单。我到处都看到这种情况,这真的让调试变得困难,特别是在生产环境中,几乎不可能立即重现问题。

因此,请在您的异常消息中更加详细地说明。在我的代码中,我也会这样做。

在你离开之前,还有一件事。在大多数面向对象编程语言中,异常是未检查的,这意味着捕获它们不是强制操作,很不幸。尽管如此,我建议你始终捕获、添加上下文并重新抛出所有异常。这可能看起来像纯噪音,但实际上不是!只需使你的方法更小并确保所有从它们中发送的异常都有足够的关于其来源的信息。这将对你自己和其他人都有很大帮助。

Translated by ChatGPT gpt-3.5-turbo/35 on 2023-09-09 at 05:37

sixnines availability badge   GitHub stars