数据与服务 · 运行与可靠性
错误处理Error Handling
检测错误、加上下文、决定怎么处理(翻译 / 降级 / 重试 / 失败),给用户可行动的反馈,给系统可追查的日志——而不是崩溃、静默吞掉或原样泄漏。
错误处理实验室:同一个错误,四种接法
一个订单查询接口面对四种故障。你触发错误,再选择处理策略——吞掉、原样抛出、带上下文抛出、降级——看响应、日志和用户提示分别长成什么样。
边界 try / catch 接住错误后,你怎么处理决定用户和系统看到什么。 先选一种故障,点「发送请求」看未处理时的原始错误,再选处理策略,观察响应、日志和用户提示如何变化。
GET /api/orders/ord_882订单查询接口你的处理函数怎么接住这个错误?(先点「发送请求」触发错误)
提示:数据库断开 + 带上下文抛出,是最稳的受控失败。
四条规则:分类、翻译、留痕、降级
面板里刚操作过的东西,命名成可复述的规则。
- 绝不静默吞:空 catch 把错误当成功,是最坏的选择——响应 200 空体、无日志,问题被掩埋,监控也不会告警。
- 不原样抛:把原始错误甩给客户端,状态码是吓人的 500,响应体暴露堆栈、文件路径、内网地址;既危险也无用。
- 翻译成有意义错误:把底层错误映射到合适的 HTTP 状态——校验失败 400、不存在 404、暂时故障 503/504——并给一句可行动提示。
- 区分预期和意外:预期错误(字段错、找不到)是正常结果,记 INFO/WARN、返回 4xx;意外故障(DB 断、OOM)记 ERROR/告警、返回 5xx。
- 响应记结论,日志记细节:用户提示和响应体只放可公开内容;requestId、目标地址、字段名、耗时这些细节放进日志(链接 log)。
什么时候用
错误处理不是“加个 try/catch 就行”,而是任何可能失败的边界都要做的决策。
看到这些信号就要想“怎么处理”:调用数据库或下游服务、解析外部输入、写文件或发请求、执行可能抛异常的库函数。不适合的处理方式:在生产代码里用空 catch 压住一切、把所有异常都当成 500、把原始 stack 直接渲染给用户、 或者在循环里无限重试一个不会自己好的错误(比如权限不足)。
怎么用
从一次失败到“用户有下一步、系统能追查”的最短路径。
- 在请求边界
try / catch,给每次请求注入 requestId 并透传到日志和下游。 - 分类错误:是校验失败、不存在,还是依赖故障?据此选择 4xx、5xx 或降级。
- 翻译:构造有意义的 HTTP 状态 + 面向用户的提示;响应体不带堆栈和内部细节。
- 留痕:在日志里记 level、requestId、目标、字段、耗时(链接 log、monitoring);敏感字段脱敏。
- 读接口可降级到缓存;写接口对幂等操作有限重试(链接 idempotency),持续失败要熔断或排队。
正反例:同一笔 DB 故障
只改一个关键选择:catch 之后是吞掉,还是翻译成受控失败。
try { return await db.orders.find(id) }
catch (e) { return null } // 200 空 → 用户以为成功,监控瞎数据库断了却返回 200 空:脏数据落库、用户无感知、日志无记录、告警不触发。事后追查时两眼一抹黑。
try { return await db.orders.find(id) }
catch (e) {
log.error('db unreachable', { requestId, target, id })
return response(503, { error: 'service_unavailable',
retry_after: 30 })
}翻译成 503 + retry_after,告诉客户端是暂时故障可重试;日志按 ERROR 记目标和 requestId,监控据此告警;响应不泄内部细节。
快速自测
写订单时数据库连接断了。下列处理最合适的是?
继续查证
术语的技术定义和行为以这些一手或权威资料为准。
下一步学
和本知识点经常一起出现的概念。