NEE's Blog

最危险的错误处理是静默成功的那种

May 12, 2026

我审查过很多代码,其中的错误处理在技术上完全正确,但在实践中几乎没有用处。模式是这样的:

try {
  await riskyOperation();
} catch (e) {
  logger.error(e);
  return null;
}

这段代码捕获了错误。记录了错误。返回了一个让程序继续运行的值。但这几乎总是错误的做法。

问题不在于 try-catch。问题在于 return null。每个调用方现在都必须处理 null,而大多数调用方不会处理。它们把 null 传递下去,三个函数调用之后,某个地方因为令人困惑的 NullPointerException 或 TypeError: Cannot read properties of undefined 而崩溃。原始错误被记录了,但没有人看到日志,而失败的位置已经远离了真正的起因。

我的三个简单准则

我开始在代码审查中使用一个简单的启发式规则:

1. 如果错误是预期中的、可恢复的——在调用处明确处理。不要返回 null。返回一个结果类型或一个默认值,并清楚地注释为什么这个默认值是安全的。

2. 如果错误是意外的——让它传播。尽早崩溃。在实际失败点的堆栈跟踪,胜过一百条没人看的日志。

3. 如果必须吞掉错误——在边界处吞掉。在最外层的、有足够上下文来做真正决策的函数中处理。永远不要在三层深的工具函数中吞掉错误。

问题的本质

我帮忙调试过的最严重的生产环境 bug,不是因为缺少错误处理。而是因为错误处理完全按照编写的方式工作:它捕获了错误,记录在某处,然后返回了 null。代码很”防御性”。这种防御完美地隐藏了问题。

错误处理不是关于防止崩溃。它是关于保留修复实际问题所需的信息。一个带堆栈跟踪的崩溃是信息。一个 null 返回是信息销毁。

comments powered by Disqus