后端错误码设计原则:HTTP状态码与业务错误码的区分、错误码分段规则、错误码语义化设计
在集成测试环境下,错误码是后端服务与调用方(包括测试断言)之间最核心的契约。设计混乱的错误码会导致测试脚本难以维护、故障定位效率低下,甚至引发安全合规风险。本章从三个维度阐述后端错误码的设计原则:HTTP状态码与业务错误码的区分、错误码分段规则、错误码语义化设计。
1. HTTP状态码与业务错误码的区分
HTTP状态码(如 200、400、500)用于表达传输层与协议层的结果,而业务错误码用于表达应用层与业务逻辑层的具体错误原因。两者必须严格分离,不可混用。
| 维度 | HTTP 状态码 | 业务错误码 |
|---|---|---|
| 职责 | 表示请求的传输结果(成功、客户端错误、服务端错误) | 表示业务逻辑的具体失败原因(如余额不足、权限不足) |
| 粒度 | 粗粒度(约 60 个标准码) | 细粒度(可扩展至数千个) |
| 变更频率 | 极低(RFC 标准,几乎不变) | 随业务迭代频繁变更 |
| 安全合规关注点 | 避免泄露内部信息(如 500 不应带堆栈) | 避免暴露敏感业务逻辑或数据 |
| 集成测试断言 | 断言响应状态码是否在预期范围内 | 断言业务错误码是否匹配预期值 |
核心原则:HTTP 状态码只回答“请求是否被正确接收和处理”,业务错误码回答“如果处理失败,具体原因是什么”。集成测试中,两者必须同时断言,缺一不可。
典型响应体结构示例:
{
"code": 100301,
"message": "用户账户余额不足",
"detail": "当前余额: 10.50,所需金额: 200.00",
"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
此时 HTTP 状态码为 400 Bad Request,业务错误码 100301 精确标识了“余额不足”这一业务场景。
2. 错误码分段规则
错误码应采用数字分段方式,将不同模块、不同严重级别、不同错误类型映射到固定的数字区间。分段规则是错误码体系可维护、可扩展的基础。
推荐分段策略:采用 6 位数字编码(XXYYZZ),前两位标识模块,中间两位标识错误大类,后两位标识具体错误。预留足够的扩展空间,避免后期重构。
3. 错误码语义化设计
语义化设计要求错误码本身能够传达足够的上下文信息,使开发人员、测试脚本、运维人员无需查阅文档即可大致理解错误性质。语义化包含三个层面:可读性、可分类性、可搜索性。
3.1 可读性:错误码与错误消息的配对
每个错误码必须绑定一个固定的、面向开发者的错误消息(message),以及一个可选的面向用户的用户提示(userHint)。消息内容应遵循以下规范:
- 动词优先:以“缺少”、“无效”、“超过”、“禁止”等动词开头。
- 参数化:使用占位符代替具体值,避免硬编码。
- 无歧义:同一错误码只能对应一种错误场景。
// 反例:语义模糊
100401: "操作失败"
// 正例:语义清晰
100401: "用户 [userId] 对资源 [resourceId] 无 [permission] 权限"
3.2 可分类性:按严重级别与错误来源分段
在分段规则基础上,进一步按严重级别划分区间,便于集成测试设置不同的断言策略:
| 错误码区间 | 严重级别 | 集成测试断言策略 |
|---|---|---|
| XX00-XX49 | 参数校验错误 | 断言 HTTP 400,业务码精确匹配 |
| XX50-XX79 | 业务逻辑错误 | 断言 HTTP 400/409,业务码精确匹配 |
| XX80-XX89 | 权限/安全错误 | 断言 HTTP 403,业务码精确匹配,检查安全审计日志 |
| XX90-XX99 | 系统内部错误 | 断言 HTTP 500,业务码精确匹配,检查是否泄露堆栈 |
3.3 可搜索性:错误码的命名与文档化
错误码应作为一等公民纳入 API 文档和测试用例管理。推荐做法:
- 在代码中以枚举或常量形式定义,禁止魔法数字。
- 每个错误码附带
@since、@deprecated等元数据。 - 集成测试中直接引用错误码常量,而非硬编码数字。
// Java 枚举示例
public enum ErrorCode {
USER_BALANCE_INSUFFICIENT(100301, "用户账户余额不足"),
ORDER_NOT_FOUND(200101, "订单不存在"),
PAYMENT_TIMEOUT(400201, "支付超时");
private final int code;
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() { return code; }
public String getMessage() { return message; }
}
安全合规红线:错误码 message 中禁止包含敏感信息(如数据库表名、SQL 片段、完整堆栈、用户密码、Token 明文)。集成测试应额外断言:当错误码为系统内部错误(XX90-XX99)时,响应体不包含
stackTrace 或 exception 字段。
4. 集成测试中的错误码断言实践
基于上述设计原则,集成测试断言应遵循以下模板:
// 伪代码示例
Response response = given()
.header("Authorization", "Bearer " + token)
.body(requestBody)
.when()
.post("/api/v1/order/create");
// 第一步:断言 HTTP 状态码
response.then().statusCode(400);
// 第二步:断言业务错误码
int bizCode = response.jsonPath().getInt("code");
assertThat(bizCode).isEqualTo(ErrorCode.USER_BALANCE_INSUFFICIENT.getCode());
// 第三步:断言错误消息不含敏感信息
String message = response.jsonPath().getString("message");
assertThat(message).doesNotContain("stackTrace", "SQL", "password");
通过严格遵循 HTTP 状态码与业务错误码的分离、数字分段规则、以及语义化设计,集成测试脚本可以获得稳定、可预测、可维护的错误断言基础,同时满足安全合规的审计要求。