Golang学习历程【第十二篇 错误处理(error)】

1. Go语言错误处理哲学

Go语言采用了显式的错误处理机制,与其他语言的异常处理不同。Go认为错误是程序正常流程的一部分,应该被显式地检查和处理,而不是通过异常机制来处理。这种设计理念让错误处理更加透明和可控。

1.1 Go错误处理的特点

Go的错误处理哲学

  • 错误是值,不是异常
  • 显式检查比回避更好
  • 错误应该被处理,而不是被忽略
  • 失败的请求是正常的,成功的请求才是例外

与其他语言的对比

go 复制代码
// Java风格的异常处理
try {
    result = riskyOperation();
} catch (Exception e) {
    // 处理异常
}

// Go风格的错误处理
result, err := riskyOperation();
if err != nil {
    // 处理错误
    return err
}

1.2 为什么Go选择显式错误处理

优点

  1. 清晰可见:错误处理逻辑明确,不会隐藏在深处
  2. 强制处理:编译器会提醒你处理返回的错误
  3. 性能更好:避免了异常处理的性能开销
  4. 更可控:程序员完全控制错误处理流程

缺点

  1. 代码冗长:需要大量的if err != nil检查
  2. 容易忽略:新手可能习惯性忽略错误检查

2. error接口详解

2.1 error接口定义

go 复制代码
// error接口定义在builtin包中
type error interface {
    Error() string
}

2.2 errors包的基本使用

go 复制代码
package main

import (
    "errors"
    "fmt"
)

func main() {
    // 创建简单错误
    err1 := errors.New("这是一个简单的错误")
    fmt.Printf("错误1:%v\n", err1)
    fmt.Printf("错误1类型:%T\n", err1)
    
    // 使用fmt.Errorf创建格式化错误
    name := "张三"
    age := -5
    err2 := fmt.Errorf("用户 %s 的年龄 %d 无效", name, age)
    fmt.Printf("错误2:%v\n", err2)
    
    // 错误的字符串表示
    fmt.Printf("错误1的字符串:%s\n", err1.Error())
    fmt.Printf("错误2的字符串:%s\n", err2.Error())
    
    // nil错误表示没有错误
    var noError error
    fmt.Printf("nil错误:%v,是否为nil:%t\n", noError, noError == nil)
}

运行结果:

bash 复制代码
错误1:这是一个简单的错误
错误1类型:*errors.errorString
错误2:用户 张三 的年龄 -5 无效
错误1的字符串:这是一个简单的错误
错误2的字符串:用户 张三 的年龄 -5 无效
nil错误:<nil>,是否为nil:true

2.3 自定义错误类型

go 复制代码
package main

import (
    "fmt"
)

// 自定义错误类型1:简单结构体
type ValidationError struct {
    Field   string
    Value   interface{}
    Message string
}

func (e ValidationError) Error() string {
    return fmt.Sprintf("验证错误 - 字段:%s,值:%v,原因:%s", 
                      e.Field, e.Value, e.Message)
}

// 自定义错误类型2:带错误码
type APIError struct {
    Code    int
    Message string
    Details string
}

func (e APIError) Error() string {
    return fmt.Sprintf("API错误[%d]: %s (详情: %s)", e.Code, e.Message, e.Details)
}

// 自定义错误类型3:包装其他错误
type WrappedError struct {
    Operation string
    Err       error
}

func (e WrappedError) Error() string {
    return fmt.Sprintf("操作 %s 失败: %v", e.Operation, e.Err)
}

func (e WrappedError) Unwrap() error {
    return e.Err
}

// 使用自定义错误的函数
func validateUserAge(age int) error {
    if age < 0 {
        return ValidationError{
            Field:   "age",
            Value:   age,
            Message: "年龄不能为负数",
        }
    }
    if age > 150 {
        return ValidationError{
            Field:   "age",
            Value:   age,
            Message: "年龄不能超过150岁",
        }
    }
    return nil
}

func callAPI(endpoint string) error {
    if endpoint == "/admin" {
        return APIError{
            Code:    403,
            Message: "权限不足",
            Details: "需要管理员权限才能访问",
        }
    }
    if endpoint == "/timeout" {
        return APIError{
            Code:    504,
            Message: "请求超时",
            Details: "服务器响应超时",
        }
    }
    return nil
}

func processData(data string) error {
    if data == "" {
        return WrappedError{
            Operation: "数据处理",
            Err:       errors.New("输入数据为空"),
        }
    }
    return nil
}

