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 次
