主题
Go 错误处理
Go 不使用异常(exception),而是通过返回 error 值来显式处理错误。这是 Go 错误处理哲学的核心:错误是值,应被检查和处理。
error 接口
error 是一个内建接口:
go
type error interface {
Error() string
}基本错误处理模式
go
f, err := os.Open("file.txt")
if err != nil {
return err // 向上传播
}
defer f.Close()
// 配合 defer 确保资源释放错误包装(Go 1.13+)
使用 %w 包装底层错误,保留原始错误链以便追溯:
go
if err != nil {
return fmt.Errorf("读取配置失败: %w", err)
}
// 不保留链(仅格式化字符串)用 %v
return fmt.Errorf("读取配置失败: %v", err)errors.Is / errors.As
go
// errors.Is:判断错误链中是否包含某个目标错误(哨兵错误)
if errors.Is(err, os.ErrNotExist) {
fmt.Println("文件不存在")
}
// errors.As:将错误链中的特定类型提取出来
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("路径错误:", pathErr.Path)
}自定义错误
实现 error 接口即可定义自己的错误类型:
go
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("error %d: %s", e.Code, e.Message)
}
func doSomething() error {
return &MyError{Code: 400, Message: "参数错误"}
}
// 调用方用 errors.As 提取
if err := doSomething(); err != nil {
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Println("错误码:", myErr.Code)
}
}哨兵错误(Sentinel Errors)
预定义的包级错误变量,用于调用方做 errors.Is 比较:
go
var (
ErrNotFound = errors.New("not found")
ErrForbidden = errors.New("forbidden")
)约定:哨兵错误变量名以
Err开头。
panic 与 recover
panic 用于真正不可恢复的程序错误(类似其他语言的异常),recover 用于捕获 panic 防止程序崩溃(通常仅用于库/服务器顶层兜底)。
go
// 触发 panic
panic("something went terribly wrong")
// recover 必须在 defer 中调用才有效
func safeDivide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
return a / b, nil
}实践原则:
- 不要用 panic 做常规错误控制,常规错误一律返回
error。panic仅用于:程序无法继续运行的 invariants 被破坏(如初始化失败)。recover仅用于 goroutine / HTTP 中间件 / 服务器顶层做兜底,避免单点故障拖垮整个进程。- 不要跨 goroutine recover(每个 goroutine 需各自 defer recover)。
错误处理最佳实践
- 立即检查:
if err != nil紧跟在可能出错的调用之后。 - 包装并附加上下文:返回错误时用自己的语义包装(
%w),方便定位。 - 不要静默忽略:空
_ = err会丢失信息,至少记录日志。 - 错误透传而非转换丢失:保留原始错误链便于
errors.Is/As判断。 - 库代码返回 error,由调用方决定如何处理(重试、降级、返回用户友好信息等)。
go
// 推荐:包装 + 上下文
user, err := repo.FindUser(id)
if err != nil {
return fmt.Errorf("查询用户 %d 失败: %w", id, err)
}作者:yanshaodong