Go学习笔记-Error Handling

Go语言的错误处理采用显式的错误返回值方式,而不是异常机制。这种设计使得错误处理更加明确和可控,是Go语言的重要特色之一。

基本错误处理

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
package main

import (
    "errors"
    "fmt"
    "strconv"
)

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

// 2. 使用fmt.Errorf创建格式化错误
func validateAge(age int) error {
    if age < 0 {
        return fmt.Errorf("年龄不能为负数,得到: %d", age)
    }
    if age > 150 {
        return fmt.Errorf("年龄不能超过150岁,得到: %d", age)
    }
    return nil
}

// 3. 多返回值的错误处理
func parseAndValidate(s string) (int, error) {
    // 先尝试解析
    num, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("解析失败: %w", err)
    }
    
    // 再进行验证
    if err := validateAge(num); err != nil {
        return 0, fmt.Errorf("验证失败: %w", err)
    }
    
    return num, nil
}

// 4. 错误检查的最佳实践
func processFile(filename string) error {
    // 早期返回模式
    if filename == "" {
        return errors.New("文件名不能为空")
    }
    
    // 模拟文件操作
    if filename == "nonexistent.txt" {
        return fmt.Errorf("文件不存在: %s", filename)
    }
    
    fmt.Printf("成功处理文件: %s\n", filename)
    return nil
}

func main() {
    // 1. 基本错误处理
    fmt.Println("=== 基本错误处理 ===")
    
    result, err := divide(10, 2)
    if err != nil {
        fmt.Printf("错误: %v\n", err)
    } else {
        fmt.Printf("10 / 2 = %.2f\n", result)
    }
    
    result, err = divide(10, 0)
    if err != nil {
        fmt.Printf("错误: %v\n", err)
    } else {
        fmt.Printf("10 / 0 = %.2f\n", result)
    }
    
    // 2. 格式化错误
    fmt.Println("\n=== 格式化错误 ===")
    
    ages := []int{25, -5, 200}
    for _, age := range ages {
        if err := validateAge(age); err != nil {
            fmt.Printf("验证失败: %v\n", err)
        } else {
            fmt.Printf("年龄 %d 验证通过\n", age)
        }
    }
    
    // 3. 错误包装和解包
    fmt.Println("\n=== 错误包装 ===")
    
    inputs := []string{"25", "abc", "-10", "200"}
    for _, input := range inputs {
        num, err := parseAndValidate(input)
        if err != nil {
            fmt.Printf("处理 '%s' 失败: %v\n", input, err)
        } else {
            fmt.Printf("处理 '%s' 成功: %d\n", input, num)
        }
    }
    
    // 4. 文件处理错误
    fmt.Println("\n=== 文件处理错误 ===")
    
    files := []string{"data.txt", "", "nonexistent.txt"}
    for _, file := range files {
        if err := processFile(file); err != nil {
            fmt.Printf("处理失败: %v\n", err)
        }
    }
}

自定义错误类型

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
package main

import (
    "fmt"
    "time"
)

// 1. 实现error接口的自定义错误
type ValidationError struct {
    Field   string
    Value   interface{}
    Message string
}

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

// 2. 带错误码的错误类型
type APIError struct {
    Code    int
    Message string
    Details map[string]interface{}
}

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

func (e APIError) GetCode() int {
    return e.Code
}

func (e APIError) GetDetails() map[string]interface{} {
    return e.Details
}

// 3. 临时错误接口
type TemporaryError interface {
    error
    Temporary() bool
}

type NetworkError struct {
    Op        string
    Addr      string
    Err       error
    Temporary bool
}

func (e NetworkError) Error() string {
    return fmt.Sprintf("网络错误 [%s %s]: %v", e.Op, e.Addr, e.Err)
}

func (e NetworkError) Temporary() bool {
    return e.Temporary
}

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

// 4. 错误分类
type ErrorType int

const (
    ErrorTypeValidation ErrorType = iota
    ErrorTypeNetwork
    ErrorTypeDatabase
    ErrorTypePermission
    ErrorTypeNotFound
)

type ClassifiedError struct {
    Type    ErrorType
    Message string
    Cause   error
}

