在设计 Go REST API 的返回码时,是否需要规划子错误码取决于具体的业务复杂度和客户端需求。以下是分场景分析及建议:
一、什么情况下需要子错误码?
1. 业务场景复杂,需精细化错误分类
-
示例:用户模块的认证错误可能包含
TOKEN_EXPIRED
、INVALID_TOKEN
、PERMISSION_DENIED
等不同原因,子错误码能明确区分。 -
场景:需客户端根据不同错误类型采取不同逻辑(如 Token 过期需跳转登录页)。
2. 第三方服务或跨团队协作
-
示例:支付模块调用外部风控服务失败,子错误码可标识是网络超时 (
NETWORK_TIMEOUT
) 还是风控策略拦截 (POLICY_DENY
)。 -
场景:需快速定位问题根源,减少沟通成本。
3. 错误码需支持动态扩展
-
示例:订单模块未来可能新增子错误码(如
PAYMENT_CHANNEL_CLOSED
),子错误码体系需预留扩展性。
二、什么情况下不需要子错误码?
1. 错误类型简单且稳定
-
示例:仅区分“参数缺失”和“参数格式错误”,主错误码已足够。
-
场景:内部工具类 API,客户端处理逻辑简单。
2. 错误信息可通过 message
字段描述
-
示例:返回
code: "INVALID_PARAM"
和message: "手机号格式不正确"
,无需子错误码。 -
场景:错误原因单一,且无需客户端做差异化处理。
3. 客户端兼容性要求高
-
示例:前端代码固化,无法适配动态子错误码。
-
场景:遗留系统改造,需避免错误码结构变动。
三、如何设计子错误码?
1. 结构化设计(推荐)
-
格式:
主错误码:子错误码
(如USER_AUTH:TOKEN_EXPIRED
)。 -
示例响应:
{ "code": "USER_AUTH:TOKEN_EXPIRED", "message": "Token 已过期,请重新登录", "detail": "当前 Token 有效期至 2023-10-01 12:00:00", "request_id": "xxx" }
2. 分层设计(模块前缀)
-
格式:
模块名_错误类型_子类型
(如PAYMENT_ALIPAY_NETWORK_TIMEOUT
)。 -
示例:
const ( ErrPaymentAlipayNetworkTimeout = "PAYMENT_ALIPAY_NETWORK_TIMEOUT" ErrPaymentAlipayPolicyDeny = "PAYMENT_ALIPAY_POLICY_DENY" )
3. 嵌套结构(JSON 字段扩展)
-
格式:在
detail
或metadata
中嵌套子错误码。 -
示例响应:
{ "code": "ORDER_PAYMENT_FAILED", "message": "支付失败", "detail": { "sub_code": "PAYMENT_CHANNEL_CLOSED", "reason": "支付宝渠道已关闭" } }
四、子错误码的优缺点
优点
-
精准定位问题:客户端或运维人员可快速识别错误根源。
-
减少冗余信息:避免在
message
中写冗长的错误描述。 -
扩展性强:通过子错误码体系,未来新增错误类型更灵活。
缺点
-
复杂度增加:需维护更细粒度的错误码文档。
-
客户端适配成本:需解析子错误码并处理多级逻辑。
-
过度设计风险:简单场景下可能冗余。
五、实践建议
1. 根据场景选择
-
复杂业务(如支付、风控):推荐子错误码,提升可维护性。
-
简单业务(如内容管理):主错误码 + 详细
message
足够。
2. 保持一致性
-
统一子错误码分隔符(如
:
或_
)。 -
按模块划分前缀(如
USER_XXX
、ORDER_XXX
)。
3. 文档化
-
提供完整的子错误码列表及对应处理建议。
-
示例:
USER_AUTH:TOKEN_EXPIRED → 客户端应跳转登录页 USER_AUTH:INVALID_TOKEN → 客户端应清除本地 Token 并重试
4. 兼容性设计
-
旧版 API 避免频繁修改子错误码结构。
-
通过版本号(如
/api/v1/xxx
)隔离变更。
六、代码示例(Go)
// 定义错误码常量
const (
ErrUserAuthInvalidToken = "USER_AUTH:INVALID_TOKEN"
ErrUserAuthTokenExpired = "USER_AUTH:TOKEN_EXPIRED"
)
// 返回错误响应
func WriteErrorResponse(w http.ResponseWriter, code string, message string) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"code": code,
"message": message,
})
}
// 使用示例
if tokenExpired {
WriteErrorResponse(w, ErrUserAuthTokenExpired, "Token 已过期")
}
总结
有必要规划子错误码的场景:业务复杂、需精细化错误处理、跨团队协作或长期维护的系统。
无需子错误码的场景:简单业务、错误类型稳定或客户端兼容性受限时。
最终决策应权衡开发成本、维护成本和客户端需求。