Skip to content

错误处理

Go语言使用显式的错误处理机制,不使用异常。

error类型

基本概念

error 是一个内置接口:

go
type error interface {
    Error() string
}

创建错误

go
import "errors"

// 使用errors.New创建简单错误
err := errors.New("出错了")

// 使用fmt.Errorf创建格式化错误
err := fmt.Errorf("用户 %s 不存在", username)

错误处理模式

基本模式

go
package main

import (
    "errors"
    "fmt"
)

// 返回错误的函数
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 2)
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    fmt.Printf("结果: %.2f\n", result)
    
    // 测试错误情况
    _, err = divide(10, 0)
    if err != nil {
        fmt.Println("错误:", err)
    }
}

提前返回

go
func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return fmt.Errorf("打开文件失败: %w", err)
    }
    defer file.Close()
    
    data, err := io.ReadAll(file)
    if err != nil {
        return fmt.Errorf("读取文件失败: %w", err)
    }
    
    // 处理数据...
    return nil
}

自定义错误

自定义错误类型

go
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("验证错误 [%s]: %s", e.Field, e.Message)
}

func validateUser(name string, age int) error {
    if name == "" {
        return &ValidationError{
            Field:   "name",
            Message: "姓名不能为空",
        }
    }
    if age < 0 || age > 150 {
        return &ValidationError{
            Field:   "age",
            Message: "年龄必须在0-150之间",
        }
    }
    return nil
}

func main() {
    err := validateUser("", 25)
    if err != nil {
        fmt.Println(err)
        
        // 类型断言获取详细信息
        if ve, ok := err.(*ValidationError); ok {
            fmt.Printf("字段: %s, 消息: %s\n", ve.Field, ve.Message)
        }
    }
}

错误包装

使用 %w 包装错误

go
func readFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("打开文件 %s 失败: %w", path, err)
    }
    defer file.Close()
    return nil
}

func main() {
    err := readFile("nonexistent.txt")
    fmt.Println(err)  // 打开文件 nonexistent.txt 失败: open nonexistent.txt: 系统找不到指定的文件。
}

解包错误

go
import "errors"

func main() {
    err := readFile("nonexistent.txt")
    
    // 使用errors.Is检查错误链
    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("文件不存在")
    }
    
    // 使用errors.As获取特定类型错误
    var pathErr *os.PathError
    if errors.As(err, &pathErr) {
        fmt.Println("路径:", pathErr.Path)
    }
}

panic和recover

panic

panic 用于不可恢复的错误:

go
func mustPositive(n int) int {
    if n < 0 {
        panic("数值必须为正数")
    }
    return n
}

func main() {
    fmt.Println(mustPositive(5))   // 正常
    fmt.Println(mustPositive(-1))  // panic
    fmt.Println("这行不会执行")
}

recover

recover 用于捕获panic:

go
func safeOperation() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到panic:", r)
        }
    }()
    
    panic("出错了")
}

func main() {
    safeOperation()
    fmt.Println("程序继续执行")
}

实际应用

go
func httpHandler(w http.ResponseWriter, r *http.Request) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("处理请求时发生panic:", err)
            http.Error(w, "内部服务器错误", 500)
        }
    }()
    
    // 处理请求...
}

错误处理最佳实践

1. 不要忽略错误

go
// 错误做法
file, _ := os.Open("file.txt")

// 正确做法
file, err := os.Open("file.txt")
if err != nil {
    return err
}

2. 提供上下文信息

go
// 错误做法
return err

// 正确做法
return fmt.Errorf("处理用户 %s 时出错: %w", username, err)

3. 使用自定义错误类型

go
type NotFoundError struct {
    Resource string
    ID       string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s (ID: %s) 不存在", e.Resource, e.ID)
}

func (e *NotFoundError) Is(target error) bool {
    t, ok := target.(*NotFoundError)
    if !ok {
        return false
    }
    return e.Resource == t.Resource
}

4. 错误变量定义

go
// 可导出的错误变量
var (
    ErrNotFound     = errors.New("资源不存在")
    ErrUnauthorized = errors.New("未授权")
    ErrBadRequest   = errors.New("请求参数错误")
)

// 使用errors.Is检查
if errors.Is(err, ErrNotFound) {
    // 处理不存在的情况
}

错误处理示例

数据库操作

go
type User struct {
    ID   int
    Name string
}

func GetUser(id int) (*User, error) {
    row := db.QueryRow("SELECT id, name FROM users WHERE id = ?", id)
    
    var user User
    err := row.Scan(&user.ID, &user.Name)
    if err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, &NotFoundError{Resource: "User", ID: fmt.Sprint(id)}
        }
        return nil, fmt.Errorf("查询用户失败: %w", err)
    }
    
    return &user, nil
}

配置文件读取

go
type Config struct {
    Host string `json:"host"`
    Port int    `json:"port"`
}

func LoadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("读取配置文件失败: %w", err)
    }
    
    var config Config
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("解析配置文件失败: %w", err)
    }
    
    if config.Host == "" {
        return nil, &ValidationError{Field: "host", Message: "不能为空"}
    }
    
    return &config, nil
}