func main() {
    fmt.Println("=== 自定义错误类型演示 ===")
    
    // 测试验证错误
    ages := []int{-5, 25, 200}
    for _, age := range ages {
        fmt.Printf("验证年龄 %d: ", age)
        if err := validateUserAge(age); err != nil {
            fmt.Printf("❌ %v\n", err)
        } else {
            fmt.Printf("✅ 验证通过\n")
        }
    }
    
    // 测试API错误
    endpoints := []string{"/user", "/admin", "/timeout"}
    fmt.Println("\n=== API调用测试 ===")
    for _, endpoint := range endpoints {
        fmt.Printf("调用 %s: ", endpoint)
        if err := callAPI(endpoint); err != nil {
            fmt.Printf("❌ %v\n", err)
        } else {
            fmt.Printf("✅ 调用成功\n")
        }
    }
    
    // 测试包装错误
    fmt.Println("\n=== 包装错误测试 ===")
    testData := []string{"", "有效数据"}
    for _, data := range testData {
        fmt.Printf("处理数据 '%s': ", data)
        if err := processData(data); err != nil {
            fmt.Printf("❌ %v\n", err)
            // 解包错误
            if wrapped, ok := err.(WrappedError); ok {
                fmt.Printf("    原始错误: %v\n", wrapped.Err)
            }
        } else {
            fmt.Printf("✅ 处理成功\n")
        }
    }
}

运行结果:

bash 复制代码
=== 自定义错误类型演示 ===
验证年龄 -5: ❌ 验证错误 - 字段:age,值:-5,原因:年龄不能为负数
验证年龄 25: ✅ 验证通过
验证年龄 200: ❌ 验证错误 - 字段:age,值:200,原因:年龄不能超过150岁

=== API调用测试 ===
调用 /user: ✅ 调用成功
调用 /admin: ❌ API错误[403]: 权限不足 (详情: 需要管理员权限才能访问)
调用 /timeout: ❌ API错误[504]: 请求超时 (详情: 服务器响应超时)

=== 包装错误测试 ===
处理数据 '': ❌ 操作 数据处理 失败: 输入数据为空
    原始错误: 输入数据为空
处理数据 '有效数据': ✅ 处理成功

3. 错误检查和处理模式

3.1 基本错误处理模式

go 复制代码
package main

import (
    "fmt"
    "os"
)

// 模式1:立即处理错误
func readFileBasic(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        return fmt.Errorf("打开文件失败 %s: %w", filename, err)
    }
    defer file.Close()
    
    // 文件操作...
    fmt.Printf("成功打开文件:%s\n", filename)
    return nil
}

// 模式2:错误累积处理
func processMultipleFiles(filenames []string) error {
    var errors []error
    
    for _, filename := range filenames {
        if err := readFileBasic(filename); err != nil {
            errors = append(errors, err)
        }
    }
    
    if len(errors) > 0 {
        return fmt.Errorf("处理文件时发生 %d 个错误: %v", len(errors), errors)
    }
    return nil
}

// 模式3:错误重试
func retryOperation(operation func() error, maxRetries int) error {
    var lastErr error
    
    for i := 0; i < maxRetries; i++ {
        err := operation()
        if err == nil {
            return nil  // 成功
        }
        lastErr = err
        fmt.Printf("第 %d 次尝试失败:%v\n", i+1, err)
    }
    
    return fmt.Errorf("重试 %d 次后仍然失败,最后错误:%w", maxRetries, lastErr)
}

// 模式4:错误忽略(谨慎使用)
func ignoreErrors() {
    // 只在确实可以忽略错误的情况下使用
    dir, _ := os.Getwd()  // 忽略错误
    fmt.Printf("当前目录:%s\n", dir)
}

func main() {
    fmt.Println("=== 基本错误处理模式 ===")
    
    // 测试基本模式
    fmt.Println("1. 基本错误处理:")
    if err := readFileBasic("不存在的文件.txt"); err != nil {
        fmt.Printf("❌ %v\n", err)
    }
    
    // 测试累积模式
    fmt.Println("\n2. 错误累积处理:")
    files := []string{"file1.txt", "file2.txt", "file3.txt"}
    if err := processMultipleFiles(files); err != nil {
        fmt.Printf("❌ %v\n", err)
    }
    
    // 测试重试模式
    fmt.Println("\n3. 错误重试:")
    failingOp := func() error {
        return fmt.Errorf("模拟操作失败")
    }
    
    if err := retryOperation(failingOp, 3); err != nil {
        fmt.Printf("❌ %v\n", err)
    }
    
    // 测试忽略错误
    fmt.Println("\n4. 错误忽略:")
    ignoreErrors()
}

运行结果:

bash 复制代码
=== 基本错误处理模式 ===
1. 基本错误处理:
❌ 打开文件失败 不存在的文件.txt: open 不存在的文件.txt: The system cannot find the file specified.

2. 错误累积处理:
❌ 处理文件时发生 3 个错误: [打开文件失败 file1.txt: open file1.txt: The system cannot find the file specified. 打开文件失败 file2.txt: open file2.txt: The system cannot find the file specified. 打开文件失败 file3.txt: open file3.txt: The system cannot find the file specified.]

3. 错误重试:
第 1 次尝试失败:模拟操作失败
第 2 次尝试失败:模拟操作失败
第 3 次尝试失败:模拟操作失败
❌ 重试 3 次后仍然失败,最后错误:模拟操作失败

