Go 错误处理
Go 没有异常机制,错误就是普通的值:函数出错时返回 error,由调用方检查处理。error 是内置接口,只有一个方法 Error() string。(说明:文中的片段代码需放进完整程序里运行)
error 与 errors.New
完整示例:
package main
import (
"errors"
"fmt"
)
func checkAge(age int) error {
if age < 0 {
return errors.New("年龄不能为负数")
}
return nil // 没有错误就返回 nil
}
func main() {
if err := checkAge(-1); err != nil {
fmt.Println("出错了:", err)
} else {
fmt.Println("一切正常")
}
}
// 输出: 出错了: 年龄不能为负数
约定:错误变量叫 err,nil 表示没有错误,if err != nil 是 Go 最常见的代码节奏。
fmt.Errorf 与 %w 包装
fmt.Errorf 可以拼接上下文信息;%w 能包装已有错误,形成可追溯的错误链,供 errors.Is/As 使用:
err := errors.New("文件名为空")
fmt.Errorf("读取失败:%w", err) // 输出: 读取失败:文件名为空
注意 %w 只能用于 fmt.Errorf;用 %v 只是拼文本,会丢失错误链。
errors.Is 与 errors.As
错误被多层包装后,用 errors.Is 判断链上是否存在某个目标错误;errors.As 把链上某类错误取出赋值:
var ErrNotFound = errors.New("记录不存在") // 哨兵错误
type MyError struct{ Code int }
func (e *MyError) Error() string { return fmt.Sprintf("错误码 %d", e.Code) }
func find(id int) error {
if id <= 0 {
return fmt.Errorf("find: %w", ErrNotFound) // 包装
}
return &MyError{Code: 404}
}
// errors.Is(find(0), ErrNotFound) → true
// var me *MyError; errors.As(find(1), &me) → me.Code == 404
判断错误应优先用 errors.Is/As 而不是比较字符串——字符串容易拼错,且经 %w 包装后会失效。
自定义错误类型
只要实现 Error() string 就满足 error 接口,还能携带额外字段:
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("错误码 %d:%s", e.Code, e.Msg)
}
// err := &MyError{Code: 1001, Msg: "除数不能为 0"}
// fmt.Println(err) → 错误码 1001:除数不能为 0
若还想取回字段细节,配合类型断言 e, ok := err.(*MyError) 即可。
panic 与 recover(一句话)
Go 也有 panic(抛出)和 recover(在 defer 中捕获),但只适合程序无法继续的致命情况;可预期的错误请用 error 返回处理。
小结:习惯 if err != nil;包装用 %w,判断用 errors.Is/As,自定义错误只需实现 Error()。