错误处理
显式 error 返回是 Go 的默认风格;panic 只用于真正异常路径。
error 与惯用写法
error 是单方法接口:Error() string。函数常返回 (T, error),调用方立刻检查:
f, err := os.Open("a.txt")
if err != nil {
return fmt.Errorf("open: %w", err)
}
defer f.Close()
不要忽略错误(_ = do() 除非你真的不在乎)。成功时 err == nil。
自定义错误:
var ErrNotFound = errors.New("not found")
type QueryError struct {
Key string
Err error
}
func (e *QueryError) Error() string {
return fmt.Sprintf("query %s: %v", e.Key, e.Err)
}
包装与判断
用 %w 包装以保留错误链,再用 errors.Is / errors.As 判断:
func load(key string) error {
err := ErrNotFound
return fmt.Errorf("load %s: %w", key, err)
}
err := load("user")
if errors.Is(err, ErrNotFound) {
fmt.Println("missing")
}
var qe *QueryError
if errors.As(err, &qe) {
fmt.Println(qe.Key)
}
| API | 用途 |
|---|---|
errors.Is(err, target) | 链上是否匹配某 sentinel / 可比较错误 |
errors.As(err, &target) | 链上是否有某具体类型 |
fmt.Errorf("...: %w", err) | 包装 |
errors.Join(1.20+) | 合并多个错误 |
只想附加信息、不参与 Is 链时,可用 %v(一般优先 %w)。
panic / recover 的边界
panic 中止当前 goroutine 的正常控制流;recover 只能在 defer 中调用以接住 panic:
func safe() (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
panic("boom")
}
建议:
- 库代码:返回
error,不要 panic 表达可预期失败 - 进程边界(如 HTTP 中间件、worker 顶层):可 recover,记日志,避免整个进程挂掉
- 编程错误(如索引越界)本身会 panic——修复代码,而不是到处 recover
小结
(T, error)+if err != nil是主路径%w+errors.Is/As做错误链- sentinel(
var ErrX = errors.New)与自定义类型按需选用 - panic/recover 留给真正异常与边界防护