go中error是值而非异常,业务错误应返回error而非panic;panic仅用于致命错误且需recover;错误包装用%w保留原始类型;自定义错误须实现error()方法;http层需映射错误为状态码并脱敏敏感信息。

Go 里 error 不是异常,别用 panic 拦错
Go 的错误处理逻辑和 Python/Java 完全不同:error 是值,不是控制流。用 panic 处理业务错误(比如文件不存在、参数校验失败)会导致调用栈炸开、无法被上层恢复,还容易掩盖真正该 panic 的场景(如空指针解引用)。
常见错误现象:panic: open config.yaml: no such file or directory 被直接抛到 main 函数外,日志里只有一堆 goroutine stack trace,根本看不出是哪个模块在读配置。
- 业务错误一律返回
error,由调用方决定重试、降级或透传 -
panic只用于程序无法继续运行的致命状态(如初始化失败、全局资源泄漏),且必须配recover(仅限顶层 goroutine) - 不要为了“简洁”把
if err != nil { return err }写成if err != nil { panic(err) }
包装错误要用 fmt.Errorf + %w,不是字符串拼接
错误链(error wrapping)是 Go 1.13 引入的关键能力,目的是保留原始错误类型和上下文。如果用 + " failed" 或 fmt.Sprintf 拼接,原始错误就丢了——你再也查不到底层是不是 os.PathError,也无法用 errors.Is 或 errors.As 判断。
使用场景:HTTP handler 中调用数据库,DB 层返回 sql.ErrNoRows,你想加一层 “user not found”,又得保留可判定性。
- 正确写法:
return fmt.Errorf("get user %d: %w", id, err) - 错误写法:
return fmt.Errorf("get user %d: %s", id, err.Error()) - 检查包装后的错误是否为某类:
errors.Is(err, sql.ErrNoRows)—— 只有用了%w才有效 - 提取底层错误实例:
var perr *os.PathError; if errors.As(err, &perr) { ... }
自定义错误类型要实现 Error() 方法,别只靠字段
当需要携带结构化信息(如错误码、trace ID、重试建议)时,光靠包装不够。但很多人只加字段不实现 Error(),结果 fmt.Println(err) 打印出来是 {Code:404 Message:"not found"} 这种难读格式,而且 errors.Is 无法匹配。
性能影响:自定义错误对象本身开销极小;但若在高频路径(如每请求都 new 一个)中带大量字段或大结构体,会增加 GC 压力。
- 必须实现
func (e *MyError) Error() string,返回人类可读文本 - 如果需支持判定,额外实现
Unwrap() error返回底层错误(用于%w链式包装) - 避免在
Error()方法里做耗时操作(如 JSON 序列化、网络请求) - 示例:
type ValidationError struct { Code int; Field string }; func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed on %s: code %d", e.Field, e.Code) }
HTTP handler 里别裸露底层错误,要映射成状态码和响应体
把 os.IsPermission(err) 或 redis.Nil 直接塞进 HTTP 响应体,前端拿到的是 “permission denied” 或 “nil”,既不安全也不友好。更糟的是,有些错误(如 DB 连接超时)本该返回 503,却因没判断被当成 400 返回。
兼容性注意:不同中间件对错误的处理方式不同。Gin 默认把 panic 转 500,Echo 需手动注册 HTTPErrorHandler;标准库 net/http 则完全不管错误,全靠你自己写 if err != nil 分支。
- 建一个错误映射表:
map[error]int{sql.ErrNoRows: 404, io.ErrUnexpectedEOF: 400, ...} - 用
errors.Is匹配包装链里的任意一层,再查表定状态码 - 敏感错误(如 DB 密码错误、文件路径泄露)必须抹掉细节,统一返回泛化消息
- 别在 middleware 里吞掉所有
error并返回 500——这会让前端无法区分是客户端错还是服务端错
最常被忽略的一点:错误变量命名。别叫 err 就完事,尤其在多 err 场景下(比如先查缓存再查 DB),cacheErr 和 dbErr 能立刻帮你避开「到底哪个 err 没判」的坑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