4. 错误忽略:
当前目录:F:\blog\zg-blog

3.2 错误类型断言处理

go 复制代码
package main

import (
    "fmt"
    "os"
    "strconv"
)

type ValidationError struct {
    Field   string
    Message string
}

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

type NetworkError struct {
    Code    int
    Message string
}

func (e NetworkError) Error() string {
    return fmt.Sprintf("网络错误[%d]: %s", e.Code, e.Message)
}

func parseAndValidate(input string) error {
    // 尝试转换为整数
    num, err := strconv.Atoi(input)
    if err != nil {
        return ValidationError{
            Field:   "input",
            Message: "输入必须是有效的整数",
        }
    }
    
    // 验证数值范围
    if num < 0 {
        return ValidationError{
            Field:   "number",
            Message: "数字不能为负数",
        }
    }
    
    // 模拟网络操作
    if num == 999 {
        return NetworkError{
            Code:    503,
            Message: "服务暂时不可用",
        }
    }
    
    return nil
}

func handleSpecificErrors() {
    inputs := []string{"abc", "-5", "999", "42"}
    
    for _, input := range inputs {
        fmt.Printf("处理输入 '%s': ", input)
        err := parseAndValidate(input)
        
        if err != nil {
            // 方法1:类型断言
            if validationErr, ok := err.(ValidationError); ok {
                fmt.Printf("❌ 验证错误 - 字段:%s,消息:%s\n", 
                          validationErr.Field, validationErr.Message)
                continue
            }
            
            // 方法2:类型选择
            switch specificErr := err.(type) {
            case NetworkError:
                fmt.Printf("❌ 网络错误 - 代码:%d,消息:%s\n", 
                          specificErr.Code, specificErr.Message)
            case ValidationError:
                fmt.Printf("❌ 验证错误 - 字段:%s,消息:%s\n", 
                          specificErr.Field, specificErr.Message)
            default:
                fmt.Printf("❌ 未知错误:%v\n", err)
            }
        } else {
            fmt.Printf("✅ 处理成功\n")
        }
    }
}

func main() {
    fmt.Println("=== 错误类型断言处理 ===")
    handleSpecificErrors()
    
    // 检查文件是否存在
    fmt.Println("\n=== 文件错误处理 ===")
    filenames := []string{"existing.txt", "missing.txt"}
    
    for _, filename := range filenames {
        fmt.Printf("检查文件 '%s': ", filename)
        _, err := os.Stat(filename)
        
        if err != nil {
            if os.IsNotExist(err) {
                fmt.Printf("❌ 文件不存在\n")
            } else if os.IsPermission(err) {
                fmt.Printf("❌ 权限不足\n")
            } else {
                fmt.Printf("❌ 其他错误:%v\n", err)
            }
        } else {
            fmt.Printf("✅ 文件存在\n")
        }
    }
}

运行结果:

bash 复制代码
=== 错误类型断言处理 ===
处理输入 'abc': ❌ 验证错误 - 字段:input,消息:输入必须是有效的整数
处理输入 '-5': ❌ 验证错误 - 字段:number,消息:数字不能为负数
处理输入 '999': ❌ 网络错误 - 代码:503,消息:服务暂时不可用
处理输入 '42': ✅ 处理成功

=== 文件错误处理 ===
检查文件 'existing.txt': ✅ 文件存在
检查文件 'missing.txt': ❌ 文件不存在

4. 错误包装和上下文

4.1 errors包的新功能(Go 1.13+)

go 复制代码
package main

import (
    "errors"
    "fmt"
    "io"
    "os"
)

// 模拟数据库操作
func databaseQuery(query string) error {
    if query == "invalid" {
        return errors.New("数据库查询语法错误")
    }
    if query == "timeout" {
        return errors.New("数据库连接超时")
    }
    return nil
}

// 业务层函数
func getUserData(userID string) error {
    // 验证用户ID
    if userID == "" {
        return fmt.Errorf("用户ID不能为空")
    }
    
    // 查询数据库
    if err := databaseQuery("SELECT * FROM users WHERE id = " + userID); err != nil {
        return fmt.Errorf("获取用户数据失败: %w", err)  // 包装错误
    }
    
    return nil
}

// 服务层函数
func handleUserRequest(userID string) error {
    if err := getUserData(userID); err != nil {
        return fmt.Errorf("处理用户请求失败: %w", err)  // 再次包装
    }
    return nil
}

func demonstrateErrorWrapping() {
    userIDs := []string{"", "invalid", "timeout", "123"}
    
    for _, userID := range userIDs {
        fmt.Printf("处理用户请求 UserID='%s': ", userID)
        err := handleUserRequest(userID)
        
        if err != nil {
            fmt.Printf("❌ %v\n", err)
            
            // 检查特定错误
            if errors.Is(err, os.ErrNotExist) {
                fmt.Printf("    原因:文件不存在\n")
            }
            
            // 查找特定类型的错误
            var pathError *os.PathError
            if errors.As(err, &pathError) {
                fmt.Printf("    路径错误:操作=%s,路径=%s\n", 
                          pathError.Op, pathError.Path)
            }
            
            // 打印错误链
            fmt.Printf("    错误链:")
            for err != nil {
                fmt.Printf("%v", err)
                if err = errors.Unwrap(err); err != nil {
                    fmt.Printf(" -> ")
                }
            }
            fmt.Println()
        } else {
            fmt.Printf("✅ 请求处理成功\n")
        }
        fmt.Println()
    }
}

