Go 的错误理念:错误也是值

Go 没有 try/catch。函数出错时,把错误作为返回值交还给调用者,由调用者决定怎么处理——这就是 Go 错误处理的核心。

最基本的模式

package main

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

func main() {
    r, err := divide(10, 2)
    if err != nil {
        fmt.Println("出错了:", err)
        return
    }
    fmt.Println("结果:", r)     // 输出:结果: 5
}

if err != nil 是 Go 代码里出现频率最高的写法,几乎每个函数调用后都会看到它。

拼变量进错误信息:fmt.Errorf

需要把变量写进错误信息时用 fmt.Errorf,用法和 fmt.Printf 一样:

func checkAge(age int) error {
    if age < 0 {
        return fmt.Errorf("年龄不合法: %d", age)
    }
    return nil
}

checkAge(-5) 返回的错误信息是:年龄不合法: -5。

自定义错误类型

想把错误当成"数据"来比对,可以定义自己的错误类型:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "字段校验失败: " + e.Field
}

func validate(age int) error {
    if age < 0 {
        return &ValidationError{Field: "age"}
    }
    return nil
}

只要实现了 Error() string 方法,任何类型都是合法的 error。

errors.Is 与 errors.As

Go 1.13 之后,标准库提供了两个判断错误的利器:

var ErrNotFound = errors.New("记录不存在")

func find(id int) error {
    return ErrNotFound
}

func main() {
    err := find(1)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("没有找到这条记录")   // 输出:没有找到这条记录
    }
}
  • errors.Is:判断错误链里是否包含目标错误(推荐替代 == 直接比较)
  • errors.As:把错误转换成具体类型,再取出里面的字段

panic 与 recover:最后的手段

程序遇到无法继续运行的严重问题时才用 panic;recover 配合 defer 可以拦截 panic,避免程序崩溃:

func safeRun() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("已恢复:", r)    // 输出:已恢复: 炸了
        }
    }()
    panic("炸了")
}

func main() {
    safeRun()
    fmt.Println("程序没有崩溃")          // 输出:程序没有崩溃
}

小结

  • Go 用返回值传递错误,if err != nil 是标准处理模式
  • 临时错误用 errors.New / fmt.Errorf,需要携带数据就定义错误类型
  • errors.Is 判断相等,errors.As 提取具体类型
  • panic 只留给真正无法挽回的场景,正常业务错误一律返回 error