jee said

Go语言错误处理最佳实践

unclejee & AI
764 words
4min read
Go语言错误处理最佳实践

Go语言错误处理最佳实践

Go语言的错误处理是其设计哲学的重要组成部分。与异常处理不同,Go将错误作为返回值显式处理,这促使开发者更加关注错误情况。

Error接口

Go语言的error是一个内置接口:

type error interface {
    Error() string
}

基本错误处理

标准错误处理模式

func readFile(filename string) ([]byte, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    return data, nil
}

func main() {
    data, err := readFile("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(data))
}

错误检查

// 总是检查错误
file, err := os.Open("file.txt")
if err != nil {
    return err
}
defer file.Close()

自定义错误

创建自定义错误类型

type MyError struct {
    Code    int
    Message string
}

func (e *MyError) Error() string {
    return fmt.Sprintf("错误 %d: %s", e.Code, e.Message)
}

func process(value int) error {
    if value < 0 {
        return &MyError{Code: 400, Message: "值不能为负数"}
    }
    return nil
}

使用errors.New

var (
    ErrInvalidInput = errors.New("无效的输入")
    ErrNotFound    = errors.New("未找到")
)

func validate(input string) error {
    if input == "" {
        return ErrInvalidInput
    }
    return nil
}

错误包装

Go 1.13+ 提供了错误包装功能。

使用fmt.Errorf包装错误

func readFile(filename string) ([]byte, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("读取文件 %s 失败: %w", filename, err)
    }
    return data, nil
}

使用errors.Unwrap

err := someFunction()
if err != nil {
    // 获取原始错误
    originalErr := errors.Unwrap(err)
    fmt.Println("原始错误:", originalErr)
}

使用errors.Is

err := someFunction()
if errors.Is(err, os.ErrNotExist) {
    fmt.Println("文件不存在")
}

使用errors.As

err := someFunction()
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    fmt.Printf("路径错误: %s\n", pathErr.Path)
}

错误处理模式

Sentinel错误

var (
    ErrUserNotFound = errors.New("用户不存在")
    ErrInvalidToken = errors.New("无效的令牌")
)

func getUser(id int) (*User, error) {
    // ...
    return nil, ErrUserNotFound
}

错误类型断言

func handleError(err error) {
    switch e := err.(type) {
    case *os.PathError:
        fmt.Printf("路径错误: %s\n", e.Path)
    case *net.OpError:
        fmt.Printf("网络错误: %s\n", e.Op)
    default:
        fmt.Printf("未知错误: %v\n", err)
    }
}

错误收集

func processFiles(filenames []string) error {
    var errs []error
    for _, filename := range filenames {
        if err := processFile(filename); err != nil {
            errs = append(errs, err)
        }
    }
    if len(errs) > 0 {
        return fmt.Errorf("处理文件时发生 %d 个错误", len(errs))
    }
    return nil
}

最佳实践

  1. 总是检查错误:不要忽略返回的错误
  2. 尽早返回错误:避免深层嵌套
  3. 包装错误信息:添加上下文信息
  4. 使用标准错误:对于常见错误使用预定义错误
  5. 避免panic:panic应该只用于不可恢复的错误
  6. 记录错误:使用log包记录错误信息

总结

Go语言的错误处理机制鼓励开发者显式处理错误,这虽然增加了代码量,但也提高了程序的可靠性。遵循这些最佳实践,可以编写出更加健壮的Go程序。