mcpField
A
awesome-error-standards

awesome-error-standards

github.com/khasky开发2255 次安装1 周前更新
代码仓库

关于 awesome-error-standards

用于设计和审查 API 与应用一致错误处理方式的 Skill,帮助开发者分类故障、定义安全的错误契约,并在不泄露内部信息的前提下便于诊断。

主要功能

  1. 01区分运行故障与程序错误
  2. 02定义结构化类型错误模式
  3. 03将 API 错误映射到 HTTP 状态码
  4. 04在边界处验证输入
  5. 05记录带安全上下文的故障
  6. 06指导重试与静默故障诊断

适用场景

  1. 01统一 API 错误响应
  2. 02重构应用中的故障处理
  3. 03审查重试和日志行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/khasky/awesome-agent-skills --skill awesome-error-standards

安装后在 Claude Code 中输入/awesome-error-standards确认已加载。

附带文件evals/eval_queries.json

详细介绍

错误处理模式

采用一致的抛出、捕获、记录和呈现错误的模式,让故障可预测且便于调试,同时不泄露内部信息。

适用场景

  • 在 API 或应用中实现或重构错误处理
  • 用户询问“错误处理”“如何处理错误”或“一致的错误”
  • 审查代码中的故障处理是否妥当
  • 为服务或 API 定义或编写错误契约文档

核心原则

  • 区分运行时错误与程序员错误——先分类。运行时故障(网络超时、输入无效、记录缺失)是可预期的:应处理、重试或呈现给用户。程序员错误(访问未定义内容、破坏不变量)属于缺陷:应明确崩溃或重启进程,而不是在状态已损坏的情况下勉强运行。
  • 让缺陷无法发生,而不只是修复它——单点修复只能阻止一条路径;纵深防御则能阻止整类问题。优先在进入生产环境前预防,依次采用:(1) 在类型系统中让无效状态无法表示(判别联合、品牌类型/不透明类型),使错误状态无法通过编译;(2) 在边界处进行验证;(3) 使用运行时保护;(4) 使用错误边界。对于高价值不变量,应在多个层级进行保护,并测试绕过外层后内层仍能捕获问题(主动跳过第 1 层,确认第 2 层能捕获;确认 mock 不会悄然禁用验证)。 按阶梯评估每项修复的级别。控制:错误无法发生(类型、API 结构、移除的选项)。警告:错误发生时会立即显现(编译错误、验证失败、lint 规则)。检测:之后才会发现问题(测试、监控、告警)。最低一级只是注释、文档中的一句话或“务必小心”;这不是修复,不计入等级。监控并非预防:如果缺陷已经影响用户,此时才触发告警,只是转移了问题,并未阻止问题。修复报告应说明修复使哪些情况无法发生,以及明确保留了哪些可能性。
  • 在启动时失败,而不是在生产环境中失败——初始化时验证所有必需配置、环境变量和连接;验证失败则拒绝启动,而不是等到首次请求时才发现缺少值。
  • 尽早失败——尽早验证输入和前置条件;抛出错误或返回带有明确消息的错误。不要在状态无效时继续执行。
  • 不要吞掉错误——避免使用空的 catch 块或忽略错误的宽泛 catch。应记录并/或重新抛出错误,或返回结果类型。传播错误时应附带上下文。
  • 结构化错误——API 错误和日志应使用一致的结构(例如 code、message、details)。带类型的错误类或错误代码有助于调用方按类型处理。
  • 区分用户与开发者——面向用户的消息应安全且可操作;面向开发者的详细信息(堆栈、内部消息、请求 ID)仅应出现在日志或调试信息中。绝不要向最终用户泄露堆栈跟踪或 SQL。

工作流程

  1. 确定边界——错误从哪里产生(验证、DB、外部 API、身份验证)?在哪里处理(路由处理程序、中间件、顶层)?确保每一层都能处理错误,或附带上下文向上层传播。
  2. 定义错误结构——对于 API:HTTP 状态码 + 响应体封装(code、message、可选的 details)。对于代码:错误类或结果类型。若项目已有模式,应与之保持一致。
  3. 将错误映射到 HTTP 状态码(适用于 API)——验证 → 400 或 422;身份验证 → 401;授权 → 403;未找到 → 404;冲突 → 409;服务器错误 → 500。发生错误时,不要返回带有 success: false 的 200。为验证失败选择一种风格(400 或 422),记录该约定,并在所有端点统一应用——混用会破坏生成的客户端及调用方的错误分支处理。
  4. 响应前记录日志——在适当的级别(error/warn)记录附带上下文的错误(请求 ID、安全时可记录用户 ID、操作)。然后返回对用户安全的响应。
  5. 验证并清理数据——在 API 边界使用 schema(Zod、Pydantic 等);以一致的格式返回字段级验证错误。

API 错误响应

标准封装:

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed",
    "details": [
      { "field": "email", "message": "Must be a valid email address", "code": "invalid_format" }
    ]
  }
}
  • code — 机器可读(例如 validation_error、not_found、rate_limit_exceeded)。客户端可以据此进行分支处理。
  • message — 面向用户的可读信息,可安全地展示给用户。不包含堆栈跟踪或内部路径。
  • details — 可选;对于验证错误,列出字段级错误。对于通用 500 错误则省略。
  • request_id / trace_id — 可选,放在封装或标头中以供支持排查;不要暴露内部信息。

