Skip to content

Go 错误处理

Go 语言采用了独特的错误处理机制,通过显式的错误返回值来处理错误情况。这种方式让错误处理变得明确和可预测。

📋 错误处理基础

error 接口

go
package main

import (
    "errors"
    "fmt"
)

// Go 内置的 error 接口
// type error interface {
//     Error() string
// }

// 基本错误处理
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为零")
    }
    return a / b, nil
}

// 多种错误情况
func validateAge(age int) error {
    if age < 0 {
        return errors.New("年龄不能为负数")
    }
    if age > 150 {
        return errors.New("年龄不能超过150岁")
    }
    return nil
}

// 返回结果和错误
func sqrt(x float64) (float64, error) {
    if x < 0 {
        return 0, fmt.Errorf("不能计算负数的平方根: %f", x)
    }
    
    // 简单的牛顿法计算平方根
    guess := x / 2
    for i := 0; i < 10; i++ {
        guess = (guess + x/guess) / 2
    }
    
    return guess, nil
}

func main() {
    fmt.Println("=== 基本错误处理 ===")
    
    // 正常情况
    if result, err := divide(10, 2); err != nil {
        fmt.Printf("错误: %v\n", err)
    } else {
        fmt.Printf("10 / 2 = %.2f\n", result)
    }
    
    // 错误情况
    if result, err := divide(10, 0); err != nil {
        fmt.Printf("错误: %v\n", err)
    } else {
        fmt.Printf("10 / 0 = %.2f\n", result)
    }
    
    // 验证年龄
    fmt.Println("\n=== 年龄验证 ===")
    ages := []int{25, -5, 200, 30}
    
    for _, age := range ages {
        if err := validateAge(age); err != nil {
            fmt.Printf("年龄 %d 无效: %v\n", age, err)
        } else {
            fmt.Printf("年龄 %d 有效\n", age)
        }
    }
    
    // 平方根计算
    fmt.Println("\n=== 平方根计算 ===")
    numbers := []float64{16, 25, -4, 0}
    
    for _, num := range numbers {
        if result, err := sqrt(num); err != nil {
            fmt.Printf("错误: %v\n", err)
        } else {
            fmt.Printf("√%.1f = %.6f\n", num, result)
        }
    }
}

自定义错误类型

go
package main

import (
    "fmt"
    "time"
)

// 自定义错误类型
type ValidationError struct {
    Field   string
    Value   interface{}
    Message string
}

func (ve ValidationError) Error() string {
    return fmt.Sprintf("验证失败 [%s]: %s (值: %v)", ve.Field, ve.Message, ve.Value)
}

// 网络错误类型
type NetworkError struct {
    Operation string
    URL       string
    Timestamp time.Time
    Err       error
}

func (ne NetworkError) Error() string {
    return fmt.Sprintf("网络错误 [%s] %s at %v: %v", 
                      ne.Operation, ne.URL, ne.Timestamp.Format("15:04:05"), ne.Err)
}

// 实现 Unwrap 方法,支持错误链
func (ne NetworkError) Unwrap() error {
    return ne.Err
}

// 用户注册验证
func validateUser(name, email string, age int) error {
    if name == "" {
        return ValidationError{
            Field:   "name",
            Value:   name,
            Message: "用户名不能为空",
        }
    }
    
    if len(name) < 2 {
        return ValidationError{
            Field:   "name",
            Value:   name,
            Message: "用户名至少需要2个字符",
        }
    }
    
    if email == "" {
        return ValidationError{
            Field:   "email",
            Value:   email,
            Message: "邮箱不能为空",
        }
    }
    
    if age < 18 {
        return ValidationError{
            Field:   "age",
            Value:   age,
            Message: "年龄必须满18岁",
        }
    }
    
    return nil
}

// 模拟网络请求
func fetchUserData(userID string) (map[string]interface{}, error) {
    if userID == "" {
        return nil, ValidationError{
            Field:   "userID",
            Value:   userID,
            Message: "用户ID不能为空",
        }
    }
    
    if userID == "invalid" {
        return nil, NetworkError{
            Operation: "GET",
            URL:       fmt.Sprintf("/api/users/%s", userID),
            Timestamp: time.Now(),
            Err:       fmt.Errorf("用户不存在"),
        }
    }
    
    if userID == "timeout" {
        return nil, NetworkError{
            Operation: "GET",
            URL:       fmt.Sprintf("/api/users/%s", userID),
            Timestamp: time.Now(),
            Err:       fmt.Errorf("请求超时"),
        }
    }
    
    // 模拟成功响应
    return map[string]interface{}{
        "id":   userID,
        "name": "张三",
        "age":  25,
    }, nil
}

