Go错误处理速查表
一、三种错误形态与判断方式

| # |
形态 |
怎么产生 |
适用场景 |
怎么判断 |
| 1 |
哨兵错误(sentinel) |
var ErrNotFound = errors.New("not found") |
调用方只需知道“是不是这个错”,不需要额外信息 |
errors.Is(err, ErrNotFound)(推荐)
err == ErrNotFound(仅当确定未被包裹) |
| 2 |
包裹错误(wrap,加上下文) |
fmt.Errorf("load user %d: %w", id, err) |
往上传递时补充“在哪、在做什么” |
errors.Is(err, ErrNotFound) 沿链查找值 |
| 3 |
自定义错误类型(struct) |
type QueryError struct{...} 实现 Error() string |
调用方需要读取错误里的字段(状态码、字段名、重试时间等) |
e, ok := errors.AsType[*QueryError](err)(Go 1.26+)
var e *QueryError; errors.As(err, &e)(老版本) |
比较错误值用 Is,提取错误类型用 As/AsType。
二、代码模板
1. 哨兵错误
1 2 3 4 5 6 7 8
| var ErrNotFound = errors.New("not found")
func Find(id int) (*User, error) { return nil, ErrNotFound }
if errors.Is(err, ErrNotFound) { ... } if err == ErrNotFound { ... }
|
2. 包裹错误
1 2 3 4 5 6
| if err != nil { return fmt.Errorf("find user %d: %w", id, err) }
if errors.Is(err, ErrNotFound) { ... }
|
3. 自定义错误类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| type QueryError struct { Query string Err error } func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() } func (e *QueryError) Unwrap() error { return e.Err }
return &QueryError{Query: q, Err: ErrNotFound}
if qe, ok := errors.AsType[*QueryError](err); ok { log.Println(qe.Query) }
var qe *QueryError if errors.As(err, &qe) { log.Println(qe.Query) }
|
三、%w 还是 %v
| 用法 |
效果 |
什么时候用 |
%w |
保留错误链,调用方可 Is/As |
希望调用方能识别底层错误 |
%v |
只拼字符串,断开链 |
不想把底层错误暴露成你的 API(如隐藏所用的数据库驱动) |
%w 不是必须的。用了它,底层错误就成了对外契约的一部分,所以只在希望调用方判断底层错误时使用。
四、其他要点
- 不要比较错误字符串:不要写
strings.Contains(err.Error(), "...")。
- 多个错误:
errors.Join(err1, err2),或 fmt.Errorf("...: %w, %w", e1, e2)(Go 1.20+),Is/As 会逐个检查。
- 只处理一次:要么 log,要么 return。既打日志又往上抛,同一个错误会被记录多次。
- 上下文措辞:小写开头、不加标点、不写 “failed to”,例:
"open config: %w"。
- 自定义类型用指针接收者,
AsType/As 时也用指针类型 *QueryError,二者要一致。
- 不要返回有类型的 nil:
var e *QueryError = nil; return e 得到的 error 不等于 nil。
- 自定义匹配:类型可实现
Is(target error) bool 改变 errors.Is 的判定逻辑(少用)。
- 哨兵错误命名:
ErrXxx;错误类型命名:XxxError。
五、决策流程