HTTP 状态码:400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found、409 Conflict、422 Unprocessable Entity、429 Too Many Requests、500 Internal Server Error。应按语义使用;不要用 200 表示错误。

重试失败的调用(客户端)

根据重试是否有帮助对每种失败进行分类——重试不可重试的错误只会浪费时间,还可能造成重复操作:

  • 永不重试(修正请求):400、401、403、404、415、大多数 422——输入或身份验证有误,重试不会改变结果。
  • 修改请求后重试:402(充值 / 更改套餐)、部分 422(调整负载)。不要盲目重试。
  • 退避后重试:429、500、502、503、504——临时错误。限制尝试次数(约 5 次)和总等待时间(约 30 秒);使用带抖动的指数退避,以避免惊群效应。

规则:

  • 遵循服务端的信号——如果存在 Retry-After 和 X-RateLimit-Reset/X-RateLimit-* 标头,应优先遵循它们,而不是使用自己的计时器。
  • 对重试的状态变更请求使用 Idempotency-Key——POST/PATCH 请求可能已在服务端成功扣费、创建或发送,但响应却丢失;简单重试会导致重复扣费。发送稳定的幂等键,以便服务端进行去重。
  • 流式传输(SSE/websocket)——错误会作为流内事件(error 事件或终止帧)到达,而不是作为 HTTP 状态码,因为流打开时状态码已经是 200。应显式处理流的错误通道,而不只是检查初始响应码。

代码模式

类型化错误(推荐):

class NotFoundError extends Error {
  constructor(resource: string) {
    super(`${resource} not found`);
    this.name = 'NotFoundError';
  }
}
// Caller: catch (e) { if (e instanceof NotFoundError) return res.status(404).json(...); }

分层处理:底层抛出错误或返回 Result;路由处理程序或中间件捕获错误,并将其映射为 HTTP 响应并记录日志。不要在每一层都捕获错误;应向上传播,并在边界处处理。

异步:在异步函数中使用 try/catch;确保 Promise 拒绝得到处理(例如通过全局处理程序或 .catch),以便记录日志且不留下未处理的拒绝。为 unhandledRejection/uncaughtException(或平台对应机制)注册边界处理程序:记录带有上下文的信息,然后针对程序错误退出——不要在状态已损坏时继续运行。

错误日志记录

  • 级别:需要关注的故障使用 error;可恢复的情况(例如重试、回退)使用 warn。
  • 内容:记录消息、错误类型、堆栈(仅限服务端)、request_id。不要记录密钥或完整的 PII。
  • 只记录一次:在处理错误的边界处记录日志;避免在每一层重复记录同一错误。
  • 安全序列化:在日志 / 错误处理路径中使用安全序列化器(safeStringify、结构化日志记录器自带的序列化器),不要直接使用 JSON.stringify——它会因循环引用和 BigInt 而抛出异常,甚至导致尝试报告故障的处理程序自身崩溃。

规则

  • 遵循项目中现有的模式(错误类、响应结构、日志记录)。如果没有现成模式,则引入最精简且一致的模式,并将其记录下来。
  • 不要向最终用户暴露内部细节(堆栈跟踪、数据库错误、文件路径);仅在服务端记录这些信息。
  • 验证错误:返回带有字段级详情的 400/422,而不是 500。

静默失败

这是最棘手的一类:一开始运行正常,返回 200,却悄无声息地什么都没做(或做错了)。按顺序诊断:组件未接线 / 未注册 → 未定义引用被解析为空操作 → 类型 / 信号不匹配导致负载丢失 → 错误被吞掉(catch {}、|| true)→ 上游采样 / 过滤导致下游指标悄然出错。切勿因某个键被拒绝就断定功能不存在——发布降级配置之前,先查找该组件自己的键;如果替换了工具 / 方法,应明确说明,而不是报告原有功能缺失。

检查清单

  • 在边界处验证输入;以标准格式返回验证错误
  • 将错误映射到正确的 HTTP 状态码(错误时不要返回 200)
  • 面向用户的消息应安全且可操作;详细信息仅记录在日志中
  • 响应前记录错误及上下文(请求 ID、操作)
  • 不要留空的 catch;应处理错误、记录并重新抛出,或返回结果类型

反模式

反模式更好的做法
使用 { success: false, error: "..." } 返回 200 OK使用正确的 HTTP 状态码(4xx/5xx)和错误正文
空的 catch 块记录并重新抛出,或返回 Err(result);如果有意忽略,则说明原因
丢弃错误:Go 中的 _ := f()、Python 中的裸 except:、未经检查的 Close()处理错误,或就地说明忽略错误为何安全
在库代码中因操作错误调用 panic() / process.exit()返回或抛出带类型的错误;由边界决定如何处理
出错时记录完整请求/响应记录 ID 和摘要;对机密和个人身份信息进行脱敏
向客户端暴露堆栈跟踪在服务端记录堆栈;向客户端返回通用消息
各端点的错误格式不同在整个 API 中使用统一的封装格式(code、message、details)
领域错误与传输层相关:服务或存储库返回或抛出框架响应对象或 HTTP 状态码抛出与传输层无关的带类型错误;由唯一的全局处理器将其映射到 HTTP——业务逻辑与交付方式保持解耦,更易于复用和测试

评论

支持 Markdown