func main() {
    fmt.Println("=== 自定义错误类型 ===")
    
    // 用户验证测试
    fmt.Println("用户验证测试:")
    testCases := []struct {
        name  string
        email string
        age   int
    }{
        {"张三", "zhangsan@example.com", 25},
        {"", "empty@example.com", 20},
        {"A", "short@example.com", 30},
        {"李四", "", 22},
        {"王五", "wangwu@example.com", 15},
    }
    
    for _, tc := range testCases {
        if err := validateUser(tc.name, tc.email, tc.age); err != nil {
            fmt.Printf("  %v\n", err)
            
            // 类型断言检查具体错误类型
            if ve, ok := err.(ValidationError); ok {
                fmt.Printf("    错误字段: %s\n", ve.Field)
            }
        } else {
            fmt.Printf("  用户 %s 验证通过\n", tc.name)
        }
    }
    
    // 网络请求测试
    fmt.Println("\n网络请求测试:")
    userIDs := []string{"user123", "", "invalid", "timeout"}
    
    for _, id := range userIDs {
        if data, err := fetchUserData(id); err != nil {
            fmt.Printf("  请求失败: %v\n", err)
            
            // 检查错误类型
            switch e := err.(type) {
            case ValidationError:
                fmt.Printf("    验证错误: 字段 %s\n", e.Field)
            case NetworkError:
                fmt.Printf("    网络错误: %s %s\n", e.Operation, e.URL)
            }
        } else {
            fmt.Printf("  用户数据: %v\n", data)
        }
    }
}

🔄 错误包装和链式处理

错误包装(Go 1.13+)

go
package main

import (
    "errors"
    "fmt"
)

// 数据层错误
func dataLayerError() error {
    return errors.New("数据库连接失败")
}

// 业务层错误
func businessLayerError() error {
    err := dataLayerError()
    if err != nil {
        return fmt.Errorf("业务处理失败: %w", err)
    }
    return nil
}

// 控制层错误
func controllerLayerError() error {
    err := businessLayerError()
    if err != nil {
        return fmt.Errorf("请求处理失败: %w", err)
    }
    return nil
}

// 自定义错误类型用于包装
type ServiceError struct {
    Service string
    Code    int
    Err     error
}

func (se ServiceError) Error() string {
    return fmt.Sprintf("服务错误 [%s:%d]: %v", se.Service, se.Code, se.Err)
}

func (se ServiceError) Unwrap() error {
    return se.Err
}

// 模拟服务调用
func callUserService(userID string) error {
    if userID == "" {
        baseErr := errors.New("用户ID为空")
        return ServiceError{
            Service: "UserService",
            Code:    400,
            Err:     baseErr,
        }
    }
    
    if userID == "not_found" {
        baseErr := errors.New("用户不存在")
        return ServiceError{
            Service: "UserService",
            Code:    404,
            Err:     baseErr,
        }
    }
    
    return nil
}

func callOrderService(orderID string) error {
    if err := callUserService("not_found"); err != nil {
        return fmt.Errorf("订单服务调用用户服务失败: %w", err)
    }
    return nil
}

