不该。go 错误码不应使用 int 类型定义,因其丢失语义、引发隐式转换、难以维护;应采用自定义错误类型(如结构体实现 error 接口)并配合 errors.is/as 进行类型化判断,以符合 go “用类型表达意图”的错误哲学。

Go 错误码该不该用 int 类型定义
不该。用 int 定义错误码会丢失语义、引发隐式转换、难以维护。Go 原生推荐用自定义错误类型(error 接口实现)或带字段的结构体,而不是裸数字。
常见错误现象:if err == 404 编译不通过(error 和 int 类型不兼容),或者退而求其次写成 err.Error() == "404" —— 字符串匹配脆弱、无类型安全、无法导出含义。
- 正确做法:定义带状态码和消息的结构体,比如
type AppError struct { Code int; Message string },并实现Error()方法 - 更推荐:用
fmt.Errorf+ 自定义包装(如errors.Join或fmt.Errorf("failed: %w", err)),配合errors.Is/errors.As判断底层错误类型 - 如果必须暴露 HTTP 状态码,建议通过方法返回:
func (e *AppError) StatusCode() int { return e.Code },而不是让调用方硬拆err
如何区分业务错误码和系统错误(比如 io.EOF、os.IsNotExist)
靠类型判断,不是靠字符串或数字比对。Go 的错误处理哲学是「用类型表达意图」,不是「用数字编码分类」。
使用场景:API 返回需要映射不同 HTTP 状态码(400 对应参数校验失败,404 对应资源不存在,500 对应数据库连接失败),但底层错误来源混杂(标准库、第三方包、自己写的逻辑)。
- 定义业务错误类型,比如
type ValidationError struct{ Field string; Msg string },并实现Unwrap() error和Error() - 在 handler 中用
errors.As(err, &ValidationError{})提取业务错误,再转成对应 HTTP 状态;对os.IsNotExist(err)这类系统判断保留原方式 - 避免把所有错误都塞进一个大 switch:比如
switch code := GetCode(err); code { case 400: ... }—— 这会让错误传播链断裂,丢失原始上下文
要不要把错误码集中定义在一个 const 文件里
要,但仅限于「明确由当前服务统一管控的业务错误码」,且必须配说明和使用约束。不能把 HTTP 状态码、gRPC 状态码、数据库驱动错误码全混进去。
容易踩的坑:定义一堆全局 const ErrUserNotFound = 1001,结果不同模块各自实现 1001 含义不一致;或者把 http.StatusNotFound 也放进同一组常量,导致语义污染。
- 只定义本服务独有、需跨层透传的业务错误标识,例如
ErrInsufficientBalance,类型为自定义错误,不是int - 每个错误码常量旁加注释,写清触发条件、是否可重试、建议客户端行为(如“前端应提示用户充值”,而非“返回 1003”)
- 禁止用数字字面量直接构造错误:
return errors.New("1003")或fmt.Errorf("%d", ErrInvalidToken)—— 这等于放弃类型系统
HTTP handler 中怎么把错误转成 JSON 响应(含错误码字段)
关键不是“转码”,而是“保留错误的可识别性”。不要在中间件里统一 json.Marshal(map[string]interface{}{"code": 500, "msg": err.Error()}) —— 这会抹掉所有结构化信息。
性能影响:每次反射解析错误类型开销极小,远小于序列化本身;但若错误嵌套过深(比如 10 层 %w),errors.As 查找可能变慢,此时应限制包装深度。
- 定义统一响应结构:
type Resp struct { Code int `json:"code"`; Message string `json:"message"`; Data interface{} `json:"data,omitempty"` } - handler 内用类型断言分发:
if errors.As(err, &ValidationError{}) { resp.Code = 400 },而不是从err.Error()里 parse 数字 - 对未预期错误(panic、空指针等),统一 fallback 到
500,但日志中必须打印完整错误栈,不能只记"internal error"
最常被忽略的一点:错误码的「可测试性」。如果你的 handler 测试只能检查返回的 JSON 字段,却没法验证它是否真的来自 ValidationError 类型,那这个错误码设计就已经在退化了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











