Go 错误处理进阶:sentinel errors、%w 包装与 errors.Is/As 精准判定

test12026-10-084 次阅读

Go 1.13 之后的错误哲学

Go 不靠异常机制,而是把错误当普通值返回。问题在于早年的错误只能比较字符串,一旦底层加了包装就再也比不中。Go 1.13 引入 %w 与 errors.Is/As 之后,错误链才真正可用。

1 定义 sentinel error

对调用方需要精确判定的错误,导出包级变量,让上层用 errors.Is 比对。

package repo

import "errors"

var ErrNotFound = errors.New("record not found")
var ErrConflict = errors.New("resource conflict")

2 用 %w 包装并保留链

在业务层把底层错误包装进去,既补充上下文,又不丢失原始错误,便于上层逐层拆解。

func (r *UserRepo) Find(id int) (*User, error) {
    u, err := r.db.Get(id)
    if err != nil {
        return nil, fmt.Errorf("UserRepo.Find id=%d: %w", id, err)
    }
    return u, nil
}

3 errors.Is 判定

不管错误被包装多少层,errors.Is 都会沿着链找到最底层的 sentinel,调用方再也不用依赖字符串匹配。

u, err := repo.Find(42)
if errors.Is(err, ErrNotFound) {
    return render404()
}

4 errors.As 抽取自定义错误

当底层是带字段的自定义错误类型时,用 errors.As 把它取出来读取上下文,而不是盲目打印。

type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string { return e.Field + ": " + e.Msg }

var ve *ValidationError
if errors.As(err, &ve) {
    log.Printf("字段 %s 校验失败: %s", ve.Field, ve.Msg)
}

5 在 HTTP 层统一收敛

用一个中间件把错误翻译成状态码,避免每个 handler 都重复写错误分支。

func writeError(w http.ResponseWriter, err error) {
    switch {
    case errors.Is(err, ErrNotFound):
        http.Error(w, "not found", http.StatusNotFound)
    case errors.As(err, &ValidationError{}):
        http.Error(w, err.Error(), http.StatusBadRequest)
    default:
        http.Error(w, "internal error", http.StatusInternalServerError)
    }
}
T

test1

文章作者

Go 1.13 之后的错误哲学 Go 不靠异常机制,而是把错误当普通值返回。问题在于早年的错误只能比较字符串,一旦底层...

分类
技术
发布时间
2026-10-08
字数
约 1440 字
阅读
4 次