错误码必须用具名类型封装,如type errorcode int并定义const errusernotfound errorcode = 1001,避免int裸用导致语义混淆、跨模块冲突及类型不兼容;各模块划错误码范围并预留空档,错误实例须结构化为bizerror,中间件用errors.as提取而非字符串匹配。

错误码必须用具名类型封装,不能裸用 int
直接写 const ErrUserNotFound = 404 看似省事,但立刻踩进三个坑:编译器不报错却无法和 http.StatusNotFound 区分语义;不同模块都定义 ErrNotFound = 1001,日志里根本看不出是用户还是订单出问题;更致命的是 if err == ErrUserNotFound 直接编译失败——error 和 int 类型不兼容,最后只能退化成脆弱的字符串匹配。
正确做法是定义具名类型:type ErrorCode int,再声明常量:const ErrUserNotFound ErrorCode = 1001。这样既保留数字可比性(err.Code() == ErrUserNotFound),又通过类型隔离避免误用。IDE 能跳转到定义,编译器能拦住跨模块隐式赋值。
每个业务模块要划清错误码范围,预留空档
错误码不是编号游戏,而是为了新增时不用翻几十个文件找空号。按服务/模块切片,每段留足余量:
-
user模块:1000–1499(ErrUserNotFound = 1001,ErrUserExists = 1002) -
order模块:2000–2499(ErrOrderNotFound = 2001,ErrInsufficientStock = 2002) -
payment模块:3000–3499(ErrBalanceInsufficient = 3001)
注意:1500–1999、2500–2999 这类空档必须留着,方便未来拆分子模块;已发布的错误码含义绝不可变更,哪怕写错了也得加新码——客户端逻辑依赖这个契约,静默修改等于线上事故。
错误实例必须结构化,禁止裸 error 返回
业务函数返回 error 时,不能只靠 fmt.Errorf("user not found")。这种字符串错误无法携带 Code、HTTPStatus、上下文字段,下游 handler 只能靠字符串匹配,一改文案就崩。
推荐结构体实现:
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
type BizError struct {
code ErrorCode
message string
cause error
}
func (e *BizError) Error() string { return e.message }
func (e *BizError) Code() ErrorCode { return e.code }
func (e *BizError) Unwrap() error { return e.cause }
使用时统一走工厂函数:NewUserError(ErrUserNotFound, "用户不存在"),内部查表映射 HTTP 状态码、注入 traceID,并校验 code 是否在合法区间——非法值直接 panic,不给静默失效留缝隙。
中间件里必须用 errors.As 提取,别碰 err.Error()
HTTP handler 或 gRPC server 中,千万别写 strings.Contains(err.Error(), "not found")。message 一翻译、一加前缀、甚至多一个空格,整个判断就失效。
可靠方式是类型提取:
- 判断是否为业务错误:
var bizErr *BizError; if errors.As(err, &bizErr) { ... } - 取错误码:
bizErr.Code() - 取 HTTP 状态码:
httpStatusMap[bizErr.Code()](查表映射,不硬编码)
对未封装的底层错误(如 json.SyntaxError、io.EOF),中间件应统一转成 500 并记录原始堆栈,绝不暴露细节给前端。
最易被忽略的一点:错误码查表映射必须是单向、只读的配置,不能让每个 BizError 实例自己存 httpStatus 字段——否则状态码改一处,就得同步修几十个构造调用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