func main() {
    fmt.Println("=== 错误包装和链式处理 ===")
    
    // 基本错误链
    fmt.Println("错误链演示:")
    if err := controllerLayerError(); err != nil {
        fmt.Printf("最终错误: %v\n", err)
        
        // 解包错误链
        fmt.Println("错误链追踪:")
        currentErr := err
        level := 1
        
        for currentErr != nil {
            fmt.Printf("  第%d层: %v\n", level, currentErr)
            
            // 使用 errors.Unwrap 解包
            currentErr = errors.Unwrap(currentErr)
            level++
        }
    }
    
    // 使用 errors.Is 检查特定错误
    fmt.Println("\n使用 errors.Is 检查错误:")
    targetErr := errors.New("数据库连接失败")
    
    if err := controllerLayerError(); err != nil {
        if errors.Is(err, targetErr) {
            fmt.Println("错误链中包含数据库连接错误")
        } else {
            fmt.Println("错误链中不包含数据库连接错误")
        }
    }
    
    // 使用 errors.As 获取特定错误类型
    fmt.Println("\n使用 errors.As 获取错误类型:")
    if err := callOrderService("order123"); err != nil {
        fmt.Printf("订单服务错误: %v\n", err)
        
        var serviceErr ServiceError
        if errors.As(err, &serviceErr) {
            fmt.Printf("找到服务错误: 服务=%s, 代码=%d\n", 
                      serviceErr.Service, serviceErr.Code)
        }
    }
    
    // 多层错误包装
    fmt.Println("\n多层错误包装:")
    userErr := callUserService("")
    if userErr != nil {
        wrappedErr := fmt.Errorf("API调用失败: %w", userErr)
        finalErr := fmt.Errorf("处理用户请求失败: %w", wrappedErr)
        
        fmt.Printf("最终错误: %v\n", finalErr)
        
        // 检查原始错误类型
        var original ServiceError
        if errors.As(finalErr, &original) {
            fmt.Printf("原始服务错误代码: %d\n", original.Code)
        }
    }
}

🎯 错误处理模式

错误处理策略

go
package main

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

// 重试策略
type RetryConfig struct {
    MaxAttempts int
    Delay       time.Duration
    BackoffRate float64
}

func withRetry(fn func() error, config RetryConfig) error {
    var lastErr error
    delay := config.Delay
    
    for attempt := 1; attempt <= config.MaxAttempts; attempt++ {
        err := fn()
        if err == nil {
            return nil
        }
        
        lastErr = err
        
        if attempt == config.MaxAttempts {
            break
        }
        
        fmt.Printf("第%d次尝试失败: %v%v后重试\n", attempt, err, delay)
        time.Sleep(delay)
        
        // 指数退避
        delay = time.Duration(float64(delay) * config.BackoffRate)
    }
    
    return fmt.Errorf("重试%d次后仍失败,最后错误: %w", config.MaxAttempts, lastErr)
}

// 模拟不稳定的操作
var attemptCount = 0

func unstableOperation() error {
    attemptCount++
    
    if attemptCount < 3 {
        return errors.New("操作失败")
    }
    
    attemptCount = 0 // 重置计数器
    return nil
}

// 错误聚合
type ErrorCollector struct {
    errors []error
}

func (ec *ErrorCollector) Add(err error) {
    if err != nil {
        ec.errors = append(ec.errors, err)
    }
}

func (ec *ErrorCollector) HasErrors() bool {
    return len(ec.errors) > 0
}

func (ec *ErrorCollector) Error() string {
    if len(ec.errors) == 0 {
        return ""
    }
    
    result := fmt.Sprintf("发现%d个错误:\n", len(ec.errors))
    for i, err := range ec.errors {
        result += fmt.Sprintf("  %d. %v\n", i+1, err)
    }
    
    return result
}

func (ec *ErrorCollector) Errors() []error {
    return ec.errors
}

// 批量验证
func validateBatch(items []string) error {
    collector := &ErrorCollector{}
    
    for i, item := range items {
        if item == "" {
            collector.Add(fmt.Errorf("索引%d: 项目不能为空", i))
        } else if len(item) < 3 {
            collector.Add(fmt.Errorf("索引%d: 项目'%s'长度不能少于3个字符", i, item))
        }
    }
    
    if collector.HasErrors() {
        return collector
    }
    
    return nil
}

// 恐慌恢复
func safeDivide(a, b float64) (result float64, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("计算发生恐慌: %v", r)
        }
    }()
    
    if b == 0 {
        panic("除数为零")
    }
    
    return a / b, nil
}

// 资源清理模式
type Resource struct {
    name   string
    opened bool
}

func NewResource(name string) *Resource {
    fmt.Printf("打开资源: %s\n", name)
    return &Resource{name: name, opened: true}
}

func (r *Resource) Close() error {
    if !r.opened {
        return errors.New("资源已关闭")
    }
    
    fmt.Printf("关闭资源: %s\n", r.name)
    r.opened = false
    return nil
}

func (r *Resource) Use() error {
    if !r.opened {
        return errors.New("资源未打开")
    }
    
    if r.name == "error_resource" {
        return errors.New("资源使用失败")
    }
    
    fmt.Printf("使用资源: %s\n", r.name)
    return nil
}

