Go Error Handling

Go uses a distinctive error handling mechanism that handles error conditions through explicit error return values. This approach makes error handling explicit and predictable.

📋 Error Handling Basics

The error Interface

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)
        }
    }
}

Custom Error Types

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)
        }
    }
}

🔄 Error Wrapping and Chaining

Error Wrapping (Go 1.13+)

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)
        }
    }
}

🎯 Error Handling Patterns

Error Handling Strategies

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)
    }
}

💼 Practical Examples

Web API Error Handling

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)
}

🎓 Summary

In this chapter, we explored Go error handling in depth:

  • Error Basics: The error interface and fundamental error handling patterns
  • Custom Errors: Creating custom error types and structures
  • Error Wrapping: Error chaining and wrapping mechanisms
  • Handling Patterns: Strategies such as retry, aggregation, and panic recovery
  • Practical Applications: A complete Web API error handling example
  • Best Practices: Design principles and techniques for error handling

Although Go's error handling mechanism may seem verbose, it makes error handling explicit and predictable—an essential foundation for writing robust programs.


Next, we will learn about the Go Concurrency Model and Concurrency Safety, one of the most important features of the Go language.

::: tip Error Handling Recommendations

  • Always check and handle errors; never ignore them
  • Use meaningful error messages to aid debugging
  • Use error wrapping appropriately to preserve error context
  • For recoverable errors, consider implementing a retry mechanism :::