响度定律:你的代码应当大声喊叫,而不是低声细语
Source: Dev.to
我们都经历过这种情况。星期五下午 4 点,你盯着一段毫无头绪的堆栈跟踪。
错误是经典的 JavaScript 致命错误:Cannot read properties of null (reading ‘email’)。
你点击行号。它指向一个尝试渲染用户资料的 React 组件。代码看起来没问题——user 对象应该在那里。为什么它是 null?
追溯数据来源,你发现一个调用服务的辅助函数,而该服务又调用了一个 API 工具。层层嵌套五层后,罪魁祸首是一个名为 getUser 的函数。它遇到了数据库错误,但没有向任何人报告,而是悄悄返回 null,让应用继续运行。
这叫做 “Passing the Buck.” 症状(崩溃)出现在离根本原因(数据库故障)相隔数分钟、跨越多个文件的地方,使其成为最难调试的 bug 类型。
Source: …
静默失败
初级工程师常常担心会导致应用崩溃,于是捕获错误、把它们掩盖起来,并返回一个“安全”的值,例如 null、false 或 -1。这会把应用置于 Zombie Mode——它半死不活地继续运行。
当你悄悄返回 null 时,你把错误处理的责任转嫁给了调用者。每一个使用你函数的地方都必须记得检查 null。只要有一个开发者忘记检查,应用稍后就会崩溃,而没人知道原因。
高级工程师遵循另一条规则:如果一个函数无法实现其名称所承诺的功能,就应该立即抛出错误。 这就是 The Law of Loudness(响亮法则)。
如果我调用一个名为
getUser(id)的函数,我期望得到一个用户。我不期待null。如果因为数据库着火而无法给我用户,请不要递给我一个空盒子并微笑。请大声喊出来。
我们希望 Fail Fast(快速失败)。使用异常来保护数据完整性;如果数据有问题,就立即停止生产线。
前后对比:应用响度法则
之前 – “安全”的沉默失败
// BEFORE: The "Safe" Silent Failure
function getUser(id) {
// If the DB is down, we hide it.
if (!db.isConnected()) {
console.log("DB is down...");
return null; // <--- Passing the buck
}
const user = db.find(id);
// If user isn't found, we return null again.
// Is it null because they don't exist?
// Or because the DB is broken?
// The caller will never know.
return user || null;
}
// THE RESULT:
// The caller code assumes it got a user.
const user = getUser(50);
// CRASH happens here, 10 lines later.
console.log(user.profile.email);
之后 – 响度法则
// AFTER: The Law of Loudness
function getUser(id) {
// 1. Guard Clause for System Failure
if (!db.isConnected()) {
// Stop execution immediately. We know EXACTLY what broke.
throw new DatabaseConnectionError('Cannot fetch user: DB is down');
}
const user = db.find(id);
// 2. Guard Clause for Data Integrity
if (!user) {
// Differentiate between "System Broken" and "Not Found"
throw new NotFoundError(`User with ID ${id} does not exist`);
}
return user;
}
// THE RESULT:
// The crash happens INSIDE getUser.
// The stack trace points exactly to the line that failed.
// We know if it was a DB error or a missing user instantly.
大声的好处
- 调试速度 – 当应用崩溃时,堆栈追踪直接指向
getUser。你可以立刻知道是数据库宕机还是用户缺失,从而节省在 UI 组件中查找null值的数小时。 - 可信任 – 调用者无需重复进行
if (user !== null)检查。如果执行能够继续到调用之后,说明已经拥有了有效的用户。 - 数据完整性 – 防止“僵尸”——半成形的数据对象——在状态管理中漂浮并导致奇怪的错误(例如,保存一个未定义 ID 的个人资料)。
不要害怕控制台中的红色文字。红色文字是诚实的。沉默的 null 是谎言;它在系统一切正常时,却在实际房子已经燃烧时保持沉默。
规则
- 预期错误(例如,搜索可能不存在的产品)——返回专门的 Result 类型或
null,仅当null是有效的领域状态时。 - 意外错误(网络中断、配置错误、缺少必需数据)——抛出。大声。
结论
不要仅仅为了取悦编译器而写代码。拥抱响亮法则,快速失败,让错误被听见。
本文摘自我的手册 “The Professional Junior: Writing Code that Matters.” 它是一本关于未成文工程规则的实用指南。
👉 获取完整手册