// 自定义可包装错误
type CustomError struct {
    Code    int
    Message string
    Err     error  // 嵌入底层错误
}

func (e CustomError) 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 CustomError) Unwrap() error {
    return e.Err
}

func main() {
    fmt.Println("=== 错误包装和上下文 ===")
    demonstrateErrorWrapping()
    
    // 自定义错误包装示例
    fmt.Println("=== 自定义错误包装 ===")
    baseErr := errors.New("底层错误")
    wrappedErr := CustomError{
        Code:    1001,
        Message: "业务逻辑错误",
        Err:     baseErr,
    }
    
    fmt.Printf("包装错误:%v\n", wrappedErr)
    fmt.Printf("底层错误:%v\n", errors.Unwrap(wrappedErr))
    fmt.Printf("是否有底层错误:%t\n", errors.Is(wrappedErr, baseErr))
}

运行结果:

bash 复制代码
=== 错误包装和上下文 ===
处理用户请求 UserID='': ❌ 处理用户请求失败: 获取用户数据失败: 用户ID不能为空
    错误链:处理用户请求失败: 获取用户数据失败: 用户ID不能为空

处理用户请求 UserID='invalid': ❌ 处理用户请求失败: 获取用户数据失败: 数据库查询语法错误
    错误链:处理用户请求失败: 获取用户数据失败: 数据库查询语法错误

处理用户请求 UserID='timeout': ❌ 处理用户请求失败: 获取用户数据失败: 数据库连接超时
    错误链:处理用户请求失败: 获取用户数据失败: 数据库连接超时

处理用户请求 UserID='123': ✅ 请求处理成功

=== 自定义错误包装 ===
包装错误:自定义错误[1001]: 业务逻辑错误 (原因: 底层错误)
底层错误:底层错误
是否有底层错误:true

4.2 多错误处理

go 复制代码
package main

import (
    "errors"
    "fmt"
    "strings"
)

// 错误集合类型
type ErrorCollection []error

func (ec ErrorCollection) Error() string {
    if len(ec) == 0 {
        return ""
    }
    
    var errorMsgs []string
    for _, err := range ec {
        errorMsgs = append(errorMsgs, err.Error())
    }
    return fmt.Sprintf("发生 %d 个错误: %s", 
                      len(ec), strings.Join(errorMsgs, "; "))
}

// 并行任务处理
func processTasks(taskNames []string) error {
    var errorsList ErrorCollection
    
    // 模拟并行处理多个任务
    for _, taskName := range taskNames {
        if err := processSingleTask(taskName); err != nil {
            errorsList = append(errorsList, 
                fmt.Errorf("任务 '%s' 失败: %w", taskName, err))
        }
    }
    
    if len(errorsList) > 0 {
        return errorsList
    }
    return nil
}

func processSingleTask(taskName string) error {
    switch taskName {
    case "task1":
        return errors.New("网络连接失败")
    case "task2":
        return errors.New("文件不存在")
    case "task3":
        return nil  // 成功
    case "task4":
        return errors.New("权限不足")
    default:
        return errors.New("未知任务")
    }
}

// 错误分组处理
func handleGroupedErrors() {
    taskGroups := [][]string{
        {"task1", "task2", "task3"},
        {"task3", "task4"},
        {"task1", "task4", "task5"},
    }
    
    for i, tasks := range taskGroups {
        fmt.Printf("处理任务组 %d %v:\n", i+1, tasks)
        err := processTasks(tasks)
        
        if err != nil {
            if errorCollection, ok := err.(ErrorCollection); ok {
                fmt.Printf("  ❌ 发生 %d 个错误:\n", len(errorCollection))
                for j, singleErr := range errorCollection {
                    fmt.Printf("    %d. %v\n", j+1, singleErr)
                }
            } else {
                fmt.Printf("  ❌ 单个错误: %v\n", err)
            }
        } else {
            fmt.Printf("  ✅ 所有任务成功完成\n")
        }
        fmt.Println()
    }
}

func main() {
    fmt.Println("=== 多错误处理 ===")
    handleGroupedErrors()
    
    // 错误合并示例
    fmt.Println("=== 错误合并 ===")
    err1 := errors.New("第一个错误")
    err2 := errors.New("第二个错误")
    err3 := errors.New("第三个错误")
    
    combined := ErrorCollection{err1, err2, err3}
    fmt.Printf("合并错误:%v\n", combined)
    
    // 空错误集合
    empty := ErrorCollection{}
    fmt.Printf("空错误集合:%v,是否为nil:%t\n", empty, empty == nil)
}

