跳转到主要内容
模块 10 · 第 2 课(共 5)课程中的47/50~14 min
模块课程(2/5)

惯用的错误处理

Go 中的错误处理是明确的:没有异常,没有 try/catch。每个可能失败的函数都会返回 error 作为其最后一个值,由调用者决定。本章收集了以稳健的方式产生、传播和检查错误的惯用模式。

黄金法则

Go
data, err := os.ReadFile(path)
if err != nil {
    return fmt.Errorf("config %s: %w", path, err)
}

四个原则:

  1. error 是最后一个返回。
  2. 立即使用 if err != nil 检查。
  3. 在传播之前添加上下文(使用 fmt.Errorf + %w)。
  4. 永远不要忽略错误:要么处理它,要么记录它,要么返回它。
Go
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)

哨兵错误

哨兵错误是一个导出的全局变量 var ErrXxx = errors.New("...") ,调用者可以与它进行比较:

Go
package store

var ErrNotFound = errors.New("not found")

func Get(key string) (string, error) {
    if !exists(key) {
        return "", ErrNotFound
    }
    // ...
}

调用者使用 errors.Is (不是 ==),它遍历包装链:

Go
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
    // handle 404
}

标准示例:io.EOF、sql.ErrNoRows、os.ErrNotExist。

自定义类型错误

要携带结构化数据(HTTP代码、无效字段等),定义一个类型:

Go
type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}

调用者使用 errors.As 沿链安全地向下转换:

Go
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("invalid field:", ve.Field)
}

errors.As 遍历 Unwrap() 链,直到找到可分配给传入的指针的错误。

用 %w 包装

fmt.Errorf 与 %w 动词会产生一个错误,包装原始错误,并将其保留给 errors.Is/errors.As:

Go
if err := db.Query(); err != nil {
    return fmt.Errorf("load users: %w", err)
}

如果没有 %w (使用 %v 或 %s),您将获得更丰富的字符串,但 丢失 链:调用者无法再识别特定错误。

%w 的规则:

  • 每个 fmt.Errorf 调用仅一次(多重换行需要 errors.Join)。
  • 参数必须是 error,而不是字符串。
  • 添加上下文,不要重复原始消息。
Go
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"

// good: useful context
return fmt.Errorf("load config %s: %w", path, err)

errors.Join 对于多个错误

从 Go 1.20 开始, errors.Join(errs...) 将多个错误合并为一个,使所有错误都可以被 errors.Is/As 检测到:

Go
var errs []error
for _, item := range items {
    if err := process(item); err != nil {
        errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
    }
}
if len(errs) > 0 {
    return errors.Join(errs...)
}

何时使用 panic

永远不会出现预期的错误。 仅在三种情况下:

  1. 不可恢复的错误:在“不可能发生”的地方违反了不变量(panic("unreachable"))。
  2. 独立程序的 init()/main() 中初始化失败(缺少关键配置 → 快速失败)。
  3. 明确记录它的库 API:例如 regexp.MustCompile (如果模式无效则出现恐慌 - 可以接受,因为该模式是构建时常量)。

其他一切都是 error。 不要使用 panic 作为向上传播的快捷方式:它会破坏组合,跳过 defer Close(),并淹没调用者。

练习

锻炼#go.m10.l2.e1
尝试:0加载中...

os.ReadFile 中出现错误。使用错误信息%w e aggiungendo il contro 'config:' (con il path)。

正在加载编辑器...

3 次尝试后可用的解决方案

锻炼#go.m10.l2.e2
尝试:0加载中...

美国错误。是否已验证错误,并包含相应的错误信息,并印有“manca!”标记。在塔尔卡索。

正在加载编辑器...

3 次尝试后可用的解决方案

Quiz#go.m10.l2.e3
准备好

Quando è idiomatico 在 una libreria Go 中使用恐慌?

Go
func F(x int) {
  // ??? panic(...)
}
答案选项