- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
导读
在 Node.js 后端应用的日常开发中,绝大多数线上事故都源于对错误类型的混淆:要么把可预期的业务错误当成灾难导致无谓重启,要么把未知的程序缺陷当成"小问题"继续运行而让应用处于残缺状态。本篇指南基于 nodebestpractices 仓库《Node.js 最佳实践清单》第 2.3 条「区分操作错误与程序员错误」(对应文档 sections/errorhandling/operationalvsprogrammererror.brazilian-portuguese.md),系统讲解两类错误的判别标准、如何通过isOperational标记错误对象、如何构建集中式错误处理器并据此决定"记录日志"还是"退出进程重启"。读完你将掌握一套可落地的错误分类与崩溃决策方案,显著降低应用停机时间并避免难以排查的隐性缺陷。
什么是操作错误,什么是程序员错误
区分以下两种错误类型,将最大限度减少应用停机时间,并帮助你避开"疯狂"的 bug:
- 操作错误(Operational errors):指你完全理解发生了什么、以及其影响范围的错误。例如,某个 HTTP 服务查询因为连接问题而失败、用户输入了非法数据、上游接口返回了 5xx。这类错误的特点是"已知、可预期、可分类",处理方式相对简单——通常只需要记录日志即可。
- 程序员错误(Programmer errors):指你完全不知道原因、甚至不知道错误从哪里冒出来的情况。例如某段代码读取了未定义的值,或者数据库连接池发生内存泄漏。这类错误意味着代码本身有缺陷,应用程序可能已经处于不一致的状态,此时除了"优雅重启"之外没有更好的选择。
简单地说:操作错误是业务环境给出的可预期反馈,程序员错误是程序自身的 bug。在 README.md 的第 2.3 条中,仓库还将其表述为 "catastrophic errors"(灾难性错误)与 operational errors 的对比,强调二者需要完全不同的处置策略。
为什么要区分:权衡"无谓停机"与"带病运行"
如果不去区分错误类型,常见的两个极端都会带来问题:
- 一律重启:任何错误都让应用崩溃重启。为了一次小小的、可预期的操作错误(例如某个接口收到了一个无效参数),就让线上约 5000 个在线用户全部断线,显然不合理。
- 一律不重启:应用中出现未知的程序员错误(灾难性错误)时仍然硬扛着继续运行。此时某些对象可能处于损坏状态(例如某个全局使用的单例状态机丢失了内部状态),后续所有请求都可能失败或行为异常。
正如 README 中所述:区分两类错误,才能根据上下文采取"平衡的处置方式"——操作错误走正常的错误响应与日志流程,程序员错误则果断触发优雅重启。
代码示例:将错误标记为可操作的(操作错误)
为了让集中式错误处理器能够区分两类错误,最直接的做法是在错误对象上打上标记:
// 将一个错误对象标记为操作错误 const myError = new Error("当我未提供任何值时,如何添加新产品?"); myError.isOperational = true;更规范的做法是使用一个集中式错误工厂(详见同模块的 「只使用内置 Error 对象」 小节),构造时显式传入isOperational:
// 集中式错误工厂 class AppError { constructor (commonType, description, isOperational) { Error.call(this); Error.captureStackTrace(this); this.commonType = commonType; this.description = description; this.isOperational = isOperational; } } // 抛出时显式声明这是一个操作错误 throw new AppError(errorManagement.commonErrors.InvalidInput, "在这里描述发生了什么", true);这里Error.captureStackTrace(this)用于保留完整的调用栈信息;第三个参数true表示"这是一个可信任的、可预期的操作错误"。
纵深:基于内置 Error 统一扩展 AppError
仓库在同一错误处理模块中进一步给出了 TypeScript 版本的规范化做法(见 useonlythebuiltinerror.md)。与上面用构造函数方式不同,TS 中推荐用class ... extends Error,并且必须显式恢复原型链:
// 集中式错误工厂:统一为所有应用级错误 export class AppError extends Error { public readonly commonType: string; public readonly isOperational: boolean; constructor(commonType: string, description: string, isOperational: boolean) { super(description); Object.setPrototypeOf(this, new.target.prototype); // 恢复原型链 this.commonType = commonType; this.isOperational = isOperational; Error.captureStackTrace(this); } } throw new AppError(errorManagement.commonErrors.InvalidInput, "在这里描述发生了什么", true);为什么不建议为每种错误(DbError、HttpError)各建一个类?仓库引用的观点给出了理由:JavaScript 语言层面并不太适合基于构造器类型的错误捕获,在对象属性上进行区分(如isOperational、commonType)远比基于构造器类型区分容易得多。因此最佳实践是:只扩展一次内置Error,得到唯一的AppError,再用构造参数区分不同的错误种类。这既保持了错误结构的统一性,又不会丢失StackTrace等关键信息。
决策落地:集中式错误处理器如何决定"崩溃"还是"记日志"
有了isOperational标记后,就可以在唯一的集中式错误处理器中做出崩溃决策。仓库在 shuttingtheprocess.md 中给出了完整模式:
// 假设开发者已用 error.isOperational=true 标记已知的操作错误 process.on('uncaughtException', (error) => { errorManagement.handler.handleError(error); if (!errorManagement.handler.isTrustedError(error)) process.exit(1); // 非操作错误 → 退出进程,交给 Restarter 重启 }); // 集中式错误处理器封装所有错误处理相关逻辑 function errorHandler() { this.handleError = (error) => { return logger.logError(error) .then(sendMailToAdminIfCritical) .then(saveInOpsQueueIfCritical) .then(determineIfOperationalError); } this.isTrustedError = (error) => { return error.isOperational; // 只有标记为操作错误的才是"可信任错误" } }关键逻辑非常清晰:
isTrustedError(error)返回error.isOperational——只有打上操作错误标记的才是可信赖的;- 不可信赖的错误(程序员错误)意味着某个组件可能处于损坏状态,所有后续请求都可能失败,此时杀掉进程,用 Forever、PM2 之类的 Restarter 工具以干净状态重新启动。
这与 centralizedhandling.md 描述的典型错误处理流程一脉相承:模块抛出错误 → API 路由捕获 → 转发给错误中间件 → 调用集中式错误处理器(记录日志、触发监控指标、按需崩溃或返回响应)。一个典型例子是:某个单例、有状态的服务抛出了异常并丢失了状态,从此刻起它可能行为异常导致所有请求失败——这正是必须走"崩溃重启"路径的场景。
纵深:连 Promise 的静默失败也不放过
区分错误类型的前提是能拿到错误。仓库在 catchunhandledpromiserejection.md 中特别提醒:现代 Node.js/Express 应用大部分代码运行在 Promise 中,如果开发者忘记添加.catch,这些错误不会被uncaughtException捕获而会直接消失。推荐的兜底方案是订阅process.on('unhandledRejection'),把未被处理的 Promise 拒绝重新抛出来,交由统一的uncaughtException处理器按isOperational决定去留:
process.on('unhandledRejection', (reason, p) => { // 既然已有兜底处理器,直接抛出,让统一的处理器去决策 throw reason; }); process.on('uncaughtException', (error) => { errorManagement.handler.handleError(error); if (!errorManagement.handler.isTrustedError(error)) process.exit(1); });同时,仓库也建议配合 failfast.md 中的"快速失败"思想:在函数入口用 Joi 之类的校验库验证参数,把因非法输入引发的操作错误尽早、显式地抛出并标记,而不是让它们变成难以追踪的隐性 bug。
权威观点:为什么程序员错误必须立即崩溃
原文档汇集了多篇业界权威论述,值得逐条理解其背后的工程共识:
观点一:程序员错误是程序中的 bug——最佳恢复方式是立即崩溃。(源自 Joyent 的博客,其在关键词 "Node.js error handling" 搜索中排名第一)
"从程序员错误中恢复的最佳方式是立即崩溃。你应该使用一个 restarter 来运行程序,在崩溃时自动重启。有了 restarter,在面对瞬时性程序员错误时,崩溃是恢复可靠服务的最快方式。"
观点二:没有安全的方式"接着干"而不制造脆弱的未定义状态。(源自 Node.js 官方文档)
"鉴于 throw 在 JavaScript 中的工作方式,几乎从来不存在一种安全的方式让你'从上次中断处继续',而不泄漏引用或制造某种未定义的脆弱状态。对抛出错误最安全的响应是关闭进程。当然,在一个普通 Web 服务器中,你可能开着很多连接,因为别人触发的错误而粗暴关闭它们并不合理。更好的做法是:向触发错误的那个请求返回错误响应,让其他请求正常完成,并让该 worker 停止监听新请求。"
观点三:否则你将赌上整个应用的状态。(源自 debugable.com 的博客,其在关键词 "Node.js uncaught exception" 搜索中排名第三)
"所以,除非你真的清楚自己在做什么,否则在收到
uncaughtException异常事件后,应当对服务执行一次优雅重启。否则,你就要承担应用状态(或第三方库状态)变得不一致的风险,进而引发各种疯狂的 bug。"
这三条观点共同指向一个结论:程序员错误发生后,进程的"内存画像"已经不可信,继续运行等于在不确定的地基上盖楼;崩溃 + 自动重启是成本最低、最可靠的恢复手段。
三种错误处理思想流派
关于错误处理,业界大致存在三种思想流派(源自 JS Recipes 博客):
- 让应用崩溃并重启它:简单粗暴,依赖进程管理器兜底;
- 处理所有可能的错误,永不崩溃:防御性极强,但工程成本高、难以覆盖全部路径;
- 两者之间的平衡方案:可预期错误(操作错误)妥善处理不崩溃,未知错误(程序员错误)果断崩溃重启。
本仓库推荐的正是第三种:以isOperational为分界线,让"记录日志 + 返回响应"与"退出进程 + 自动重启"各司其职。这与 README 中第 2.6 条 「陌生人来了就优雅退出进程」 的表述完全一致——未知错误出现时,唯一的选择是让错误可见、关闭连接并退出进程,交由 Docker 化服务或云 Serverless 平台负责重启。
落地检查清单
把上述实践落到你的项目时,可以对照以下要点自查:
- 统一错误载体:所有错误都抛
Error或其唯一扩展AppError,绝不抛字符串(见 useonlythebuiltinerror.md); - 显式分类标记:构造错误时明确传入
isOperational,或对已知业务错误设置error.isOperational = true; - 集中决策:日志、监控、邮件告警与"是否崩溃"的判断都收敛到唯一错误处理器(见 centralizedhandling.md);
- 兜底所有入口:同时订阅
uncaughtException与unhandledRejection,避免 Promise 静默失败(见 catchunhandledpromiserejection.md); - 配好自动重启:用 PM2、Forever 或容器编排保证崩溃后自动拉起,让"崩溃"成为可靠服务的一部分。
记住核心心法:操作错误是业务的一部分,记录即可;程序员错误是程序的裂缝,重启才是修复。用一行isOperational标记,换来的是线上服务在"无谓宕机"与"带病运行"之间的从容取舍。
- 文档
- 教程
- 后端
【免费下载链接】nodebestpractices
✅ The Node.js best practices list (July 2026)
相关推荐
Repomix 隐私策略全解析:CLI、网站与浏览器扩展的数据处理边界与安全设计
Repomix 隐私策略全解析:CLI、网站与浏览器扩展的数据处理边界与安全设计 本文基于 privacy.md https://link.gitcode.co
文档教程后端深入解析 CANN pyasc `MatmulApiTiling.set_split_range`:baseM/baseN/baseK 切分范围约束与 C0_size 对齐机制
深入解析 CANN pyasc MatmulApiTiling.set_split_range :baseM/baseN/baseK 切分范围约束与 C0_si
文档教程后端Dalamud错误分类:错误类型与处理策略
Dalamud错误分类:错误类型与处理策略 引言 Dalamud作为FFXIV(最终幻想14)的插件开发框架,在复杂的游戏环境交互中面临着各种异常情况。有效的错
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考