958 lines
24 KiB
Go
958 lines
24 KiB
Go
/*
|
||
01-basic-errors.go - Go 语言基础错误处理详解
|
||
|
||
学习目标:
|
||
1. 理解 Go 语言的错误处理哲学
|
||
2. 掌握 error 接口的使用
|
||
3. 学会基本的错误处理模式
|
||
4. 了解错误传播和包装
|
||
5. 掌握错误处理的最佳实践
|
||
|
||
知识点:
|
||
- error 接口的定义和实现
|
||
- 错误的创建和返回
|
||
- 错误检查和处理
|
||
- 错误传播和包装
|
||
- 错误处理的惯用法
|
||
- 错误信息的设计
|
||
*/
|
||
|
||
package main
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"os"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
func main() {
|
||
fmt.Println("=== Go 语言基础错误处理详解 ===\\n")
|
||
|
||
// 演示错误接口的基本概念
|
||
demonstrateErrorInterface()
|
||
|
||
// 演示错误的创建方式
|
||
demonstrateErrorCreation()
|
||
|
||
// 演示错误检查和处理
|
||
demonstrateErrorHandling()
|
||
|
||
// 演示错误传播
|
||
demonstrateErrorPropagation()
|
||
|
||
// 演示错误包装
|
||
demonstrateErrorWrapping()
|
||
|
||
// 演示错误处理模式
|
||
demonstrateErrorPatterns()
|
||
|
||
// 演示实际应用场景
|
||
demonstratePracticalExamples()
|
||
}
|
||
|
||
// demonstrateErrorInterface 演示错误接口的基本概念
|
||
func demonstrateErrorInterface() {
|
||
fmt.Println("1. 错误接口的基本概念:")
|
||
|
||
// error 接口的定义
|
||
fmt.Printf(" error 接口的定义:\\n")
|
||
fmt.Printf(" type error interface {\\n")
|
||
fmt.Printf(" Error() string\\n")
|
||
fmt.Printf(" }\\n")
|
||
fmt.Printf("\\n")
|
||
fmt.Printf(" 错误处理的特点:\\n")
|
||
fmt.Printf(" - 错误是值,可以像其他值一样传递\\n")
|
||
fmt.Printf(" - 使用多返回值模式处理错误\\n")
|
||
fmt.Printf(" - 显式错误检查,不隐藏错误\\n")
|
||
fmt.Printf(" - 错误应该被处理,不应该被忽略\\n")
|
||
fmt.Printf(" - 错误信息应该清晰、有用\\n")
|
||
|
||
// 基本错误示例
|
||
fmt.Printf(" 基本错误示例:\\n")
|
||
|
||
// 成功的情况
|
||
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)
|
||
}
|
||
|
||
// nil 错误检查
|
||
fmt.Printf(" nil 错误检查:\\n")
|
||
|
||
var nilError error
|
||
fmt.Printf(" nil 错误: %v\\n", nilError)
|
||
fmt.Printf(" nil 错误 == nil: %t\\n", nilError == nil)
|
||
|
||
// 错误比较
|
||
fmt.Printf(" 错误比较:\\n")
|
||
|
||
err1 := errors.New("相同的错误")
|
||
err2 := errors.New("相同的错误")
|
||
fmt.Printf(" err1 == err2: %t (内容相同但不是同一个对象)\\n", err1 == err2)
|
||
fmt.Printf(" err1.Error() == err2.Error(): %t\\n", err1.Error() == err2.Error())
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateErrorCreation 演示错误的创建方式
|
||
func demonstrateErrorCreation() {
|
||
fmt.Println("2. 错误的创建方式:")
|
||
|
||
// 使用 errors.New 创建错误
|
||
fmt.Printf(" 使用 errors.New 创建错误:\\n")
|
||
|
||
err1 := errors.New("这是一个简单的错误")
|
||
fmt.Printf(" 错误1: %v\\n", err1)
|
||
|
||
// 使用 fmt.Errorf 创建格式化错误
|
||
fmt.Printf(" 使用 fmt.Errorf 创建格式化错误:\\n")
|
||
|
||
username := "alice"
|
||
err2 := fmt.Errorf("用户 %s 不存在", username)
|
||
fmt.Printf(" 错误2: %v\\n", err2)
|
||
|
||
// 创建带有更多信息的错误
|
||
age := -5
|
||
err3 := fmt.Errorf("无效的年龄值: %d (年龄必须大于0)", age)
|
||
fmt.Printf(" 错误3: %v\\n", err3)
|
||
|
||
// 使用预定义错误
|
||
fmt.Printf(" 使用预定义错误:\\n")
|
||
|
||
predefinedErrors := []error{
|
||
ErrInvalidInput,
|
||
ErrNotFound,
|
||
ErrPermissionDenied,
|
||
ErrTimeout,
|
||
}
|
||
|
||
for i, err := range predefinedErrors {
|
||
fmt.Printf(" 预定义错误%d: %v\\n", i+1, err)
|
||
}
|
||
|
||
// 条件性错误创建
|
||
fmt.Printf(" 条件性错误创建:\\n")
|
||
|
||
testValues := []int{-1, 0, 5, 101}
|
||
for _, value := range testValues {
|
||
if err := validateAge(value); err != nil {
|
||
fmt.Printf(" 验证年龄 %d: %v\\n", value, err)
|
||
} else {
|
||
fmt.Printf(" 验证年龄 %d: 有效\\n", value)
|
||
}
|
||
}
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateErrorHandling 演示错误检查和处理
|
||
func demonstrateErrorHandling() {
|
||
fmt.Println("3. 错误检查和处理:")
|
||
|
||
// 基本错误检查模式
|
||
fmt.Printf(" 基本错误检查模式:\\n")
|
||
|
||
// 立即检查错误
|
||
content, err := readFile("example.txt")
|
||
if err != nil {
|
||
fmt.Printf(" 读取文件失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 文件内容: %s\\n", content)
|
||
}
|
||
|
||
// 多个操作的错误处理
|
||
fmt.Printf(" 多个操作的错误处理:\\n")
|
||
|
||
err = performMultipleOperations()
|
||
if err != nil {
|
||
fmt.Printf(" 多个操作失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 所有操作成功完成\\n")
|
||
}
|
||
|
||
// 错误类型检查
|
||
fmt.Printf(" 错误类型检查:\\n")
|
||
|
||
testOperations := []func() error{
|
||
func() error { return ErrNotFound },
|
||
func() error { return ErrPermissionDenied },
|
||
func() error { return fmt.Errorf("未知错误") },
|
||
func() error { return nil },
|
||
}
|
||
|
||
for i, op := range testOperations {
|
||
err := op()
|
||
fmt.Printf(" 操作%d: ", i+1)
|
||
handleSpecificError(err)
|
||
}
|
||
|
||
// 错误恢复
|
||
fmt.Printf(" 错误恢复:\\n")
|
||
|
||
data, err := fetchDataWithRetry("https://api.example.com/data", 3)
|
||
if err != nil {
|
||
fmt.Printf(" 获取数据失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 获取数据成功: %s\\n", data)
|
||
}
|
||
|
||
// 部分失败处理
|
||
fmt.Printf(" 部分失败处理:\\n")
|
||
|
||
results, errors := processBatch([]string{"item1", "item2", "invalid", "item4"})
|
||
fmt.Printf(" 成功处理: %v\\n", results)
|
||
fmt.Printf(" 处理错误: %v\\n", errors)
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateErrorPropagation 演示错误传播
|
||
func demonstrateErrorPropagation() {
|
||
fmt.Println("4. 错误传播:")
|
||
|
||
// 简单错误传播
|
||
fmt.Printf(" 简单错误传播:\\n")
|
||
|
||
result, err := calculateTotal([]float64{10.5, 20.0, -5.0, 15.5})
|
||
if err != nil {
|
||
fmt.Printf(" 计算总和失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 总和: %.2f\\n", result)
|
||
}
|
||
|
||
// 带上下文的错误传播
|
||
fmt.Printf(" 带上下文的错误传播:\\n")
|
||
|
||
user, err := getUserProfile("nonexistent_user")
|
||
if err != nil {
|
||
fmt.Printf(" 获取用户资料失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 用户资料: %+v\\n", user)
|
||
}
|
||
|
||
// 错误链
|
||
fmt.Printf(" 错误链:\\n")
|
||
|
||
err = processOrder("invalid_order_id")
|
||
if err != nil {
|
||
fmt.Printf(" 处理订单失败: %v\\n", err)
|
||
|
||
// 展示错误链
|
||
fmt.Printf(" 错误链分析:\\n")
|
||
printErrorChain(err, " ")
|
||
}
|
||
|
||
// 选择性错误传播
|
||
fmt.Printf(" 选择性错误传播:\\n")
|
||
|
||
results := []string{}
|
||
errors := []error{}
|
||
|
||
items := []string{"valid1", "invalid", "valid2", "error", "valid3"}
|
||
for _, item := range items {
|
||
result, err := processItem(item)
|
||
if err != nil {
|
||
errors = append(errors, fmt.Errorf("处理 %s 失败: %w", item, err))
|
||
} else {
|
||
results = append(results, result)
|
||
}
|
||
}
|
||
|
||
fmt.Printf(" 成功结果: %v\\n", results)
|
||
if len(errors) > 0 {
|
||
fmt.Printf(" 错误列表:\\n")
|
||
for _, err := range errors {
|
||
fmt.Printf(" - %v\\n", err)
|
||
}
|
||
}
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateErrorWrapping 演示错误包装
|
||
func demonstrateErrorWrapping() {
|
||
fmt.Println("5. 错误包装:")
|
||
|
||
// 基本错误包装
|
||
fmt.Printf(" 基本错误包装:\\n")
|
||
|
||
originalErr := errors.New("原始错误")
|
||
wrappedErr := fmt.Errorf("包装错误: %w", originalErr)
|
||
|
||
fmt.Printf(" 原始错误: %v\\n", originalErr)
|
||
fmt.Printf(" 包装错误: %v\\n", wrappedErr)
|
||
|
||
// 错误解包
|
||
fmt.Printf(" 错误解包:\\n")
|
||
|
||
unwrappedErr := errors.Unwrap(wrappedErr)
|
||
fmt.Printf(" 解包后的错误: %v\\n", unwrappedErr)
|
||
fmt.Printf(" 解包后是否为原始错误: %t\\n", unwrappedErr == originalErr)
|
||
|
||
// 错误检查 (errors.Is)
|
||
fmt.Printf(" 错误检查 (errors.Is):\\n")
|
||
|
||
fmt.Printf(" wrappedErr 是否为 originalErr: %t\\n", errors.Is(wrappedErr, originalErr))
|
||
fmt.Printf(" wrappedErr 是否为 ErrNotFound: %t\\n", errors.Is(wrappedErr, ErrNotFound))
|
||
|
||
// 多层包装
|
||
fmt.Printf(" 多层包装:\\n")
|
||
|
||
baseErr := ErrNotFound
|
||
level1Err := fmt.Errorf("数据库查询失败: %w", baseErr)
|
||
level2Err := fmt.Errorf("用户服务错误: %w", level1Err)
|
||
level3Err := fmt.Errorf("API 请求失败: %w", level2Err)
|
||
|
||
fmt.Printf(" 多层包装错误: %v\\n", level3Err)
|
||
fmt.Printf(" 是否包含 ErrNotFound: %t\\n", errors.Is(level3Err, ErrNotFound))
|
||
|
||
// 错误类型断言 (errors.As)
|
||
fmt.Printf(" 错误类型断言 (errors.As):\\n")
|
||
|
||
customErr := &CustomError{
|
||
Code: 404,
|
||
Message: "资源未找到",
|
||
Details: "请求的用户ID不存在",
|
||
}
|
||
|
||
wrappedCustomErr := fmt.Errorf("处理请求失败: %w", customErr)
|
||
|
||
var targetErr *CustomError
|
||
if errors.As(wrappedCustomErr, &targetErr) {
|
||
fmt.Printf(" 找到自定义错误: Code=%d, Message=%s\\n",
|
||
targetErr.Code, targetErr.Message)
|
||
}
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstrateErrorPatterns 演示错误处理模式
|
||
func demonstrateErrorPatterns() {
|
||
fmt.Println("6. 错误处理模式:")
|
||
|
||
// 哨兵错误模式
|
||
fmt.Printf(" 哨兵错误模式:\\n")
|
||
|
||
_, err := findUser("nonexistent")
|
||
if errors.Is(err, ErrUserNotFound) {
|
||
fmt.Printf(" 用户不存在,使用默认用户\\n")
|
||
} else if err != nil {
|
||
fmt.Printf(" 查找用户时发生其他错误: %v\\n", err)
|
||
}
|
||
|
||
// 错误类型模式
|
||
fmt.Printf(" 错误类型模式:\\n")
|
||
|
||
err = performNetworkOperation()
|
||
var netErr *NetworkError
|
||
if errors.As(err, &netErr) {
|
||
if netErr.Temporary {
|
||
fmt.Printf(" 临时网络错误,可以重试: %v\\n", netErr)
|
||
} else {
|
||
fmt.Printf(" 永久网络错误: %v\\n", netErr)
|
||
}
|
||
}
|
||
|
||
// 不透明错误模式
|
||
fmt.Printf(" 不透明错误模式:\\n")
|
||
|
||
err = callExternalAPI()
|
||
if err != nil {
|
||
fmt.Printf(" 外部API调用失败: %v\\n", err)
|
||
// 不检查具体错误类型,统一处理
|
||
}
|
||
|
||
// 错误聚合模式
|
||
fmt.Printf(" 错误聚合模式:\\n")
|
||
|
||
validator := NewValidator()
|
||
validator.ValidateRequired("name", "")
|
||
validator.ValidateEmail("email", "invalid-email")
|
||
validator.ValidateRange("age", -5, 0, 120)
|
||
|
||
if validator.HasErrors() {
|
||
fmt.Printf(" 验证失败:\\n")
|
||
for _, err := range validator.Errors() {
|
||
fmt.Printf(" - %v\\n", err)
|
||
}
|
||
}
|
||
|
||
// 错误恢复模式
|
||
fmt.Printf(" 错误恢复模式:\\n")
|
||
|
||
result := performWithFallback()
|
||
fmt.Printf(" 操作结果: %s\\n", result)
|
||
|
||
// 错误重试模式
|
||
fmt.Printf(" 错误重试模式:\\n")
|
||
|
||
data, err := retryOperation(func() (string, error) {
|
||
// 模拟不稳定的操作
|
||
if len(fmt.Sprintf("%d", rand.Int()))%3 == 0 {
|
||
return "成功数据", nil
|
||
}
|
||
return "", errors.New("临时失败")
|
||
}, 3)
|
||
|
||
if err != nil {
|
||
fmt.Printf(" 重试操作最终失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 重试操作成功: %s\\n", data)
|
||
}
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// demonstratePracticalExamples 演示实际应用场景
|
||
func demonstratePracticalExamples() {
|
||
fmt.Println("7. 实际应用场景:")
|
||
|
||
// 文件操作错误处理
|
||
fmt.Printf(" 文件操作错误处理:\\n")
|
||
|
||
content, err := safeReadFile("config.json")
|
||
if err != nil {
|
||
fmt.Printf(" 读取配置文件失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 配置内容: %s\\n", content)
|
||
}
|
||
|
||
// 网络请求错误处理
|
||
fmt.Printf(" 网络请求错误处理:\\n")
|
||
|
||
response, err := httpGet("https://api.example.com/users/1")
|
||
if err != nil {
|
||
fmt.Printf(" HTTP请求失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" HTTP响应: %s\\n", response)
|
||
}
|
||
|
||
// 数据库操作错误处理
|
||
fmt.Printf(" 数据库操作错误处理:\\n")
|
||
|
||
user := User{ID: 1, Name: "Alice", Email: "alice@example.com"}
|
||
err = saveUser(user)
|
||
if err != nil {
|
||
fmt.Printf(" 保存用户失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 用户保存成功\\n")
|
||
}
|
||
|
||
// JSON 解析错误处理
|
||
fmt.Printf(" JSON 解析错误处理:\\n")
|
||
|
||
jsonData := `{"name": "Bob", "age": "invalid"}`
|
||
user, err = parseUser(jsonData)
|
||
if err != nil {
|
||
fmt.Printf(" JSON解析失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 解析用户: %+v\\n", user)
|
||
}
|
||
|
||
// 业务逻辑错误处理
|
||
fmt.Printf(" 业务逻辑错误处理:\\n")
|
||
|
||
account := BankAccount{Balance: 100.0}
|
||
err = account.Withdraw(150.0)
|
||
if err != nil {
|
||
fmt.Printf(" 取款失败: %v\\n", err)
|
||
} else {
|
||
fmt.Printf(" 取款成功,余额: %.2f\\n", account.Balance)
|
||
}
|
||
|
||
// 并发错误处理
|
||
fmt.Printf(" 并发错误处理:\\n")
|
||
|
||
results, errors := processConcurrently([]string{"task1", "task2", "error_task", "task4"})
|
||
fmt.Printf(" 并发处理结果: %v\\n", results)
|
||
if len(errors) > 0 {
|
||
fmt.Printf(" 并发处理错误:\\n")
|
||
for i, err := range errors {
|
||
if err != nil {
|
||
fmt.Printf(" 任务%d错误: %v\\n", i, err)
|
||
}
|
||
}
|
||
}
|
||
|
||
fmt.Println()
|
||
}
|
||
|
||
// ========== 类型定义和辅助函数 ==========
|
||
|
||
// 预定义错误
|
||
var (
|
||
ErrInvalidInput = errors.New("输入无效")
|
||
ErrNotFound = errors.New("未找到")
|
||
ErrPermissionDenied = errors.New("权限被拒绝")
|
||
ErrTimeout = errors.New("操作超时")
|
||
ErrUserNotFound = errors.New("用户未找到")
|
||
)
|
||
|
||
// CustomError 自定义错误类型
|
||
type CustomError struct {
|
||
Code int
|
||
Message string
|
||
Details string
|
||
}
|
||
|
||
func (e *CustomError) Error() string {
|
||
return fmt.Sprintf("错误 %d: %s (%s)", e.Code, e.Message, e.Details)
|
||
}
|
||
|
||
// NetworkError 网络错误类型
|
||
type NetworkError struct {
|
||
Op string
|
||
URL string
|
||
Temporary bool
|
||
Err error
|
||
}
|
||
|
||
func (e *NetworkError) Error() string {
|
||
return fmt.Sprintf("网络错误 %s %s: %v", e.Op, e.URL, e.Err)
|
||
}
|
||
|
||
// User 用户结构
|
||
type User struct {
|
||
ID int `json:"id"`
|
||
Name string `json:"name"`
|
||
Email string `json:"email"`
|
||
Age int `json:"age"`
|
||
}
|
||
|
||
// BankAccount 银行账户
|
||
type BankAccount struct {
|
||
Balance float64
|
||
}
|
||
|
||
func (ba *BankAccount) Withdraw(amount float64) error {
|
||
if amount <= 0 {
|
||
return fmt.Errorf("取款金额必须大于0,实际: %.2f", amount)
|
||
}
|
||
if amount > ba.Balance {
|
||
return fmt.Errorf("余额不足: 需要 %.2f,可用 %.2f", amount, ba.Balance)
|
||
}
|
||
ba.Balance -= amount
|
||
return nil
|
||
}
|
||
|
||
// Validator 验证器
|
||
type Validator struct {
|
||
errors []error
|
||
}
|
||
|
||
func NewValidator() *Validator {
|
||
return &Validator{errors: make([]error, 0)}
|
||
}
|
||
|
||
func (v *Validator) ValidateRequired(field, value string) {
|
||
if value == "" {
|
||
v.errors = append(v.errors, fmt.Errorf("字段 %s 是必需的", field))
|
||
}
|
||
}
|
||
|
||
func (v *Validator) ValidateEmail(field, value string) {
|
||
if value != "" && !strings.Contains(value, "@") {
|
||
v.errors = append(v.errors, fmt.Errorf("字段 %s 不是有效的邮箱地址", field))
|
||
}
|
||
}
|
||
|
||
func (v *Validator) ValidateRange(field string, value, min, max int) {
|
||
if value < min || value > max {
|
||
v.errors = append(v.errors, fmt.Errorf("字段 %s 必须在 %d 到 %d 之间,实际: %d", field, min, max, value))
|
||
}
|
||
}
|
||
|
||
func (v *Validator) HasErrors() bool {
|
||
return len(v.errors) > 0
|
||
}
|
||
|
||
func (v *Validator) Errors() []error {
|
||
return v.errors
|
||
}
|
||
|
||
// ========== 辅助函数 ==========
|
||
|
||
// divide 除法运算
|
||
func divide(a, b float64) (float64, error) {
|
||
if b == 0 {
|
||
return 0, errors.New("除数不能为零")
|
||
}
|
||
return a / b, nil
|
||
}
|
||
|
||
// validateAge 验证年龄
|
||
func validateAge(age int) error {
|
||
if age < 0 {
|
||
return fmt.Errorf("年龄不能为负数: %d", age)
|
||
}
|
||
if age > 150 {
|
||
return fmt.Errorf("年龄不能超过150: %d", age)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// readFile 读取文件
|
||
func readFile(filename string) (string, error) {
|
||
// 模拟文件读取
|
||
if filename == "example.txt" {
|
||
return "", fmt.Errorf("文件 %s 不存在", filename)
|
||
}
|
||
return "文件内容", nil
|
||
}
|
||
|
||
// performMultipleOperations 执行多个操作
|
||
func performMultipleOperations() error {
|
||
// 模拟多个操作
|
||
operations := []func() error{
|
||
func() error { return nil }, // 成功
|
||
func() error { return nil }, // 成功
|
||
func() error { return errors.New("第三个操作失败") }, // 失败
|
||
}
|
||
|
||
for i, op := range operations {
|
||
if err := op(); err != nil {
|
||
return fmt.Errorf("操作 %d 失败: %w", i+1, err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// handleSpecificError 处理特定错误
|
||
func handleSpecificError(err error) {
|
||
if err == nil {
|
||
fmt.Printf("成功\\n")
|
||
return
|
||
}
|
||
|
||
switch {
|
||
case errors.Is(err, ErrNotFound):
|
||
fmt.Printf("资源未找到\\n")
|
||
case errors.Is(err, ErrPermissionDenied):
|
||
fmt.Printf("权限不足\\n")
|
||
default:
|
||
fmt.Printf("未知错误: %v\\n", err)
|
||
}
|
||
}
|
||
|
||
// fetchDataWithRetry 带重试的数据获取
|
||
func fetchDataWithRetry(url string, maxRetries int) (string, error) {
|
||
var lastErr error
|
||
|
||
for i := 0; i < maxRetries; i++ {
|
||
// 模拟网络请求
|
||
if rand.Float32() < 0.3 { // 30% 成功率
|
||
return fmt.Sprintf("数据来自 %s", url), nil
|
||
}
|
||
|
||
lastErr = fmt.Errorf("请求 %s 失败 (尝试 %d/%d)", url, i+1, maxRetries)
|
||
time.Sleep(100 * time.Millisecond) // 重试延迟
|
||
}
|
||
|
||
return "", fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, lastErr)
|
||
}
|
||
|
||
// processBatch 批量处理
|
||
func processBatch(items []string) ([]string, []error) {
|
||
var results []string
|
||
var errors []error
|
||
|
||
for _, item := range items {
|
||
if item == "invalid" {
|
||
errors = append(errors, fmt.Errorf("无效项目: %s", item))
|
||
} else {
|
||
results = append(results, fmt.Sprintf("处理后的%s", item))
|
||
}
|
||
}
|
||
|
||
return results, errors
|
||
}
|
||
|
||
// calculateTotal 计算总和
|
||
func calculateTotal(values []float64) (float64, error) {
|
||
total := 0.0
|
||
for i, value := range values {
|
||
if value < 0 {
|
||
return 0, fmt.Errorf("索引 %d 的值不能为负数: %.2f", i, value)
|
||
}
|
||
total += value
|
||
}
|
||
return total, nil
|
||
}
|
||
|
||
// getUserProfile 获取用户资料
|
||
func getUserProfile(username string) (*User, error) {
|
||
// 模拟数据库查询
|
||
if username == "nonexistent_user" {
|
||
return nil, fmt.Errorf("获取用户资料失败: %w", ErrUserNotFound)
|
||
}
|
||
|
||
return &User{
|
||
ID: 1,
|
||
Name: username,
|
||
Email: username + "@example.com",
|
||
}, nil
|
||
}
|
||
|
||
// processOrder 处理订单
|
||
func processOrder(orderID string) error {
|
||
// 模拟订单处理链
|
||
if err := validateOrder(orderID); err != nil {
|
||
return fmt.Errorf("处理订单 %s 失败: %w", orderID, err)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// validateOrder 验证订单
|
||
func validateOrder(orderID string) error {
|
||
if orderID == "invalid_order_id" {
|
||
if err := checkOrderExists(orderID); err != nil {
|
||
return fmt.Errorf("订单验证失败: %w", err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// checkOrderExists 检查订单是否存在
|
||
func checkOrderExists(orderID string) error {
|
||
return fmt.Errorf("数据库查询失败: %w", ErrNotFound)
|
||
}
|
||
|
||
// printErrorChain 打印错误链
|
||
func printErrorChain(err error, indent string) {
|
||
if err == nil {
|
||
return
|
||
}
|
||
|
||
fmt.Printf("%s- %v\\n", indent, err)
|
||
|
||
if unwrapped := errors.Unwrap(err); unwrapped != nil {
|
||
printErrorChain(unwrapped, indent+" ")
|
||
}
|
||
}
|
||
|
||
// processItem 处理项目
|
||
func processItem(item string) (string, error) {
|
||
switch item {
|
||
case "invalid":
|
||
return "", ErrInvalidInput
|
||
case "error":
|
||
return "", errors.New("处理错误")
|
||
default:
|
||
return fmt.Sprintf("处理后的%s", item), nil
|
||
}
|
||
}
|
||
|
||
// findUser 查找用户
|
||
func findUser(username string) (*User, error) {
|
||
if username == "nonexistent" {
|
||
return nil, ErrUserNotFound
|
||
}
|
||
return &User{Name: username}, nil
|
||
}
|
||
|
||
// performNetworkOperation 执行网络操作
|
||
func performNetworkOperation() error {
|
||
return &NetworkError{
|
||
Op: "GET",
|
||
URL: "https://api.example.com",
|
||
Temporary: true,
|
||
Err: errors.New("连接超时"),
|
||
}
|
||
}
|
||
|
||
// callExternalAPI 调用外部API
|
||
func callExternalAPI() error {
|
||
return errors.New("外部服务不可用")
|
||
}
|
||
|
||
// performWithFallback 带回退的操作
|
||
func performWithFallback() string {
|
||
// 尝试主要操作
|
||
if err := primaryOperation(); err != nil {
|
||
// 主要操作失败,使用回退
|
||
return fallbackOperation()
|
||
}
|
||
return "主要操作成功"
|
||
}
|
||
|
||
// primaryOperation 主要操作
|
||
func primaryOperation() error {
|
||
return errors.New("主要操作失败")
|
||
}
|
||
|
||
// fallbackOperation 回退操作
|
||
func fallbackOperation() string {
|
||
return "回退操作成功"
|
||
}
|
||
|
||
// retryOperation 重试操作
|
||
func retryOperation(op func() (string, error), maxRetries int) (string, error) {
|
||
var lastErr error
|
||
|
||
for i := 0; i < maxRetries; i++ {
|
||
result, err := op()
|
||
if err == nil {
|
||
return result, nil
|
||
}
|
||
|
||
lastErr = err
|
||
fmt.Printf(" 尝试 %d/%d 失败: %v\\n", i+1, maxRetries, err)
|
||
|
||
if i < maxRetries-1 {
|
||
time.Sleep(100 * time.Millisecond)
|
||
}
|
||
}
|
||
|
||
return "", fmt.Errorf("重试 %d 次后失败: %w", maxRetries, lastErr)
|
||
}
|
||
|
||
// safeReadFile 安全读取文件
|
||
func safeReadFile(filename string) (string, error) {
|
||
// 模拟文件读取
|
||
switch filename {
|
||
case "config.json":
|
||
return "", fmt.Errorf("读取文件 %s 失败: %w", filename, os.ErrNotExist)
|
||
default:
|
||
return "文件内容", nil
|
||
}
|
||
}
|
||
|
||
// httpGet HTTP GET 请求
|
||
func httpGet(url string) (string, error) {
|
||
// 模拟HTTP请求
|
||
if strings.Contains(url, "example.com") {
|
||
return "", &NetworkError{
|
||
Op: "GET",
|
||
URL: url,
|
||
Temporary: false,
|
||
Err: errors.New("域名解析失败"),
|
||
}
|
||
}
|
||
return "HTTP响应内容", nil
|
||
}
|
||
|
||
// saveUser 保存用户
|
||
func saveUser(user User) error {
|
||
// 模拟数据库保存
|
||
if user.Email == "" {
|
||
return fmt.Errorf("保存用户失败: %w", ErrInvalidInput)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// parseUser 解析用户JSON
|
||
func parseUser(jsonData string) (User, error) {
|
||
var user User
|
||
if err := json.Unmarshal([]byte(jsonData), &user); err != nil {
|
||
return User{}, fmt.Errorf("解析用户JSON失败: %w", err)
|
||
}
|
||
return user, nil
|
||
}
|
||
|
||
// processConcurrently 并发处理
|
||
func processConcurrently(tasks []string) ([]string, []error) {
|
||
var wg sync.WaitGroup
|
||
results := make([]string, len(tasks))
|
||
errors := make([]error, len(tasks))
|
||
|
||
for i, task := range tasks {
|
||
wg.Add(1)
|
||
go func(index int, taskName string) {
|
||
defer wg.Done()
|
||
|
||
// 模拟任务处理
|
||
if strings.Contains(taskName, "error") {
|
||
errors[index] = fmt.Errorf("任务 %s 处理失败", taskName)
|
||
} else {
|
||
results[index] = fmt.Sprintf("处理后的%s", taskName)
|
||
}
|
||
}(i, task)
|
||
}
|
||
|
||
wg.Wait()
|
||
return results, errors
|
||
}
|
||
|
||
/*
|
||
运行这个程序:
|
||
go run 01-basic-errors.go
|
||
|
||
学习要点:
|
||
1. Go 语言使用显式错误处理,错误是值
|
||
2. error 接口只有一个 Error() string 方法
|
||
3. 使用多返回值模式处理错误
|
||
4. 错误应该被检查和处理,不应该被忽略
|
||
5. 错误信息应该清晰、有用、包含上下文
|
||
|
||
错误处理的特点:
|
||
1. 显式性:错误必须被显式检查
|
||
2. 简单性:error 接口设计简单
|
||
3. 组合性:错误可以被包装和组合
|
||
4. 传播性:错误可以在调用栈中传播
|
||
5. 灵活性:支持多种错误处理模式
|
||
|
||
错误创建方式:
|
||
1. errors.New():创建简单错误
|
||
2. fmt.Errorf():创建格式化错误
|
||
3. 自定义错误类型:实现 error 接口
|
||
4. 预定义错误:定义常用错误变量
|
||
5. 错误包装:使用 %w 动词包装错误
|
||
|
||
错误检查模式:
|
||
1. 立即检查:if err != nil { ... }
|
||
2. 错误传播:return err 或 return fmt.Errorf("...: %w", err)
|
||
3. 错误类型检查:errors.Is() 和 errors.As()
|
||
4. 错误恢复:提供默认值或回退方案
|
||
5. 错误聚合:收集多个错误
|
||
|
||
错误处理模式:
|
||
1. 哨兵错误:预定义的错误值
|
||
2. 错误类型:自定义错误结构体
|
||
3. 不透明错误:不检查具体错误类型
|
||
4. 错误包装:添加上下文信息
|
||
5. 错误聚合:收集和批量处理错误
|
||
|
||
最佳实践:
|
||
1. 错误信息应该小写开头,不以标点结尾
|
||
2. 错误信息应该包含足够的上下文
|
||
3. 使用错误包装传播上下文
|
||
4. 不要忽略错误,至少要记录日志
|
||
5. 在适当的层级处理错误
|
||
|
||
常见陷阱:
|
||
1. 忽略错误返回值
|
||
2. 错误信息不够清晰
|
||
3. 过度包装错误
|
||
4. 不适当的错误类型检查
|
||
5. 在错误处理中引入新的错误
|
||
|
||
性能考虑:
|
||
1. 错误创建有一定开销
|
||
2. 错误包装会增加内存使用
|
||
3. 错误检查是必要的开销
|
||
4. 避免在热点路径创建复杂错误
|
||
5. 合理使用预定义错误
|
||
|
||
注意事项:
|
||
1. nil 错误表示没有错误
|
||
2. 错误比较使用 == 或 errors.Is()
|
||
3. 错误类型断言使用 errors.As()
|
||
4. 错误包装使用 fmt.Errorf() 和 %w
|
||
5. 错误解包使用 errors.Unwrap()
|
||
*/
|