func useResourceSafely(name string) (err error) {
    resource := NewResource(name)
    
    // 确保资源被正确关闭
    defer func() {
        if closeErr := resource.Close(); closeErr != nil {
            if err == nil {
                err = closeErr
            } else {
                err = fmt.Errorf("原错误: %v; 关闭错误: %v", err, closeErr)
            }
        }
    }()
    
    return resource.Use()
}

func main() {
    fmt.Println("=== 错误处理模式 ===")
    
    // 重试模式
    fmt.Println("1. 重试模式:")
    config := RetryConfig{
        MaxAttempts: 5,
        Delay:       100 * time.Millisecond,
        BackoffRate: 1.5,
    }
    
    err := withRetry(unstableOperation, config)
    if err != nil {
        fmt.Printf("最终失败: %v\n", err)
    } else {
        fmt.Println("操作成功!")
    }
    
    // 错误聚合模式
    fmt.Println("\n2. 错误聚合模式:")
    items := []string{"有效项目", "", "ab", "另一个有效项目", "x"}
    
    if err := validateBatch(items); err != nil {
        fmt.Printf("批量验证失败:\n%v", err)
    } else {
        fmt.Println("批量验证通过")
    }
    
    // 恐慌恢复模式
    fmt.Println("\n3. 恐慌恢复模式:")
    if result, err := safeDivide(10, 2); err != nil {
        fmt.Printf("计算失败: %v\n", err)
    } else {
        fmt.Printf("10 / 2 = %.2f\n", result)
    }
    
    if result, err := safeDivide(10, 0); err != nil {
        fmt.Printf("计算失败: %v\n", err)
    } else {
        fmt.Printf("10 / 0 = %.2f\n", result)
    }
    
    // 资源清理模式
    fmt.Println("\n4. 资源清理模式:")
    
    fmt.Println("正常使用资源:")
    if err := useResourceSafely("normal_resource"); err != nil {
        fmt.Printf("使用失败: %v\n", err)
    }
    
    fmt.Println("\n错误使用资源:")
    if err := useResourceSafely("error_resource"); err != nil {
        fmt.Printf("使用失败: %v\n", err)
    }
}

💼 实际应用示例

Web API 错误处理

go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
)

// API 错误响应结构
type APIError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Details string `json:"details,omitempty"`
}

func (ae APIError) Error() string {
    return fmt.Sprintf("API错误 %d: %s", ae.Code, ae.Message)
}

// HTTP 状态码常量
const (
    StatusBadRequest          = 400
    StatusUnauthorized        = 401
    StatusNotFound           = 404
    StatusInternalServerError = 500
)

// 创建不同类型的 API 错误
func NewBadRequestError(message string) APIError {
    return APIError{
        Code:    StatusBadRequest,
        Message: "请求参数错误",
        Details: message,
    }
}

func NewNotFoundError(resource string) APIError {
    return APIError{
        Code:    StatusNotFound,
        Message: "资源未找到",
        Details: fmt.Sprintf("资源 '%s' 不存在", resource),
    }
}

func NewInternalError(details string) APIError {
    return APIError{
        Code:    StatusInternalServerError,
        Message: "服务器内部错误",
        Details: details,
    }
}

// 模拟用户服务
type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
    Age  int    `json:"age"`
}

var users = map[int]User{
    1: {ID: 1, Name: "张三", Age: 25},
    2: {ID: 2, Name: "李四", Age: 30},
    3: {ID: 3, Name: "王五", Age: 35},
}

// 获取用户
func getUser(id int) (User, error) {
    if id <= 0 {
        return User{}, NewBadRequestError("用户ID必须大于0")
    }
    
    user, exists := users[id]
    if !exists {
        return User{}, NewNotFoundError(fmt.Sprintf("用户ID=%d", id))
    }
    
    return user, nil
}

// 创建用户
func createUser(name string, age int) (User, error) {
    if name == "" {
        return User{}, NewBadRequestError("用户名不能为空")
    }
    
    if age < 0 || age > 150 {
        return User{}, NewBadRequestError("年龄必须在0-150之间")
    }
    
    // 模拟数据库错误
    if name == "error" {
        return User{}, NewInternalError("数据库保存失败")
    }
    
    // 生成新用户ID
    newID := len(users) + 1
    user := User{
        ID:   newID,
        Name: name,
        Age:  age,
    }
    
    users[newID] = user
    return user, nil
}