运行结果:

bash 复制代码
=== 多错误处理 ===
处理任务组 1 [task1 task2 task3]:
  ❌ 发生 2 个错误:
    1. 任务 'task1' 失败: 网络连接失败
    2. 任务 'task2' 失败: 文件不存在

处理任务组 2 [task3 task4]:
  ❌ 发生 1 个错误:
    1. 任务 'task4' 失败: 权限不足

处理任务组 3 [task1 task4 task5]:
  ❌ 发生 3 个错误:
    1. 任务 'task1' 失败: 网络连接失败
    2. 任务 'task4' 失败: 权限不足
    3. 任务 'task5' 失败: 未知任务

=== 错误合并 ===
合并错误:发生 3 个错误: 第一个错误; 第二个错误; 第三个错误
空错误集合:,是否为nil:true

5. 实际应用中的错误处理

5.1 Web服务错误处理

go 复制代码
package main

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

// API错误响应结构
type APIErrorResponse struct {
    Error   bool   `json:"error"`
    Code    int    `json:"code"`
    Message string `json:"message"`
    Time    string `json:"time"`
}

// 自定义HTTP错误类型
type HTTPError struct {
    Code    int
    Message string
    Err     error
}

func (e HTTPError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("HTTP %d: %s (原因: %v)", e.Code, e.Message, e.Err)
    }
    return fmt.Sprintf("HTTP %d: %s", e.Code, e.Message)
}

// 错误处理中间件
func errorHandler(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                fmt.Printf("恐慌恢复:%v\n", err)
                sendErrorResponse(w, http.StatusInternalServerError, "内部服务器错误")
            }
        }()
        
        next(w, r)
    }
}