func (e ClassifiedError) Error() string {
    typeNames := map[ErrorType]string{
        ErrorTypeValidation: "验证错误",
        ErrorTypeNetwork:    "网络错误",
        ErrorTypeDatabase:   "数据库错误",
        ErrorTypePermission: "权限错误",
        ErrorTypeNotFound:   "未找到",
    }
    
    typeName := typeNames[e.Type]
    if e.Cause != nil {
        return fmt.Sprintf("%s: %s (原因: %v)", typeName, e.Message, e.Cause)
    }
    return fmt.Sprintf("%s: %s", typeName, e.Message)
}

func (e ClassifiedError) Unwrap() error {
    return e.Cause
}

// 5. 使用自定义错误的函数
func validateUser(name string, age int, email string) error {
    if name == "" {
        return ValidationError{
            Field:   "name",
            Value:   name,
            Message: "姓名不能为空",
        }
    }
    
    if age < 0 || age > 150 {
        return ValidationError{
            Field:   "age",
            Value:   age,
            Message: "年龄必须在0-150之间",
        }
    }
    
    if email == "" {
        return ValidationError{
            Field:   "email",
            Value:   email,
            Message: "邮箱不能为空",
        }
    }
    
    return nil
}

func callAPI(endpoint string) error {
    // 模拟API调用
    switch endpoint {
    case "/users":
        return nil
    case "/forbidden":
        return APIError{
            Code:    403,
            Message: "访问被禁止",
            Details: map[string]interface{}{
                "endpoint": endpoint,
                "time":     time.Now(),
            },
        }
    case "/notfound":
        return APIError{
            Code:    404,
            Message: "资源未找到",
            Details: map[string]interface{}{
                "endpoint": endpoint,
            },
        }
    default:
        return APIError{
            Code:    500,
            Message: "内部服务器错误",
            Details: map[string]interface{}{
                "endpoint": endpoint,
                "error":    "未知端点",
            },
        }
    }
}

func connectToServer(addr string) error {
    // 模拟网络连接
    if addr == "unreachable.com" {
        return NetworkError{
            Op:        "connect",
            Addr:      addr,
            Err:       fmt.Errorf("连接超时"),
            Temporary: true,
        }
    }
    
    if addr == "invalid.com" {
        return NetworkError{
            Op:        "connect",
            Addr:      addr,
            Err:       fmt.Errorf("无效地址"),
            Temporary: false,
        }
    }
    
    return nil
}

func main() {
    // 1. 自定义验证错误
    fmt.Println("=== 自定义验证错误 ===")
    
    users := []struct {
        name  string
        age   int
        email string
    }{
        {"张三", 25, "zhangsan@example.com"},
        {"", 30, "test@example.com"},
        {"李四", -5, "lisi@example.com"},
        {"王五", 25, ""},
    }
    
    for _, user := range users {
        if err := validateUser(user.name, user.age, user.email); err != nil {
            fmt.Printf("用户验证失败: %v\n", err)
            
            // 类型断言获取详细信息
            if validationErr, ok := err.(ValidationError); ok {
                fmt.Printf("  字段: %s, 值: %v\n", validationErr.Field, validationErr.Value)
            }
        } else {
            fmt.Printf("用户 %s 验证通过\n", user.name)
        }
    }
    
    // 2. API错误处理
    fmt.Println("\n=== API错误处理 ===")
    
    endpoints := []string{"/users", "/forbidden", "/notfound", "/unknown"}
    for _, endpoint := range endpoints {
        if err := callAPI(endpoint); err != nil {
            fmt.Printf("API调用失败: %v\n", err)
            
            // 类型断言获取错误码和详情
            if apiErr, ok := err.(APIError); ok {
                fmt.Printf("  错误码: %d\n", apiErr.GetCode())
                fmt.Printf("  详情: %v\n", apiErr.GetDetails())
            }
        } else {
            fmt.Printf("API调用成功: %s\n", endpoint)
        }
    }
    
    // 3. 网络错误和临时错误
    fmt.Println("\n=== 网络错误处理 ===")
    
    addresses := []string{"google.com", "unreachable.com", "invalid.com"}
    for _, addr := range addresses {
        if err := connectToServer(addr); err != nil {
            fmt.Printf("连接失败: %v\n", err)
            
            // 检查是否是临时错误
            if tempErr, ok := err.(TemporaryError); ok {
                if tempErr.Temporary() {
                    fmt.Printf("  这是临时错误,可以重试\n")
                } else {
                    fmt.Printf("  这是永久错误,不应重试\n")
                }
            }
        } else {
            fmt.Printf("连接成功: %s\n", addr)
        }
    }
    
    // 4. 错误分类
    fmt.Println("\n=== 错误分类 ===")
    
    classifiedErrors := []ClassifiedError{
        {
            Type:    ErrorTypeValidation,
            Message: "输入数据无效",
        },
        {
            Type:    ErrorTypeNetwork,
            Message: "网络连接失败",
            Cause:   fmt.Errorf("连接超时"),
        },
        {
            Type:    ErrorTypeDatabase,
            Message: "数据库查询失败",
            Cause:   fmt.Errorf("表不存在"),
        },
    }
    
    for _, err := range classifiedErrors {
        fmt.Printf("分类错误: %v\n", err)
    }
}

