错误包装 #

一、错误包装概述 #

Go 1.13引入了错误包装机制,允许错误包含其他错误。

二、fmt.Errorf包装 #

2.1 %w动词 #

go
err := fmt.Errorf("operation failed: %w", originalErr)

2.2 示例 #

go
func readFile(path string) error {
    _, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("open file %s: %w", path, err)
    }
    return nil
}

三、errors.Is #

3.1 检查错误值 #

go
if errors.Is(err, os.ErrNotExist) {
    fmt.Println("File not found")
}

3.2 自定义错误 #

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

func find(id int) error {
    return fmt.Errorf("find %d: %w", id, ErrNotFound)
}

func main() {
    err := find(1)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("Not found")
    }
}

3.3 错误链 #

go
err1 := errors.New("error 1")
err2 := fmt.Errorf("error 2: %w", err1)
err3 := fmt.Errorf("error 3: %w", err2)

fmt.Println(errors.Is(err3, err1))  // true

四、errors.As #

4.1 类型检查 #

go
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    fmt.Println("Path:", pathErr.Path)
}

4.2 自定义类型 #

go
type ValidationError struct {
    Field string
}

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

func validate() error {
    return &ValidationError{Field: "name"}
}

func main() {
    err := validate()
    
    var ve *ValidationError
    if errors.As(err, &ve) {
        fmt.Println("Field:", ve.Field)
    }
}

4.3 错误链中的类型 #

go
err1 := &ValidationError{Field: "name"}
err2 := fmt.Errorf("validate: %w", err1)

var ve *ValidationError
if errors.As(err2, &ve) {
    fmt.Println("Field:", ve.Field)  // name
}

五、Unwrap #

5.1 errors.Unwrap #

go
err1 := errors.New("error 1")
err2 := fmt.Errorf("error 2: %w", err1)

unwrapped := errors.Unwrap(err2)
fmt.Println(unwrapped)  // error 1

5.2 自定义Unwrap #

go
type WrappedError struct {
    Msg string
    Err error
}

func (e *WrappedError) Error() string {
    return e.Msg
}

func (e *WrappedError) Unwrap() error {
    return e.Err
}

六、实际应用 #

6.1 分层错误处理 #

go
func service() error {
    if err := repository(); err != nil {
        return fmt.Errorf("service: %w", err)
    }
    return nil
}

func repository() error {
    if err := db(); err != nil {
        return fmt.Errorf("repository: %w", err)
    }
    return nil
}

func main() {
    err := service()
    
    var dbErr *DBError
    if errors.As(err, &dbErr) {
        // 处理数据库错误
    }
}

6.2 错误判断 #

go
func handleError(err error) {
    if errors.Is(err, sql.ErrNoRows) {
        fmt.Println("No data found")
    } else if errors.Is(err, context.Canceled) {
        fmt.Println("Operation canceled")
    } else {
        fmt.Println("Unknown error:", err)
    }
}

6.3 错误恢复 #

go
func retryable(err error) bool {
    var netErr net.Error
    if errors.As(err, &netErr) {
        return netErr.Timeout() || netErr.Temporary()
    }
    return false
}

七、最佳实践 #

7.1 使用%w包装 #

go
return fmt.Errorf("operation failed: %w", err)

7.2 使用errors.Is检查 #

go
if errors.Is(err, ErrNotFound) {
    // 处理
}

7.3 使用errors.As提取 #

go
var ve *ValidationError
if errors.As(err, &ve) {
    // 使用ve
}

八、总结 #

错误包装要点:

函数 说明
fmt.Errorf(“%w”) 包装错误
errors.Is 检查错误值
errors.As 检查错误类型
errors.Unwrap 解包错误

关键点:

  1. %w包装:使用%w包装错误
  2. errors.Is:检查错误链中的值
  3. errors.As:检查错误链中的类型
  4. Unwrap:实现Unwrap支持错误链
  5. 错误链:错误可以包含其他错误

准备好学习panic与recover了吗?让我们进入下一章!

最后更新:2026-03-26