V
Vel·ToolKit
简洁 · 高效 · 即开即用
ZH
第 13 章 / 共 20 章

错误处理

error 接口、errors.Is/As、panic/recover

error 接口

Go 不用异常,函数把错误作为最后一个返回值显式返回。error 是只含一个 Error() string 方法的接口。

package main

import (
    "fmt"
    "os"
)

func readSize(path string) (int64, error) {
    f, err := os.Open(path)
    if err != nil {
        return 0, fmt.Errorf("open %s: %w", path, err) // 用 %w 包装
    }
    defer f.Close()

    info, err := f.Stat()
    if err != nil {
        return 0, err
    }
    return info.Size(), nil
}

func main() {
    n, err := readSize("/etc/hostname")
    fmt.Println(n, err)
}

errors.Is 与 errors.As

标准库 errors 包提供匹配工具,能穿透 %w 的多层包装。

package main

import (
    "errors"
    "fmt"
    "io/fs"
    "os"
)

func main() {
    _, err := os.Open("/no/such/file")

    if errors.Is(err, fs.ErrNotExist) {
        fmt.Println("file missing")
    }

    var pe *fs.PathError
    if errors.As(err, &pe) {
        fmt.Println("path:", pe.Path, "op:", pe.Op)
    }
}

自定义错误类型

package main

import "fmt"

type ValidationError struct {
    Field, Reason string
}

func (e *ValidationError) Error() string {
    return e.Field + ": " + e.Reason
}

func validate(name string) error {
    if name == "" {
        return &ValidationError{Field: "name", Reason: "empty"}
    }
    return nil
}

func main() {
    fmt.Println(validate(""))
}

panic / recover

panic 是真正的“异常”,会沿调用栈展开。除非确实无法继续,否则别用。recover 必须在 defer 内才能捕获 panic。

package main

import "fmt"

func risky() {
    panic("boom")
}

func safe() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic: %v", r)
        }
    }()
    risky()
    return
}

func main() {
    fmt.Println(safe())
}