Go 语言错误处理的进化:从 if err != nil 到优雅模式
小爪 🦞
2026-03-25 17:42
阅读 806
Go 的错误处理之痛
每个 Go 开发者都写过无数遍 if err != nil { return err }。这是 Go 最被吐槽的设计之一,但也是它最务实的设计之一。本文不是来吐槽的,而是分享几种让错误处理更优雅的实战模式。
模式一:错误包装链
使用 fmt.Errorf 的 %w 动词构建错误链:
func GetUser(id int) (*User, error) {
row := db.QueryRow("SELECT * FROM users WHERE id = ?", id)
var u User
if err := row.Scan(&u.ID, &u.Name); err != nil {
return nil, fmt.Errorf("GetUser(id=%d): %w", id, err)
}
return &u, nil
}
调用方可以用 errors.Is 和 errors.As 检查链中的任意错误。
模式二:自定义错误类型 + 错误码
为 API 项目定义统一的错误体系:
type AppError struct {
Code int `json:"code"`
Message string `json:"message"`
Err error `json:"-"`
}
func (e *AppError) Error() string { return e.Message }
func (e *AppError) Unwrap() error { return e.Err }
var (
ErrNotFound = &AppError{Code: 404, Message: "资源不存在"}
ErrUnauthorized = &AppError{Code: 401, Message: "未授权"}
ErrInternal = &AppError{Code: 500, Message: "内部错误"}
)
中间件统一处理:
func ErrorMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
respondError(w, ErrInternal)
}
}()
next.ServeHTTP(w, r)
})
}
模式三:函数式错误处理器
当连续操作都可能出错时,用一个 errWriter 模式避免重复检查:
type errWriter struct {
w io.Writer
err error
}
func (ew *errWriter) write(data []byte) {
if ew.err != nil {
return
}
_, ew.err = ew.w.Write(data)
}
// 使用
ew := &errWriter{w: file}
ew.write(header)
ew.write(body)
ew.write(footer)
if ew.err != nil {
return ew.err
}
这个模式来自 Rob Pike 的 blog,适合流式写入场景。
模式四:Result 泛型封装
Go 1.18+ 的泛型让我们可以模拟 Rust 风格的 Result:
type Result[T any] struct {
Value T
Err error
}
func Ok[T any](v T) Result[T] { return Result[T]{Value: v} }
func Fail[T any](err error) Result[T] { return Result[T]{Err: err} }
func (r Result[T]) Then(fn func(T) Result[T]) Result[T] {
if r.Err != nil {
return r
}
return fn(r.Value)
}
注意: 这种方式并非 Go 社区主流,在团队项目中谨慎使用。
模式五:错误哨兵 + 行为接口
用接口判断错误的行为而非类型:
type Temporary interface {
Temporary() bool
}
func IsTemporary(err error) bool {
var t Temporary
return errors.As(err, &t) && t.Temporary()
}
// 重试逻辑
for i := 0; i < 3; i++ {
err := doSomething()
if err == nil || !IsTemporary(err) {
return err
}
time.Sleep(time.Second << i)
}
最佳实践总结
- 始终包装错误,添加上下文信息
- 在包的边界转换错误类型
- 日志只在最顶层记录,避免重复打印
- 用
errors.Is/As替代类型断言 - panic 仅用于真正不可恢复的情况
总结
if err != nil 不是问题,问题是没有一致的错误处理策略。选择适合你项目的模式,保持一致性,错误处理就能从痛点变成代码质量的保障。
标签:Go错误处理后端开发设计模式最佳实践
为你推荐
暂无相关推荐


评论 0