// HTTP 错误响应
func writeErrorResponse(w http.ResponseWriter, err error) {
    var apiErr APIError
    var statusCode int
    
    // 检查是否是 API 错误
    if ae, ok := err.(APIError); ok {
        apiErr = ae
        statusCode = ae.Code
    } else {
        // 未知错误,返回内部服务器错误
        apiErr = NewInternalError(err.Error())
        statusCode = StatusInternalServerError
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(statusCode)
    
    json.NewEncoder(w).Encode(apiErr)
}

// HTTP 成功响应
func writeSuccessResponse(w http.ResponseWriter, data interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    
    json.NewEncoder(w).Encode(map[string]interface{}{
        "success": true,
        "data":    data,
    })
}

// 模拟 HTTP 处理器
func handleGetUser(w http.ResponseWriter, r *http.Request) {
    // 这里简化处理,实际应该从URL路径或参数中获取ID
    userID := 1 // 模拟获取到的用户ID
    
    user, err := getUser(userID)
    if err != nil {
        writeErrorResponse(w, err)
        return
    }
    
    writeSuccessResponse(w, user)
}

func main() {
    fmt.Println("=== Web API 错误处理演示 ===")
    
    // 模拟不同的API调用场景
    fmt.Println("1. 正常获取用户:")
    if user, err := getUser(1); err != nil {
        fmt.Printf("错误: %v\n", err)
    } else {
        fmt.Printf("用户: %+v\n", user)
    }
    
    fmt.Println("\n2. 获取不存在的用户:")
    if user, err := getUser(999); err != nil {
        fmt.Printf("错误: %v\n", err)
        
        // 检查具体错误类型
        if apiErr, ok := err.(APIError); ok {
            fmt.Printf("错误码: %d\n", apiErr.Code)
            fmt.Printf("错误详情: %s\n", apiErr.Details)
        }
    } else {
        fmt.Printf("用户: %+v\n", user)
    }
    
    fmt.Println("\n3. 无效的用户ID:")
    if user, err := getUser(-1); err != nil {
        fmt.Printf("错误: %v\n", err)
    } else {
        fmt.Printf("用户: %+v\n", user)
    }
    
    fmt.Println("\n4. 创建用户:")
    if user, err := createUser("赵六", 28); err != nil {
        fmt.Printf("创建失败: %v\n", err)
    } else {
        fmt.Printf("创建成功: %+v\n", user)
    }
    
    fmt.Println("\n5. 创建用户时参数错误:")
    if user, err := createUser("", 25); err != nil {
        fmt.Printf("创建失败: %v\n", err)
    } else {
        fmt.Printf("创建成功: %+v\n", user)
    }
    
    fmt.Println("\n6. 模拟服务器内部错误:")
    if user, err := createUser("error", 25); err != nil {
        fmt.Printf("创建失败: %v\n", err)
    } else {
        fmt.Printf("创建成功: %+v\n", user)
    }
    
    // 演示 JSON 错误响应格式
    fmt.Println("\n7. JSON 错误响应示例:")
    err := NewNotFoundError("用户ID=999")
    jsonData, _ := json.MarshalIndent(err, "", "  ")
    fmt.Printf("JSON响应:\n%s\n", jsonData)
}

🎓 小结

本章我们深入学习了 Go 语言的错误处理:

  • 错误基础:error 接口和基本错误处理模式
  • 自定义错误:创建自定义错误类型和结构
  • 错误包装:错误链式处理和包装机制
  • 处理模式:重试、聚合、恐慌恢复等策略
  • 实际应用:Web API 错误处理的完整示例
  • 最佳实践:错误处理的设计原则和技巧

Go 语言的错误处理机制虽然看起来冗长,但它让错误处理变得明确和可预测,是编写健壮程序的重要基础。


接下来,我们将学习 Go 并发模型和并发安全,这是 Go 语言最重要的特性之一。

错误处理建议

  • 总是检查和处理错误,不要忽略
  • 使用有意义的错误消息,便于调试
  • 合理使用错误包装,保留错误上下文
  • 对于可恢复的错误,考虑重试机制

本站内容仅供学习和研究使用。