错误包装和解包

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package main

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

// 1. 错误包装示例
func readConfig(filename string) ([]byte, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        // 使用fmt.Errorf和%w动词包装错误
        return nil, fmt.Errorf("读取配置文件失败: %w", err)
    }
    return data, nil
}

func parseConfig(data []byte) (map[string]string, error) {
    if len(data) == 0 {
        return nil, errors.New("配置数据为空")
    }
    
    // 简单的解析逻辑(实际应用中可能使用JSON/YAML等)
    config := make(map[string]string)
    config["parsed"] = "true"
    return config, nil
}

func loadConfig(filename string) (map[string]string, error) {
    data, err := readConfig(filename)
    if err != nil {
        return nil, fmt.Errorf("加载配置失败: %w", err)
    }
    
    config, err := parseConfig(data)
    if err != nil {
        return nil, fmt.Errorf("解析配置失败: %w", err)
    }
    
    return config, nil
}

// 2. 自定义错误包装
type ConfigError struct {
    Filename string
    Err      error
}

func (e ConfigError) Error() string {
    return fmt.Sprintf("配置错误 [%s]: %v", e.Filename, e.Err)
}

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

func loadConfigWithCustomError(filename string) (map[string]string, error) {
    data, err := readConfig(filename)
    if err != nil {
        return nil, ConfigError{
            Filename: filename,
            Err:      err,
        }
    }
    
    config, err := parseConfig(data)
    if err != nil {
        return nil, ConfigError{
            Filename: filename,
            Err:      err,
        }
    }
    
    return config, nil
}

// 3. 多层错误包装
type DatabaseError struct {
    Operation string
    Table     string
    Err       error
}

func (e DatabaseError) Error() string {
    return fmt.Sprintf("数据库错误 [%s:%s]: %v", e.Operation, e.Table, e.Err)
}

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

type ServiceError struct {
    Service string
    Err     error
}

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

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

func queryDatabase(table string) error {
    // 模拟数据库错误
    if table == "nonexistent" {
        return DatabaseError{
            Operation: "SELECT",
            Table:     table,
            Err:       errors.New("表不存在"),
        }
    }
    return nil
}

func userService(action string) error {
    err := queryDatabase("users")
    if err != nil {
        return ServiceError{
            Service: "UserService",
            Err:     err,
        }
    }
    return nil
}

func main() {
    // 1. 基本错误包装和解包
    fmt.Println("=== 错误包装和解包 ===")
    
    _, err := loadConfig("nonexistent.conf")
    if err != nil {
        fmt.Printf("错误: %v\n", err)
        
        // 使用errors.Unwrap解包错误
        fmt.Println("\n错误链:")
        for i := 0; err != nil; i++ {
            fmt.Printf("  %d: %v\n", i, err)
            err = errors.Unwrap(err)
        }
    }
    
    // 2. 使用errors.Is检查特定错误
    fmt.Println("\n=== 使用errors.Is ===")
    
    _, err = loadConfig("nonexistent.conf")
    if err != nil {
        // 检查错误链中是否包含特定错误
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("文件不存在错误")
        }
        
        // 检查是否是权限错误
        if errors.Is(err, os.ErrPermission) {
            fmt.Println("权限错误")
        }
    }
    
    // 3. 使用errors.As进行类型断言
    fmt.Println("\n=== 使用errors.As ===")
    
    _, err = loadConfigWithCustomError("nonexistent.conf")
    if err != nil {
        // 使用errors.As查找错误链中的特定类型
        var configErr ConfigError
        if errors.As(err, &configErr) {
            fmt.Printf("配置错误,文件名: %s\n", configErr.Filename)
        }
        
        // 查找系统错误
        var pathErr *os.PathError
        if errors.As(err, &pathErr) {
            fmt.Printf("路径错误: %s\n", pathErr.Path)
        }
    }
    
    // 4. 多层错误包装
    fmt.Println("\n=== 多层错误包装 ===")
    
    err = userService("create")
    if err != nil {
        fmt.Printf("顶层错误: %v\n", err)
        
        // 逐层解包
        fmt.Println("\n错误链分析:")
        current := err
        level := 0
        for current != nil {
            fmt.Printf("  级别 %d: %T - %v\n", level, current, current)
            
            // 检查特定类型
            switch e := current.(type) {
            case ServiceError:
                fmt.Printf("    服务: %s\n", e.Service)
            case DatabaseError:
                fmt.Printf("    操作: %s, 表: %s\n", e.Operation, e.Table)
            }
            
            current = errors.Unwrap(current)
            level++
        }
    }
    
    // 5. 错误处理的最佳实践
    fmt.Println("\n=== 错误处理最佳实践 ===")
    
    demonstrateErrorHandlingPatterns()
}

