Skip to content

错误处理

Go 语言的错误处理采用显式错误返回的方式,这是 Go 语言设计哲学的重要体现。

错误类型

error 接口

Go 语言中的错误是一个接口:

go
type error interface {
    Error() string
}

创建错误

go
import "errors"

// 方式 1: 使用 errors.New
err := errors.New("发生了错误")

// 方式 2: 使用 fmt.Errorf
err := fmt.Errorf("用户 %s 不存在", username)

// 方式 3: 自定义错误类型
type MyError struct {
    Code    int
    Message string
}

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

err := &MyError{Code: 404, Message: "未找到"}

错误处理模式

基本模式

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

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Println("结果:", result)
}

错误检查

go
result, err := doSomething()
if err != nil {
    // 处理错误
    log.Printf("错误: %v", err)
    return err
}

忽略错误(不推荐)

go
result, _ := doSomething()  // 忽略错误

错误包装

fmt.Errorf 和 %w

Go 1.13+ 引入了错误包装:

go
func readFile(filename string) error {
    data, err := ioutil.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("读取文件失败: %w", err)
    }
    // 处理数据
    return nil
}

errors.Unwrap

go
err := readFile("test.txt")
if err != nil {
    unwrapped := errors.Unwrap(err)
    fmt.Println("原始错误:", unwrapped)
}

errors.Is

检查错误链中是否包含特定错误:

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

errors.As

将错误转换为特定类型:

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

自定义错误

简单自定义错误

go
type ValidationError struct {
    Field   string
    Message string
}

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

func validateUser(username string) error {
    if username == "" {
        return &ValidationError{
            Field:   "username",
            Message: "用户名不能为空",
        }
    }
    return nil
}

带错误码的错误

go
type AppError struct {
    Code    int
    Message string
    Err     error
}

func (e *AppError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("错误代码 %d: %s (%v)", e.Code, e.Message, e.Err)
    }
    return fmt.Sprintf("错误代码 %d: %s", e.Code, e.Message)
}

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

Panic 和 Recover

Panic

panic 用于处理不可恢复的错误:

go
func mustDivide(a, b float64) float64 {
    if b == 0 {
        panic("除数不能为0")
    }
    return a / b
}

Recover

recover 用于捕获 panic

go
func safeDivide(a, b float64) (result float64, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("发生 panic: %v", r)
        }
    }()
    
    result = mustDivide(a, b)
    return
}

使用场景

  • Panic: 用于不可恢复的错误,如程序逻辑错误
  • Error: 用于可预期的错误,如文件不存在、网络错误等

错误处理最佳实践

1. 尽早返回错误

go
// 好的做法
func process(data []byte) error {
    if len(data) == 0 {
        return errors.New("数据为空")
    }
    // 继续处理
    return nil
}

// 不好的做法
func process(data []byte) error {
    if len(data) != 0 {
        // 大量嵌套代码
    } else {
        return errors.New("数据为空")
    }
    return nil
}

2. 提供上下文信息

go
func readConfig(filename string) (*Config, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, fmt.Errorf("打开配置文件失败 %s: %w", filename, err)
    }
    defer file.Close()
    
    // 读取配置
    return config, nil
}

3. 使用 sentinel 错误

go
var (
    ErrNotFound    = errors.New("未找到")
    ErrInvalidData = errors.New("无效数据")
)

func findUser(id int) (*User, error) {
    if id <= 0 {
        return nil, ErrInvalidData
    }
    // 查找用户
    if user == nil {
        return nil, ErrNotFound
    }
    return user, nil
}

4. 错误日志记录

go
import "log"

func handleRequest() error {
    err := doSomething()
    if err != nil {
        log.Printf("处理请求失败: %v", err)
        return err
    }
    return nil
}

完整示例

go
package main

import (
    "errors"
    "fmt"
    "log"
)

// 自定义错误
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("验证失败 - %s: %s", e.Field, e.Message)
}

// 带错误码的错误
type AppError struct {
    Code    int
    Message string
    Err     error
}

func (e *AppError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("[%d] %s: %v", e.Code, e.Message, e.Err)
    }
    return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

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

// 业务函数
func validateUser(username, email string) error {
    if username == "" {
        return &ValidationError{
            Field:   "username",
            Message: "用户名不能为空",
        }
    }
    
    if email == "" {
        return &ValidationError{
            Field:   "email",
            Message: "邮箱不能为空",
        }
    }
    
    return nil
}

func createUser(username, email string) error {
    if err := validateUser(username, email); err != nil {
        return &AppError{
            Code:    400,
            Message: "创建用户失败",
            Err:     err,
        }
    }
    
    // 创建用户逻辑
    return nil
}

func main() {
    // 错误处理示例
    err := createUser("", "test@example.com")
    if err != nil {
        log.Printf("错误: %v", err)
        
        // 检查错误类型
        var validationErr *ValidationError
        if errors.As(err, &validationErr) {
            fmt.Printf("验证错误: %s\n", validationErr.Field)
        }
    }
    
    // Panic 和 Recover 示例
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("捕获到 panic: %v\n", r)
        }
    }()
    
    panic("这是一个 panic 示例")
}

下一步

接下来我们将学习:

基于 VitePress 构建