错误码必须带服务前缀(如usr-001),禁用纯数字;需集中定义、结构化封装、工厂函数创建,并在grpc响应体显式携带error_code字段,http状态码仅表协议层问题。

错误码必须带服务前缀,不能只用纯数字
收到一个 5001,你根本不知道是用户服务、订单服务还是支付网关返回的——跨服务调用时,纯数字错误码等于放弃溯源能力。前缀不是装饰,是定位依据。
- 每个服务定义固定短前缀,比如
USR(用户)、ORD(订单)、PAY(支付),全大写、无连字符、不带版本或环境信息 - 错误码格式统一为
USR-001、ORD-002,后缀从001开始,三位对齐,方便grep和日志排序 - 前缀必须在公司级文档注册,禁止重复;不能用服务名全称(如
user-service-001),HTTP Header 或 JSON key 解析会失败 - gRPC 响应体里必须显式带
error_code字段(string 类型),网关层只透传,不映射、不翻译
错误必须结构化封装,不能用 fmt.Errorf 或 errors.New
裸字符串错误无法提取码值、无法携带 traceID、下游只能靠 strings.Contains(err.Error(), "not found") 这种脆弱方式判断——一改文案就崩,多语言支持也直接失效。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 定义具名类型,如
type ErrorCode string或type BizError struct { code ErrorCode; message string; cause error } - 所有错误必须通过工厂函数创建,例如
user.NewErrUserInactive(),而非fmt.Errorf("USR-002: %w", err) - 必须实现
Error()(返回用户提示)、Code()(返回ErrorCode)、Unwrap()(返回底层 error) - 调用方用
errors.As(err, &BizError{})安全提取,不用errors.Is(err, someErr)—— 后者只适合系统级错误(如os.IsNotExist)
错误码要集中定义在公共 module,禁止分散硬编码
不同服务各自定义 const ErrNotFound = 1001,很快就会撞号:用户服务的 1001 是“用户不存在”,订单服务的 1001 却是“库存不足”——日志里看到 1001,谁都没法确定语义。
- 错误码定义必须放在独立的公共 module(如
common/errors),所有服务 go get 引入同一份 - 推荐分层编号:服务号(3位)+ 类型(2位)+ 序号(3位),如
101-02-001表示用户服务的「参数校验失败」 - 每个错误码必须绑定固定文案,且文案不含变量(禁止
"用户%s不存在"),防止 XSS 或日志脱敏失败 - 已发布的错误码含义绝不可变更;写错了就加新码,否则客户端逻辑会静默失效
HTTP 和 gRPC 的错误响应不能混用协议层状态码
把 gRPC 的 codes.NotFound 硬映射成 HTTP 404,会抹掉业务语义:用户不存在和订单已删除都变成 “404 Not Found”,前端没法差异化提示,监控也无法按业务维度聚合。
- gRPC 必须用
status.Error()包装,同时在响应 body 中显式带error_code字段(如"USR-003") - HTTP 响应体用标准 JSON 格式,包含
code、message、trace_id,HTTP 状态码仅反映协议层问题(如 400/401/404/500) - 中间件需区分两类错误:实现了
StatusCoder接口的业务错误(可取StatusCode()),和未封装的底层 error(如json.SyntaxError) - 禁止在中间件里根据
codes.XXX自动改写响应体——那会让错误语义失真,调用链断层
BizError,而不是一路 return err 直到 handler 才补码。一旦漏一层,整条链的可观测性就塌一角。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










