Skip to content

错误处理

字数
704 字
阅读时间
4 分钟

Go 中的错误是一个简单的接口类型:

go
type error interface {
    Error() string
}

编写任何库的时候,就可以通过实现这个接口,提供错误的上下文信息。例如 os.Open 返回的错误如下:

go
// PathError records an error and the operation and
// file path that caused it.
type PathError struct {
    Op string    // "open", "unlink", etc.
    Path string  // The associated file.
    Err error    // Returned by the system call.
}

func (e *PathError) Error() string {
    return e.Op + " " + e.Path + ": " + e.Err.Error()
}

这个错误字符串类似这样:“open /etc/passwx: no such file or directory”

调用者如果关心错误的具体类型,可通过类型断言来获取:

go
if err, ok := err.(*os.PathError); ok && err.Err == syscall.ENOSPC {
    deleteTempFiles()
}

错误是值(Errors are values)

设计接口时,也可以不直接返回错误,而是通过一个方法去读取错误:

go
scanner := bufio.NewScanner(input)
for scanner.Scan() {
    token := scanner.Text()
    // process token
}
if err := scanner.Err(); err != nil {
    // process the error
}

包装错误(Wrapping errors)

fmt.Errorf("more context info: %w", err) 来包装错误到一个错误链,而不是直接拼接字符串或errors.New(fmt.Sprintf("%v", err)) 。这样可以用 errors.Unwrap 来拆解错误链。

go
var ErrOutOfTea = fmt.Errorf("no more tea available")
var ErrPower = fmt.Errorf("can't boil water")

func makeTea(arg int) error {
    if arg == 2 {
        return ErrOutOfTea
    }
    if arg == 4 {
        return fmt.Errorf("making tea: %w", ErrPower)
    }
    return nil
}

func main() {
    for i := range 5 {
        if err := makeTea(i); err != nil {
            if errors.Is(err, ErrOutOfTea) {
                fmt.Println("We should buy new tea!")
            } else if errors.Is(err, ErrPower) {
                fmt.Println("Now it is dark.")
            } else {
                fmt.Printf("unknown error: %s\n", err)
            }
            continue
        }
        fmt.Println("Tea is ready!")
    }
}

返回错误(Returning errors)

  • 避免具体错误类型:导出函数返回 error 而非具体类型(如 *os.PathError),防止接口包装导致的 nil 错误。

带内错误

带内错误指通过函数的正常返回值(如 -1""null)来传递错误或缺失结果的方式,常见于 C 等语言

  • Go 中不鼓励使用带内错误,应使用多返回值返回额外的错误。

错误信息(Error strings)

  • 格式:不要首字母大写,也不要结尾标点(如 fmt.Errorf("something bad happened"))。
  • 日志场景:完整消息需首字母大写(如 log.Errorf("Operation failed: %v", err))。

错误处理逻辑

  • 不忽略错误:尽量不要用 _ 丢弃错误,必要时注释说明(如 n, _ := b.Write(p) // 文档声明永不失败)。
  • 提前处理:错误处理代码前置,避免深层嵌套(使用 if err != nil { return } 而非 else 块)。

不要 Panic

  • 避免使用 panic 处理常规错误,使用 error 和多返回值。

打印错误

log.V2.Error().with(ctx).Error(err).Emit() 使用专用的方法打印

贡献者

页面历史


总访问量 次, 访客数 人次