业务错误码不推荐用int,应定义自定义类型ErrorCode并封装AppError结构体,通过构造函数统一创建、绑定上下文,HTTP状态码映射应在transport层独立处理,全局错误码需按模块命名、集中管理、CI校验。
Go 官方生态(如 net/http、os)普遍用 int 表示系统级错误码,但业务错误码**不推荐直接用 int 常量**。原因很实际:类型丢失导致易误用、无法携带上下文、IDE 无法跳转到定义、难以批量校验重复值。
int 定义:const ErrUserNotFound = 404 → 调用时可能被当成任意整数传入,编译器不报错type ErrorCode int const ( ErrUserNotFound ErrorCode = 1001 ErrInvalidToken ErrorCode = 1002 )
String() 方法还能让日志输出可读名:func (e ErrorCode) String() string { ... }
单纯定义常量没用,关键是要让每个错误码能生成带上下文的 error。别写 errors.New("user not found") 这种无结构的字符串错误。
type AppError struct {
Code ErrorCode
Message string
TraceID string
}
func (e *AppError) Error() string { return e.Message }
func (e *AppError) StatusCode() int { return int(e.Code) }
NewUserNotFoundError("user id: 123") 内部自动填 Code: ErrUserNotFound
&AppError{...} —— 构造逻辑收口后,后续加 trace、metrics、i18n 才可控业务错误码(如 1001)和 HTTP 状态码(如 404)是两层概念,硬性一一对应会出问题。比如 ErrRateLimited 应该返回 429,但 ErrInternalFailure 在调试环境可能返回 500,上线后却要返回 503。
const ErrUserNotFound = 404 是典型反模式func (e ErrorCode) HTTPStatus() int {
switch e {
case ErrUserNotFound:

return 404
case ErrRateLimited:
return 429
default:
return 500
}
}多人协作时,最怕两个模块都定义了 ErrInvalidParam = 1000。靠文档约定或人工 review 不可靠。
user.ErrInvalidEmail、order.ErrInsufficientStock,类型也分包定义errors/code.go 文件,禁止分散定义;用注释标注用途和责任人:// ErrInvalidEmail user module, used in email validation // @owner: auth-team const ErrInvalidEmail ErrorCode = 2001
go list -f '{{.ImportPath}}' ./... | xargs -I{} go tool vet -shadow {} 配合自定义脚本扫重复值ErrTimeout 在三个服务里含义不同、HTTP 状态码被前端硬编码、日志里只看到数字看不到上下文——那些地方,往往没在定义时就设计好边界。