// 发送错误响应
func sendErrorResponse(w http.ResponseWriter, code int, message string) {
    response := APIErrorResponse{
        Error:   true,
        Code:    code,
        Message: message,
        Time:    time.Now().Format(time.RFC3339),
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(code)
    
    if err := json.NewEncoder(w).Encode(response); err != nil {
        fmt.Printf("JSON编码错误:%v\n", err)
    }
}

// 模拟用户服务
type UserService struct{}

func (s *UserService) GetUser(id string) (map[string]interface{}, error) {
    if id == "" {
        return nil, HTTPError{
            Code:    http.StatusBadRequest,
            Message: "用户ID不能为空",
        }
    }
    
    if id == "admin" {
        return nil, HTTPError{
            Code:    http.StatusForbidden,
            Message: "禁止访问管理员账户",
        }
    }
    
    if id == "missing" {
        return nil, HTTPError{
            Code:    http.StatusNotFound,
            Message: "用户不存在",
        }
    }
    
    // 成功情况
    return map[string]interface{}{
        "id":   id,
        "name": fmt.Sprintf("用户%s", id),
        "age":  25,
    }, nil
}

// HTTP处理器
func userHandler(w http.ResponseWriter, r *http.Request) {
    userID := r.URL.Query().Get("id")
    
    userService := &UserService{}
    user, err := userService.GetUser(userID)
    
    if err != nil {
        if httpErr, ok := err.(HTTPError); ok {
            sendErrorResponse(w, httpErr.Code, httpErr.Message)
        } else {
            sendErrorResponse(w, http.StatusInternalServerError, "内部错误")
        }
        return
    }
    
    // 成功响应
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(user)
}

func demonstrateWebErrors() {
    // 模拟HTTP请求处理
    testCases := []string{"", "admin", "missing", "normal"}
    
    fmt.Println("=== Web服务错误处理演示 ===")
    for _, userID := range testCases {
        fmt.Printf("请求用户ID '%s': ", userID)
        
        // 模拟请求
        userService := &UserService{}
        user, err := userService.GetUser(userID)
        
        if err != nil {
            if httpErr, ok := err.(HTTPError); ok {
                fmt.Printf("❌ HTTP错误 %d: %s\n", httpErr.Code, httpErr.Message)
            } else {
                fmt.Printf("❌ 未知错误: %v\n", err)
            }
        } else {
            fmt.Printf("✅ 成功获取用户: %+v\n", user)
        }
    }
}

func main() {
    demonstrateWebErrors()
    
    // 启动HTTP服务器示例(注释掉避免实际启动)
    /*
    http.HandleFunc("/user", errorHandler(userHandler))
    fmt.Println("服务器启动在 :8080")
    http.ListenAndServe(":8080", nil)
    */
    
    // 错误日志记录示例
    fmt.Println("\n=== 错误日志记录 ===")
    logError := func(operation string, err error) {
        fmt.Printf("[%s] ERROR: %v\n", 
                  time.Now().Format("2006-01-02 15:04:05"), err)
    }
    
    // 模拟各种错误场景
    errorsToLog := []error{
        errors.New("数据库连接失败"),
        fmt.Errorf("文件读取错误: %w", errors.New("权限不足")),
        HTTPError{Code: 500, Message: "内部服务器错误"},
    }
    
    for _, err := range errorsToLog {
        logError("数据处理", err)
    }
}

运行结果:

bash 复制代码
=== Web服务错误处理演示 ===
请求用户ID '': ❌ HTTP错误 400: 用户ID不能为空
请求用户ID 'admin': ❌ HTTP错误 403: 禁止访问管理员账户
请求用户ID 'missing': ❌ HTTP错误 404: 用户不存在
请求用户ID 'normal': ✅ 成功获取用户: map[age:25 id:normal name:用户normal]

=== 错误日志记录 ===
[2024-01-15 15:30:45] ERROR: 数据库连接失败
[2024-01-15 15:30:45] ERROR: 文件读取错误: 权限不足
[2024-01-15 15:30:45] ERROR: HTTP 500: 内部服务器错误

5.2 数据库操作错误处理

go 复制代码
package main

import (
    "database/sql"
    "errors"
    "fmt"
)

// 模拟数据库驱动
type MockDB struct {
    tables map[string][]map[string]interface{}
}

func NewMockDB() *MockDB {
    return &MockDB{
        tables: make(map[string][]map[string]interface{}),
    }
}

func (db *MockDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
    if query == "invalid sql" {
        return nil, errors.New("SQL语法错误")
    }
    
    if query == "timeout" {
        return nil, errors.New("查询超时")
    }
    
    // 模拟成功查询
    return &sql.Rows{}, nil
}

func (db *MockDB) Exec(query string, args ...interface{}) (sql.Result, error) {
    if query == "duplicate key" {
        return nil, errors.New("违反唯一约束")
    }
    
    if query == "foreign key violation" {
        return nil, errors.New("外键约束违规")
    }
    
    return &mockResult{rowsAffected: 1}, nil
}

type mockResult struct {
    rowsAffected int64
}

func (r *mockResult) LastInsertId() (int64, error) {
    return 1, nil
}

func (r *mockResult) RowsAffected() (int64, error) {
    return r.rowsAffected, nil
}

// 数据访问层
type UserRepository struct {
    db *MockDB
}

func NewUserRepository(db *MockDB) *UserRepository {
    return &UserRepository{db: db}
}

func (r *UserRepository) CreateUser(user map[string]interface{}) error {
    // 验证用户数据
    if user["name"] == nil || user["name"] == "" {
        return fmt.Errorf("用户名不能为空")
    }
    
    if user["email"] == nil || user["email"] == "" {
        return fmt.Errorf("邮箱不能为空")
    }
    
    // 执行数据库插入
    query := "INSERT INTO users (name, email) VALUES (?, ?)"
    _, err := r.db.Exec(query, user["name"], user["email"])
    if err != nil {
        // 根据不同错误类型返回不同的业务错误
        if err.Error() == "违反唯一约束" {
            return fmt.Errorf("邮箱地址已存在:%w", err)
        }
        if err.Error() == "外键约束违规" {
            return fmt.Errorf("关联数据不存在:%w", err)
        }
        return fmt.Errorf("数据库操作失败:%w", err)
    }
    
    return nil
}

func (r *UserRepository) FindUserByEmail(email string) (map[string]interface{}, error) {
    if email == "" {
        return nil, fmt.Errorf("邮箱地址不能为空")
    }
    
    query := "SELECT * FROM users WHERE email = ?"
    rows, err := r.db.Query(query, email)
    if err != nil {
        return nil, fmt.Errorf("查询用户失败:%w", err)
    }
    defer rows.Close()
    
    // 模拟查询结果
    if email == "notfound@example.com" {
        return nil, fmt.Errorf("用户不存在")
    }
    
    return map[string]interface{}{
        "id":    1,
        "name":  "张三",
        "email": email,
    }, nil
}

func demonstrateDatabaseErrors() {
    db := NewMockDB()
    repo := NewUserRepository(db)
    
    fmt.Println("=== 数据库错误处理演示 ===")
    
    // 测试创建用户的各种错误情况
    testUsers := []map[string]interface{}{
        {"name": "", "email": "test@example.com"},           // 名字为空
        {"name": "张三", "email": ""},                       // 邮箱为空
        {"name": "李四", "email": "duplicate@example.com"},  // 邮箱重复
        {"name": "王五", "email": "valid@example.com"},      // 正常情况
    }
    
    for i, user := range testUsers {
        fmt.Printf("测试%d - 创建用户 %+v: ", i+1, user)
        err := repo.CreateUser(user)
        if err != nil {
            fmt.Printf("❌ %v\n", err)
        } else {
            fmt.Printf("✅ 创建成功\n")
        }
    }
    
    // 测试查询用户
    fmt.Println("\n=== 查询用户测试 ===")
    testEmails := []string{
        "",                           // 空邮箱
        "notfound@example.com",       // 用户不存在
        "valid@example.com",          // 正常查询
    }
    
    for _, email := range testEmails {
        fmt.Printf("查询邮箱 '%s': ", email)
        user, err := repo.FindUserByEmail(email)
        if err != nil {
            fmt.Printf("❌ %v\n", err)
        } else {
            fmt.Printf("✅ 找到用户: %+v\n", user)
        }
    }
}

func main() {
    demonstrateDatabaseErrors()
    
    // 事务错误处理示例
    fmt.Println("\n=== 事务错误处理 ===")
    
    simulateTransaction := func() error {
        // 开始事务
        fmt.Println("开始事务...")
        
        // 第一步:创建用户
        if err := errors.New("创建用户失败"); err != nil {
            fmt.Printf("❌ 事务步骤1失败:%v\n", err)
            return fmt.Errorf("事务回滚:创建用户失败 - %w", err)
        }
        
        // 第二步:创建用户资料
        if err := errors.New("创建资料失败"); err != nil {
            fmt.Printf("❌ 事务步骤2失败:%v\n", err)
            return fmt.Errorf("事务回滚:创建资料失败 - %w", err)
        }
        
        // 第三步:发送欢迎邮件
        if err := errors.New("发送邮件失败"); err != nil {
            fmt.Printf("⚠️  非关键步骤失败:%v\n", err)
            // 非关键步骤失败,可以选择继续
        }
        
        fmt.Println("✅ 事务提交成功")
        return nil
    }
    
    if err := simulateTransaction(); err != nil {
        fmt.Printf("事务最终失败:%v\n", err)
    }
}

运行结果:

bash 复制代码
=== 数据库错误处理演示 ===
测试1 - 创建用户 map[email:test@example.com name:]: ❌ 用户名不能为空
测试2 - 创建用户 map[email: name:张三]: ❌ 邮箱不能为空
测试3 - 创建用户 map[email:duplicate@example.com name:李四]: ❌ 邮箱地址已存在:违反唯一约束
测试4 - 创建用户 map[email:valid@example.com name:王五]: ✅ 创建成功

=== 查询用户测试 ===
查询邮箱 '': ❌ 邮箱地址不能为空
查询邮箱 'notfound@example.com': ❌ 用户不存在
查询邮箱 'valid@example.com': ✅ 找到用户: map[email:valid@example.com id:1 name:张三]

=== 事务错误处理 ===
开始事务...
❌ 事务步骤1失败:创建用户失败
事务最终失败:事务回滚:创建用户失败 - 创建用户失败

6. 错误处理最佳实践

6.1 错误处理原则

go 复制代码
package main

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

// 好的错误处理实践

// 1. 及时处理错误,不要延迟
func goodErrorHandling1() error {
    data, err := readConfig()
    if err != nil {
        return fmt.Errorf("读取配置失败: %w", err)  // 立即处理
    }
    
    if err := validateConfig(data); err != nil {
        return fmt.Errorf("配置验证失败: %w", err)  // 立即处理
    }
    
    return nil
}

// 2. 提供有意义的错误信息
func goodErrorHandling2(filename string) error {
    file, err := openFile(filename)
    if err != nil {
        // 好的错误信息包含上下文
        return fmt.Errorf("无法打开配置文件 '%s': %w", filename, err)
    }
    defer file.Close()
    return nil
}

// 3. 区分不同类型的错误
func goodErrorHandling3(userID string) error {
    user, err := findUser(userID)
    if err != nil {
        // 根据错误类型采取不同行动
        switch {
        case errors.Is(err, ErrUserNotFound):
            return fmt.Errorf("用户不存在,请先注册")
        case errors.Is(err, ErrDatabaseDown):
            return fmt.Errorf("系统维护中,请稍后再试")
        default:
            return fmt.Errorf("获取用户信息失败: %w", err)
        }
    }
    
    if !user.IsActive {
        return fmt.Errorf("用户账号已被禁用")
    }
    
    return nil
}

// 4. 记录错误但不暴露内部细节
func goodErrorHandling4() {
    if err := sensitiveOperation(); err != nil {
        // 记录详细错误到日志
        log.Printf("敏感操作失败(内部错误):%+v", err)
        // 返回通用错误给用户
        fmt.Println("操作失败,请联系管理员")
    }
}

// 模拟函数
func readConfig() (interface{}, error) {
    return nil, errors.New("文件不存在")
}

func validateConfig(data interface{}) error {
    return errors.New("配置格式错误")
}

func openFile(filename string) (interface{}, error) {
    return nil, errors.New("权限被拒绝")
}

var (
    ErrUserNotFound = errors.New("用户不存在")
    ErrDatabaseDown = errors.New("数据库宕机")
)

func findUser(userID string) (interface{}, error) {
    return nil, ErrUserNotFound
}

type User struct {
    IsActive bool
}

func sensitiveOperation() error {
    return errors.New("内部数据库凭证泄露")
}

// 不好的错误处理实践

// 1. 忽略错误
func badErrorHandling1() {
    data, _ := readConfig()  // 错误被忽略
    processConfig(data)      // 可能使用nil数据
}

// 2. 错误信息不明确
func badErrorHandling2(filename string) error {
    _, err := openFile(filename)
    if err != nil {
        return err  // 错误信息太简单
    }
    return nil
}

// 3. 过度包装错误
func badErrorHandling3() error {
    err := lowLevelError()
    if err != nil {
        return fmt.Errorf("高层错误: %w", 
                fmt.Errorf("中层错误: %w", 
                    fmt.Errorf("底层错误: %w", err)))  // 过度包装
    }
    return nil
}

func lowLevelError() error {
    return errors.New("底层错误")
}

func processConfig(data interface{}) {
    // 处理配置
}

func main() {
    fmt.Println("=== 错误处理最佳实践 ===")
    
    fmt.Println("好的实践示例:")
    if err := goodErrorHandling1(); err != nil {
        fmt.Printf("❌ %v\n", err)
    }
    
    if err := goodErrorHandling2("config.json"); err != nil {
        fmt.Printf("❌ %v\n", err)
    }
    
    if err := goodErrorHandling3("user123"); err != nil {
        fmt.Printf("❌ %v\n", err)
    }
    
    goodErrorHandling4()
    
    fmt.Println("\n避免的坏实践:")
    fmt.Println("1. 忽略错误可能导致程序崩溃")
    fmt.Println("2. 不明确的错误信息难以调试")
    fmt.Println("3. 过度包装使错误链过于复杂")
    
    // 演示错误日志的最佳实践
    fmt.Println("\n=== 错误日志最佳实践 ===")
    
    logError := func(context string, err error) {
        // 开发环境:详细日志
        log.Printf("[DEBUG] %s 失败: %+v", context, err)
        
        // 生产环境:简化日志
        log.Printf("[ERROR] %s 失败,请查看详细日志", context)
    }
    
    // 模拟错误记录
    sampleErr := fmt.Errorf("数据库连接失败: %w", 
                          errors.New("网络超时"))
    logError("用户登录", sampleErr)
}

运行结果:

bash 复制代码
=== 错误处理最佳实践 ===
好的实践示例:
❌ 读取配置失败: 文件不存在
❌ 无法打开配置文件 'config.json': 权限被拒绝
❌ 用户不存在,请先注册
操作失败,请联系管理员

避免的坏实践:
1. 忽略错误可能导致程序崩溃
2. 不明确的错误信息难以调试
3. 过度包装使错误链过于复杂

=== 错误日志最佳实践 ===
2024/01/15 15:30:45 [DEBUG] 用户登录 失败: 数据库连接失败: 网络超时
2024/01/15 15:30:45 [ERROR] 用户登录 失败,请查看详细日志

7. 总结

Go语言的错误处理机制体现了其"简单、明确、实用"的设计哲学:

核心要点

  • 错误是值,应该被显式检查和处理
  • 使用error接口统一错误表示
  • 通过errors包提供现代错误处理功能
  • 合理使用错误包装和上下文信息

最佳实践

  1. 及时处理:发现错误立即处理,不要延迟
  2. 提供上下文:错误信息应该包含足够的上下文
  3. 区分错误类型:根据错误类型采取不同处理策略
  4. 记录详细日志:内部记录详细错误,对外返回适当信息
  5. 避免错误忽略:除非确实可以忽略,否则都应该处理

现代发展

  • Go 1.13+ 引入了错误包装和检查的新功能
  • errors.Is() 和 errors.As() 提供了更好的错误检查能力
  • fmt.Errorf() 的 %w 动词支持错误包装

良好的错误处理不仅能提高程序的健壮性,还能大大改善用户体验和系统的可维护性。在实际开发中,应该根据具体场景选择合适的错误处理策略。


上一篇:Golang学习历程【第十一篇 接口(interface)】

下一篇:Golang学习历程【第十三篇 并发入门:goroutine + channel 基础】

相关推荐
Cinema KI2 小时前
C++11(中):可变参数模板将成为重中之重
开发语言·c++
£漫步 云端彡2 小时前
Golang学习历程【第九篇 结构体(struct)】
学习·golang·xcode
川西胖墩墩2 小时前
新手在线画泳道图PC端简单操作快速做出标准化流程图表
学习·流程图·敏捷流程
凯子坚持 c2 小时前
C++基于微服务脚手架的视频点播系统---客户端(2)
开发语言·c++·微服务
Vivienne_ChenW2 小时前
Spring 事件驱动用法总结
java·开发语言·spring boot·spring
Beginner x_u2 小时前
JavaScript 中浅拷贝与深拷贝的差异与实现方式整理
开发语言·javascript·浅拷贝·深拷贝
柯一梦2 小时前
STL2--vector的介绍以及使用
开发语言·c++
saoys2 小时前
Opencv 学习笔记:提取轮廓中心点坐标(矩计算法)
笔记·opencv·学习
云霄IT2 小时前
go语言post请求遭遇403反爬解决tls/ja3指纹或Cloudflare防护
开发语言·后端·golang