func demonstrateErrorHandlingPatterns() {
    // 1. 早期返回模式
    processData := func(data []byte) error {
        if len(data) == 0 {
            return errors.New("数据为空")
        }
        
        if len(data) > 1024 {
            return errors.New("数据过大")
        }
        
        // 处理数据...
        return nil
    }
    
    // 2. 错误聚合
    var errs []error
    
    if err := processData(nil); err != nil {
        errs = append(errs, err)
    }
    
    if err := processData(make([]byte, 2048)); err != nil {
        errs = append(errs, err)
    }
    
    if len(errs) > 0 {
        fmt.Printf("发现 %d 个错误:\n", len(errs))
        for i, err := range errs {
            fmt.Printf("  %d: %v\n", i+1, err)
        }
    }
    
    // 3. 哨兵错误
    var (
        ErrInvalidInput = errors.New("无效输入")
        ErrNotFound     = errors.New("未找到")
        ErrUnauthorized = errors.New("未授权")
    )
    
    checkPermission := func(user string) error {
        if user == "" {
            return ErrInvalidInput
        }
        if user == "guest" {
            return ErrUnauthorized
        }
        if user == "unknown" {
            return ErrNotFound
        }
        return nil
    }
    
    users := []string{"admin", "", "guest", "unknown"}
    for _, user := range users {
        err := checkPermission(user)
        switch {
        case errors.Is(err, ErrInvalidInput):
            fmt.Printf("用户 '%s': 输入无效\n", user)
        case errors.Is(err, ErrUnauthorized):
            fmt.Printf("用户 '%s': 未授权\n", user)
        case errors.Is(err, ErrNotFound):
            fmt.Printf("用户 '%s': 未找到\n", user)
        case err == nil:
            fmt.Printf("用户 '%s': 权限检查通过\n", user)
        default:
            fmt.Printf("用户 '%s': 未知错误: %v\n", user, err)
        }
    }
    
    // 4. 错误恢复
    recoverableOperation := func() (result string, err error) {
        defer func() {
            if r := recover(); r != nil {
                err = fmt.Errorf("操作panic: %v", r)
            }
        }()
        
        // 可能panic的操作
        panic("模拟panic")
        
        return "success", nil
    }
    
    result, err := recoverableOperation()
    if err != nil {
        fmt.Printf("恢复的错误: %v\n", err)
    } else {
        fmt.Printf("操作成功: %s\n", result)
    }
}

总结

  1. 错误处理原则

    • 错误是值,不是异常
    • 显式处理每个错误
    • 使用多返回值传递错误
    • 错误应该被处理或传播
  2. 错误创建

    • errors.New() - 创建简单错误
    • fmt.Errorf() - 创建格式化错误
    • 自定义错误类型 - 实现error接口
  3. 错误包装

    • 使用fmt.Errorf("context: %w", err)包装错误
    • 实现Unwrap() error方法
    • 保持错误链的完整性
  4. 错误检查

    • errors.Is() - 检查错误链中的特定错误
    • errors.As() - 类型断言错误链中的类型
    • errors.Unwrap() - 解包错误
  5. 最佳实践

    • 早期返回模式
    • 哨兵错误用于预定义错误
    • 自定义错误类型提供更多信息
    • 合理的错误包装和上下文
    • 在适当的层级处理错误
updatedupdated2025-09-202025-09-20