Go 错误处理基础与进阶
Go 把错误当值而非异常:error 接口、显式检查、errors.Is/As 判定、哨兵错误与自定义错误类型、错误包装 %w、panic 的边界。本文从哲学到实战讲清 Go 错误处理。
本文依据官方文档整理,未执行运行验证或性能基准。代码片段展示局部用法,业务函数、数据和环境需按项目补齐;版本与配置以所引文档为准。
直接回答:Go 的错误是值——内置 error 接口只需一个 Error() string 方法,函数显式返回、调用方显式检查。进阶三件套:fmt.Errorf("...: %w", err) 包装保留链、errors.Is 判哨兵、errors.As 取类型。panic 只留给程序员错误与不可恢复场景。
为什么不用异常
try/catch 的控制流是隐形的——读代码看不出哪里会"跳走"。Go 的设计哲学:失败是正常路径的一部分,显式 if err != nil 强迫开发者直面每个失败点。冗长被吐槽多年,但生产系统的可靠性因此受益。
基础模式
type error interface { Error() string }
任何实现该方法的类型都是 error。返回错误的函数签名约定:错误是最后一个返回值。
创建与定制
var ErrNotFound = errors.New("record not found") // 哨兵错误
type ValidationError struct{ Field, Msg string }
func (e ValidationError) Error() string { return e.Field + ": " + e.Msg }
哨兵错误用于可预料的业务失败;自定义类型携带结构化上下文。
包装与判定
if err != nil {
return fmt.Errorf("load config: %w", err) // %w 保留错误链
}
// 调用方
if errors.Is(err, ErrNotFound) { /* 精确匹配链上任意层 */ }
var ve ValidationError
if errors.As(err, &ve) { /* 取出链上的特定类型 */ }
关键纪律:判定用 Is/As 而非字符串比较或类型断言——包装后的链上两者都能穿透。
panic 的边界
panic 只给两类场景:程序员错误(数组越界这类不该发生的)、启动期不可恢复错误(配置缺失)。库应把可预期失败作为 error 返回;明确约定的 Must API 或程序不变量失败另当别论。recover 必须在同一 goroutine 的 deferred 函数中直接调用,无法捕获另一个 goroutine 的 panic,也不能挽救所有运行时致命错误。HTTP handler 的兜底不自动覆盖它创建的后台 goroutine。
常见问题(FAQ)
Q:if err != nil 太啰嗦怎么办?
A:接受它——显式是特性不是缺陷。重复性检查可封装(如 must 助手仅限初始化代码),但业务路径别藏错误。
Q:错误消息该写多详细?
A:每一层包装加"我在做什么"的上下文,别重复下层信息。链式读下来应能重建完整失败路径:"load config: parse yaml: line 3: unexpected character"。
Q:errors.Join 什么时候用?
A:Go 1.20+ 的 errors.Join 合并已收集的错误,本身不启动或同步并发任务;Is/As 可遍历错误树,As 返回第一个匹配。注意含 typed nil 指针的 error 接口本身可能非 nil。
官方参考
资料核对日期:2026-09-29。