本节摘要:错误处理是把"违约时刻"做成机器可读的违约书——状态码表层级、错误码表业务细分、响应体结构统一承载。本节用一张状态码族谱整理 4xx/5xx 的分工,再用一个"字段违规清单"案例演示错误体怎么让客户端能自动识别并给出友好提示。
接手过一个第三方接口:整张错误处理表里只有一条 400,无论参数错、凭证过期、还是资源不存在,响应体清一色写"请求失败"。客户端想给用户提示,得去解析一段中文人话;想判断能不能重试,根本无从下手。后来一次对方把状态码升级,所有客户端一起懵——既不知道哪里错,也不知道该不该重发。错误处理如果只做到"返回失败",离"契约级的违约条款"还差得远。本节就从"机器可读"这四个字讲起。
阅读完本节,你应当能够:
很多接口把错误处理当成"把错误消息打印进响应体"。这离契约思维还差一截。错误的本质是"违约时刻",要达成的目标是机器可读:客户端收到一个错误,不用解析人话、不用猜,就能知道"这是谁的错、是什么类型的错、该不该重试、怎么修"。这要求错误具备三个层次:状态码定层级、错误码定业务、结构定表达。
把三个层次各管什么先钉死:状态码回答"是谁的问题、归哪个大类",往宽了分;错误码回答"具体是哪一档子业务问题",往细了分;错误体结构回答"怎么把细节整齐地交给客户端",往统一分。三层缺一层,错误处理都会在某次故障里露怯——要么客户端分不清该不该重试,要么解析不出具体字段错在哪。
先用状态码划总纲(一张族谱图可一眼记住):

关键分层:4xx 表示"你这请求本身有问题,改它才对",5xx 表示"服务器没搞定,重试它可能有用"。这个分界直接指导客户端的作文——4xx 不盲目重试(重试更错),5xx 可以带退避重试。
常用的几个别只认数字,要对号入座:
| 状态码 | 含义 | 该不该重试 |
|---|---|---|
| 400 | 请求格式/参数错 | 不该,改了再发 |
| 401 | 凭证缺失或过期 | 先重新认证 |
| 403 | 无权限,凭证有效但不许做 | 不该 |
| 404 | 资源不存在 | 先查 URI 是否写对 |
| 409 | 与当前状态冲突 | 看清冲突再决定 |
| 429 | 触达限流 | 按 Retry-After 退避 |
| 500 | 服务器内部错 | 可带退避试 |
| 503 | 服务暂时不可用 | 按 Retry-After 退避 |
这一张表足够日常用;小到别把"凭证过期"写成 400,大到别把"服务过载"写成 500 抹掉重试跳板——状态码选得准,客户端重试策略才写得对。
状态码只告诉"谁的错",还不够告诉"具体哪里错、怎么修"。要用统一错误体承载细节。一个被普遍采用的三层结构:
| 层 | 字段 | 作用 |
|---|---|---|
| 应用错误码 | code |
机器可判定的稳定业务码 |
| 可读消息 | message |
人类能懂,可展示给用户 |
| 详情数组 | details |
逐字段列违规,便于定位 |
{ "code": "INVALID_INPUT", "message": "提交的内容不合法。", "details": [ {"field": "email", "reason": "邮箱格式不正确"}, {"field": "password", "reason": "密码至少 8 位"} ] }
拿到这份错误体,客户端能:
code 做分支判断(比如 INVALID_INPUT 就高亮表单对应字段);details 里的 field 勾字段、贴 reason 展示给用户;message 兜底给人做通用提示。关键是结构全接口一致,字段名别一会 message 一会 msg。机器才能稳定解析。
code 不是让你随手发明的临时码,它本身值得一张"错误码目录"。把每个 code 配上一句机器可依赖的稳定含义、一个公开的文档入口,日子久了就能沉淀成一份"错误码字典",客户端拿着它做分支、告警、降级。目录的管理有两个纪律:一是新码要登记、旧码要标记弃用,别让 INVALID_INPUT 和 INVALID_INPUT_V2 并存叫人懵;二是不要把"这次的具体参数值"编进 code(如 EMAIL_ALREADY_TAKEN_a@b.com),具体值放 details 或 message,code 只做稳定归类。码越稳定,客户端越敢长期依赖,错误契约才真正"可 trust"。
把三层结构用到实战定位。你收到一条用户反馈:"登录一直报错"。回答里带上模拟日志:
HTTP/1.1 401 Unauthorized {"code":"AUTH_FAILED","message":"凭证无效。","details":[]}
背景:用户发登录请求。
操作:服务器验凭证未通过。
结果:401 + AUTH_FAILED。
解读:401 属 4xx,问题在客户端(凭证错/过期),不是服务器,重试无用,应引导重新输入或刷新令牌。
变式:若同一请求收到 503 + SERVICE_OVERLOADED 与 Retry-After 头,则是 5xx,且服务器明说"稍后再来",客户端应带退避重试而非报硬错误。
⚠️ 常见坑:把所有错误都返回 400,错误详情里才写"是 401 还是 409"。状态码分层的意义就在"不用解析 body 也能判断大方向",把层次塞进 body 等于自废武功。
💡 关键直觉:错误契约的价值在"一致性 + 机器可读"。宁可错误码少而稳定,也不要用一排会变的临时码。稳定的
code是客户端能长期 trust 的锚。
错误条款兜住了"违约时刻",下一节给契约上第一道防线——认证、授权、传输加密三件套。