继续
This commit is contained in:
957
golang-learning/07-error-handling/01-basic-errors.go
Normal file
957
golang-learning/07-error-handling/01-basic-errors.go
Normal file
@@ -0,0 +1,957 @@
|
||||
/*
|
||||
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()
|
||||
*/
|
1071
golang-learning/07-error-handling/02-custom-errors.go
Normal file
1071
golang-learning/07-error-handling/02-custom-errors.go
Normal file
File diff suppressed because it is too large
Load Diff
941
golang-learning/07-error-handling/03-panic-recover.go
Normal file
941
golang-learning/07-error-handling/03-panic-recover.go
Normal file
@@ -0,0 +1,941 @@
|
||||
/*
|
||||
03-panic-recover.go - Go 语言 Panic 和 Recover 详解
|
||||
|
||||
学习目标:
|
||||
1. 理解 panic 的概念和使用场景
|
||||
2. 掌握 recover 的恢复机制
|
||||
3. 学会 defer 与 panic/recover 的配合
|
||||
4. 了解 panic 的传播机制
|
||||
5. 掌握 panic/recover 的最佳实践
|
||||
|
||||
知识点:
|
||||
- panic 的触发条件和行为
|
||||
- recover 的恢复机制
|
||||
- defer 语句的执行顺序
|
||||
- panic 的传播和栈展开
|
||||
- panic/recover 的实际应用
|
||||
- 错误处理 vs panic 的选择
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("=== Go 语言 Panic 和 Recover 详解 ===\\n")
|
||||
|
||||
// 演示 panic 的基本概念
|
||||
demonstratePanicBasics()
|
||||
|
||||
// 演示 recover 的使用
|
||||
demonstrateRecover()
|
||||
|
||||
// 演示 defer 与 panic/recover 的配合
|
||||
demonstrateDeferWithPanicRecover()
|
||||
|
||||
// 演示 panic 的传播
|
||||
demonstratePanicPropagation()
|
||||
|
||||
// 演示 panic/recover 的实际应用
|
||||
demonstratePracticalUsage()
|
||||
|
||||
// 演示最佳实践
|
||||
demonstrateBestPractices()
|
||||
|
||||
// 演示错误处理 vs panic 的选择
|
||||
demonstrateErrorVsPanic()
|
||||
}
|
||||
|
||||
// demonstratePanicBasics 演示 panic 的基本概念
|
||||
func demonstratePanicBasics() {
|
||||
fmt.Println("1. Panic 的基本概念:")
|
||||
|
||||
// panic 的基本特性
|
||||
fmt.Printf(" Panic 的基本特性:\\n")
|
||||
fmt.Printf(" - panic 是运行时错误,会导致程序崩溃\\n")
|
||||
fmt.Printf(" - panic 会停止当前函数的执行\\n")
|
||||
fmt.Printf(" - panic 会执行当前函数的 defer 语句\\n")
|
||||
fmt.Printf(" - panic 会向上传播到调用栈\\n")
|
||||
fmt.Printf(" - panic 可以被 recover 捕获\\n")
|
||||
|
||||
// 手动触发 panic
|
||||
fmt.Printf(" 手动触发 panic:\\n")
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 捕获到 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Printf(" 准备触发 panic\\n")
|
||||
panic("这是一个手动触发的 panic")
|
||||
fmt.Printf(" 这行代码不会执行\\n")
|
||||
}()
|
||||
|
||||
// 常见的 panic 场景
|
||||
fmt.Printf(" 常见的 panic 场景:\\n")
|
||||
|
||||
// 1. 数组越界
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 数组越界 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
arr := [3]int{1, 2, 3}
|
||||
fmt.Printf(" 访问数组索引 5: %d\\n", arr[5]) // 会 panic
|
||||
}()
|
||||
|
||||
// 2. 空指针解引用
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 空指针解引用 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var ptr *int
|
||||
fmt.Printf(" 解引用空指针: %d\\n", *ptr) // 会 panic
|
||||
}()
|
||||
|
||||
// 3. 类型断言失败
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 类型断言失败 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var x interface{} = "hello"
|
||||
num := x.(int) // 会 panic
|
||||
fmt.Printf(" 类型断言结果: %d\\n", num)
|
||||
}()
|
||||
|
||||
// 4. 向已关闭的 channel 发送数据
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 向已关闭 channel 发送数据 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
ch := make(chan int)
|
||||
close(ch)
|
||||
ch <- 1 // 会 panic
|
||||
}()
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateRecover 演示 recover 的使用
|
||||
func demonstrateRecover() {
|
||||
fmt.Println("2. Recover 的使用:")
|
||||
|
||||
// recover 的基本概念
|
||||
fmt.Printf(" Recover 的基本概念:\\n")
|
||||
fmt.Printf(" - recover 只能在 defer 函数中使用\\n")
|
||||
fmt.Printf(" - recover 可以捕获当前 goroutine 的 panic\\n")
|
||||
fmt.Printf(" - recover 返回 panic 的值\\n")
|
||||
fmt.Printf(" - recover 后程序可以继续执行\\n")
|
||||
|
||||
// 基本 recover 示例
|
||||
fmt.Printf(" 基本 recover 示例:\\n")
|
||||
|
||||
result := safeFunction(func() int {
|
||||
panic("计算过程中发生错误")
|
||||
return 42
|
||||
})
|
||||
fmt.Printf(" 安全函数返回: %d\\n", result)
|
||||
|
||||
// recover 的返回值
|
||||
fmt.Printf(" recover 的返回值:\\n")
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" panic 值: %v\\n", r)
|
||||
fmt.Printf(" panic 类型: %T\\n", r)
|
||||
|
||||
// 根据 panic 类型进行不同处理
|
||||
switch v := r.(type) {
|
||||
case string:
|
||||
fmt.Printf(" 字符串 panic: %s\\n", v)
|
||||
case int:
|
||||
fmt.Printf(" 整数 panic: %d\\n", v)
|
||||
case error:
|
||||
fmt.Printf(" 错误 panic: %v\\n", v)
|
||||
default:
|
||||
fmt.Printf(" 其他类型 panic: %v\\n", v)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// 触发不同类型的 panic
|
||||
testPanics := []interface{}{
|
||||
"字符串错误",
|
||||
42,
|
||||
fmt.Errorf("错误对象"),
|
||||
struct{ Message string }{"结构体错误"},
|
||||
}
|
||||
|
||||
for i, panicValue := range testPanics {
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 测试 %d - panic: %v (%T)\\n", i+1, r, r)
|
||||
}
|
||||
}()
|
||||
panic(panicValue)
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
// recover 的条件
|
||||
fmt.Printf(" recover 的条件:\\n")
|
||||
|
||||
// 正确的 recover 使用
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 正确捕获 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
panic("正确的 recover 示例")
|
||||
}()
|
||||
|
||||
// 错误的 recover 使用
|
||||
func() {
|
||||
defer func() {
|
||||
// 这个 recover 不会捕获到 panic,因为不是直接在 defer 中调用
|
||||
go func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 错误的 recover: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 正确捕获嵌套 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
panic("嵌套 panic 示例")
|
||||
}()
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateDeferWithPanicRecover 演示 defer 与 panic/recover 的配合
|
||||
func demonstrateDeferWithPanicRecover() {
|
||||
fmt.Println("3. Defer 与 Panic/Recover 的配合:")
|
||||
|
||||
// defer 的执行顺序
|
||||
fmt.Printf(" defer 的执行顺序:\\n")
|
||||
|
||||
func() {
|
||||
defer fmt.Printf(" defer 1\\n")
|
||||
defer fmt.Printf(" defer 2\\n")
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" recover: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
defer fmt.Printf(" defer 3\\n")
|
||||
|
||||
fmt.Printf(" 正常执行\\n")
|
||||
panic("测试 defer 顺序")
|
||||
fmt.Printf(" 这行不会执行\\n")
|
||||
}()
|
||||
|
||||
// 多层 defer 和 recover
|
||||
fmt.Printf(" 多层 defer 和 recover:\\n")
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
fmt.Printf(" 外层 defer 开始\\n")
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 外层 recover: %v\\n", r)
|
||||
}
|
||||
fmt.Printf(" 外层 defer 结束\\n")
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
fmt.Printf(" 内层 defer 开始\\n")
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 内层 recover: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
fmt.Printf(" 内层 defer 结束\\n")
|
||||
}()
|
||||
|
||||
panic("多层 defer 测试")
|
||||
}()
|
||||
|
||||
// defer 中的资源清理
|
||||
fmt.Printf(" defer 中的资源清理:\\n")
|
||||
|
||||
processWithCleanup := func() {
|
||||
// 模拟资源分配
|
||||
resource := "重要资源"
|
||||
fmt.Printf(" 分配资源: %s\\n", resource)
|
||||
|
||||
defer func() {
|
||||
// 资源清理
|
||||
fmt.Printf(" 清理资源: %s\\n", resource)
|
||||
|
||||
// 捕获 panic
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 处理过程中发生 panic: %v\\n", r)
|
||||
fmt.Printf(" 资源已安全清理\\n")
|
||||
}
|
||||
}()
|
||||
|
||||
// 模拟可能 panic 的操作
|
||||
if time.Now().UnixNano()%2 == 0 {
|
||||
panic("随机 panic")
|
||||
}
|
||||
|
||||
fmt.Printf(" 正常处理完成\\n")
|
||||
}
|
||||
|
||||
// 执行多次以演示不同情况
|
||||
for i := 0; i < 3; i++ {
|
||||
fmt.Printf(" 执行 %d:\\n", i+1)
|
||||
processWithCleanup()
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstratePanicPropagation 演示 panic 的传播
|
||||
func demonstratePanicPropagation() {
|
||||
fmt.Println("4. Panic 的传播:")
|
||||
|
||||
// panic 的栈展开
|
||||
fmt.Printf(" panic 的栈展开:\\n")
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 最终捕获 panic: %v\\n", r)
|
||||
|
||||
// 打印调用栈
|
||||
buf := make([]byte, 1024)
|
||||
n := runtime.Stack(buf, false)
|
||||
fmt.Printf(" 调用栈:\\n%s\\n", buf[:n])
|
||||
}
|
||||
}()
|
||||
|
||||
level1()
|
||||
}()
|
||||
|
||||
// panic 的中途拦截
|
||||
fmt.Printf(" panic 的中途拦截:\\n")
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 最外层捕获: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
interceptedLevel1()
|
||||
}()
|
||||
|
||||
// panic 的重新抛出
|
||||
fmt.Printf(" panic 的重新抛出:\\n")
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 最终处理 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
rethrowLevel1()
|
||||
}()
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstratePracticalUsage 演示 panic/recover 的实际应用
|
||||
func demonstratePracticalUsage() {
|
||||
fmt.Println("5. Panic/Recover 的实际应用:")
|
||||
|
||||
// Web 服务器中的 panic 恢复
|
||||
fmt.Printf(" Web 服务器中的 panic 恢复:\\n")
|
||||
|
||||
requests := []string{
|
||||
"/api/users",
|
||||
"/api/panic",
|
||||
"/api/orders",
|
||||
"/api/error",
|
||||
}
|
||||
|
||||
for _, path := range requests {
|
||||
handleHTTPRequest(path)
|
||||
}
|
||||
|
||||
// 工作池中的 panic 处理
|
||||
fmt.Printf(" 工作池中的 panic 处理:\\n")
|
||||
|
||||
jobs := []Job{
|
||||
{ID: 1, Data: "正常任务"},
|
||||
{ID: 2, Data: "panic任务"},
|
||||
{ID: 3, Data: "正常任务"},
|
||||
{ID: 4, Data: "error任务"},
|
||||
}
|
||||
|
||||
for _, job := range jobs {
|
||||
processJobSafely(job)
|
||||
}
|
||||
|
||||
// 数据解析中的 panic 处理
|
||||
fmt.Printf(" 数据解析中的 panic 处理:\\n")
|
||||
|
||||
data := []string{
|
||||
"123",
|
||||
"abc",
|
||||
"456",
|
||||
"",
|
||||
"789",
|
||||
}
|
||||
|
||||
for _, item := range data {
|
||||
result := parseIntSafely(item)
|
||||
fmt.Printf(" 解析 '%s': %d\\n", item, result)
|
||||
}
|
||||
|
||||
// 递归函数中的 panic 处理
|
||||
fmt.Printf(" 递归函数中的 panic 处理:\\n")
|
||||
|
||||
testValues := []int{5, -1, 10, 0}
|
||||
for _, n := range testValues {
|
||||
result := safeFactorial(n)
|
||||
fmt.Printf(" %d! = %d\\n", n, result)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateBestPractices 演示最佳实践
|
||||
func demonstrateBestPractices() {
|
||||
fmt.Println("6. 最佳实践:")
|
||||
|
||||
// 最佳实践原则
|
||||
fmt.Printf(" 最佳实践原则:\\n")
|
||||
fmt.Printf(" 1. 优先使用错误返回值而不是 panic\\n")
|
||||
fmt.Printf(" 2. 只在真正异常的情况下使用 panic\\n")
|
||||
fmt.Printf(" 3. 在库代码中避免 panic,转换为错误\\n")
|
||||
fmt.Printf(" 4. 在应用边界使用 recover 保护\\n")
|
||||
fmt.Printf(" 5. 记录 panic 信息用于调试\\n")
|
||||
|
||||
// 库函数的 panic 处理
|
||||
fmt.Printf(" 库函数的 panic 处理:\\n")
|
||||
|
||||
// 不好的做法:库函数直接 panic
|
||||
fmt.Printf(" 不好的做法示例:\\n")
|
||||
result, err := callLibraryFunction("invalid")
|
||||
if err != nil {
|
||||
fmt.Printf(" 库函数错误: %v\\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 库函数结果: %s\\n", result)
|
||||
}
|
||||
|
||||
// 好的做法:将 panic 转换为错误
|
||||
fmt.Printf(" 好的做法示例:\\n")
|
||||
result, err = safeLibraryFunction("invalid")
|
||||
if err != nil {
|
||||
fmt.Printf(" 安全库函数错误: %v\\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 安全库函数结果: %s\\n", result)
|
||||
}
|
||||
|
||||
// panic 信息的记录
|
||||
fmt.Printf(" panic 信息的记录:\\n")
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logPanic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
panic("需要记录的 panic")
|
||||
}()
|
||||
|
||||
// 优雅的服务关闭
|
||||
fmt.Printf(" 优雅的服务关闭:\\n")
|
||||
|
||||
server := &Server{name: "测试服务器"}
|
||||
server.Start()
|
||||
|
||||
// 模拟服务运行中的 panic
|
||||
func() {
|
||||
defer server.handlePanic()
|
||||
panic("服务运行时错误")
|
||||
}()
|
||||
|
||||
server.Stop()
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// demonstrateErrorVsPanic 演示错误处理 vs panic 的选择
|
||||
func demonstrateErrorVsPanic() {
|
||||
fmt.Println("7. 错误处理 vs Panic 的选择:")
|
||||
|
||||
// 使用错误的场景
|
||||
fmt.Printf(" 使用错误的场景:\\n")
|
||||
fmt.Printf(" - 预期可能发生的错误\\n")
|
||||
fmt.Printf(" - 用户输入错误\\n")
|
||||
fmt.Printf(" - 网络或I/O错误\\n")
|
||||
fmt.Printf(" - 业务逻辑错误\\n")
|
||||
|
||||
// 使用 panic 的场景
|
||||
fmt.Printf(" 使用 panic 的场景:\\n")
|
||||
fmt.Printf(" - 程序逻辑错误\\n")
|
||||
fmt.Printf(" - 不可恢复的错误\\n")
|
||||
fmt.Printf(" - 初始化失败\\n")
|
||||
fmt.Printf(" - 断言失败\\n")
|
||||
|
||||
// 对比示例
|
||||
fmt.Printf(" 对比示例:\\n")
|
||||
|
||||
// 错误处理示例
|
||||
fmt.Printf(" 错误处理示例:\\n")
|
||||
result, err := divide(10, 0)
|
||||
if err != nil {
|
||||
fmt.Printf(" 除法错误: %v\\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 除法结果: %.2f\\n", result)
|
||||
}
|
||||
|
||||
// panic 示例
|
||||
fmt.Printf(" panic 示例:\\n")
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 捕获 panic: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
assertPositive(-5)
|
||||
}()
|
||||
|
||||
// 混合使用示例
|
||||
fmt.Printf(" 混合使用示例:\\n")
|
||||
|
||||
calculator := &Calculator{}
|
||||
|
||||
// 正常计算
|
||||
result, err = calculator.Calculate("10 + 5")
|
||||
if err != nil {
|
||||
fmt.Printf(" 计算错误: %v\\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 计算结果: %.2f\\n", result)
|
||||
}
|
||||
|
||||
// 无效表达式
|
||||
result, err = calculator.Calculate("invalid")
|
||||
if err != nil {
|
||||
fmt.Printf(" 计算错误: %v\\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 计算结果: %.2f\\n", result)
|
||||
}
|
||||
|
||||
// 内部 panic 被转换为错误
|
||||
result, err = calculator.Calculate("panic")
|
||||
if err != nil {
|
||||
fmt.Printf(" 计算错误: %v\\n", err)
|
||||
} else {
|
||||
fmt.Printf(" 计算结果: %.2f\\n", result)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// ========== 辅助函数和类型定义 ==========
|
||||
|
||||
// safeFunction 安全执行函数
|
||||
func safeFunction(fn func() int) int {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 函数执行中发生 panic,已恢复: %v\\n", r)
|
||||
}
|
||||
}()
|
||||
|
||||
return fn()
|
||||
}
|
||||
|
||||
// level1 第一层函数
|
||||
func level1() {
|
||||
defer func() {
|
||||
fmt.Printf(" level1 defer 执行\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 level1\\n")
|
||||
level2()
|
||||
fmt.Printf(" level1 正常结束\\n")
|
||||
}
|
||||
|
||||
// level2 第二层函数
|
||||
func level2() {
|
||||
defer func() {
|
||||
fmt.Printf(" level2 defer 执行\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 level2\\n")
|
||||
level3()
|
||||
fmt.Printf(" level2 正常结束\\n")
|
||||
}
|
||||
|
||||
// level3 第三层函数
|
||||
func level3() {
|
||||
defer func() {
|
||||
fmt.Printf(" level3 defer 执行\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 level3\\n")
|
||||
panic("level3 中的 panic")
|
||||
fmt.Printf(" level3 正常结束\\n")
|
||||
}
|
||||
|
||||
// interceptedLevel1 中途拦截的第一层
|
||||
func interceptedLevel1() {
|
||||
defer func() {
|
||||
fmt.Printf(" interceptedLevel1 defer\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 interceptedLevel1\\n")
|
||||
interceptedLevel2()
|
||||
fmt.Printf(" interceptedLevel1 正常结束\\n")
|
||||
}
|
||||
|
||||
// interceptedLevel2 中途拦截的第二层
|
||||
func interceptedLevel2() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" interceptedLevel2 拦截 panic: %v\\n", r)
|
||||
// 不重新抛出,panic 在这里被处理
|
||||
}
|
||||
fmt.Printf(" interceptedLevel2 defer\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 interceptedLevel2\\n")
|
||||
interceptedLevel3()
|
||||
fmt.Printf(" interceptedLevel2 正常结束\\n")
|
||||
}
|
||||
|
||||
// interceptedLevel3 中途拦截的第三层
|
||||
func interceptedLevel3() {
|
||||
defer func() {
|
||||
fmt.Printf(" interceptedLevel3 defer\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 interceptedLevel3\\n")
|
||||
panic("interceptedLevel3 中的 panic")
|
||||
fmt.Printf(" interceptedLevel3 正常结束\\n")
|
||||
}
|
||||
|
||||
// rethrowLevel1 重新抛出的第一层
|
||||
func rethrowLevel1() {
|
||||
defer func() {
|
||||
fmt.Printf(" rethrowLevel1 defer\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 rethrowLevel1\\n")
|
||||
rethrowLevel2()
|
||||
fmt.Printf(" rethrowLevel1 正常结束\\n")
|
||||
}
|
||||
|
||||
// rethrowLevel2 重新抛出的第二层
|
||||
func rethrowLevel2() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" rethrowLevel2 捕获并重新抛出 panic: %v\\n", r)
|
||||
panic(fmt.Sprintf("重新抛出: %v", r)) // 重新抛出
|
||||
}
|
||||
fmt.Printf(" rethrowLevel2 defer\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 rethrowLevel2\\n")
|
||||
rethrowLevel3()
|
||||
fmt.Printf(" rethrowLevel2 正常结束\\n")
|
||||
}
|
||||
|
||||
// rethrowLevel3 重新抛出的第三层
|
||||
func rethrowLevel3() {
|
||||
defer func() {
|
||||
fmt.Printf(" rethrowLevel3 defer\\n")
|
||||
}()
|
||||
|
||||
fmt.Printf(" 进入 rethrowLevel3\\n")
|
||||
panic("rethrowLevel3 中的 panic")
|
||||
fmt.Printf(" rethrowLevel3 正常结束\\n")
|
||||
}
|
||||
|
||||
// handleHTTPRequest 处理HTTP请求
|
||||
func handleHTTPRequest(path string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" HTTP请求 %s 发生 panic,已恢复: %v\\n", path, r)
|
||||
// 在实际应用中,这里会返回 500 错误
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Printf(" 处理请求: %s\\n", path)
|
||||
|
||||
switch path {
|
||||
case "/api/panic":
|
||||
panic("模拟处理器 panic")
|
||||
case "/api/error":
|
||||
// 这里应该返回错误而不是 panic
|
||||
fmt.Printf(" 请求处理出错\\n")
|
||||
default:
|
||||
fmt.Printf(" 请求处理成功\\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Job 任务结构
|
||||
type Job struct {
|
||||
ID int
|
||||
Data string
|
||||
}
|
||||
|
||||
// processJobSafely 安全处理任务
|
||||
func processJobSafely(job Job) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 任务 %d 处理时发生 panic,已恢复: %v\\n", job.ID, r)
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Printf(" 处理任务 %d: %s\\n", job.ID, job.Data)
|
||||
|
||||
switch job.Data {
|
||||
case "panic任务":
|
||||
panic("任务处理中的 panic")
|
||||
case "error任务":
|
||||
fmt.Printf(" 任务处理出错\\n")
|
||||
default:
|
||||
fmt.Printf(" 任务处理成功\\n")
|
||||
}
|
||||
}
|
||||
|
||||
// parseIntSafely 安全解析整数
|
||||
func parseIntSafely(s string) int {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 解析 '%s' 时发生 panic: %v\\n", s, r)
|
||||
}
|
||||
}()
|
||||
|
||||
if s == "" {
|
||||
panic("空字符串无法解析")
|
||||
}
|
||||
|
||||
result, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return 0 // 返回默认值
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// safeFactorial 安全计算阶乘
|
||||
func safeFactorial(n int) int {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 计算 %d! 时发生 panic: %v\\n", n, r)
|
||||
}
|
||||
}()
|
||||
|
||||
return factorial(n)
|
||||
}
|
||||
|
||||
// factorial 计算阶乘(可能 panic)
|
||||
func factorial(n int) int {
|
||||
if n < 0 {
|
||||
panic("负数没有阶乘")
|
||||
}
|
||||
if n == 0 || n == 1 {
|
||||
return 1
|
||||
}
|
||||
return n * factorial(n-1)
|
||||
}
|
||||
|
||||
// callLibraryFunction 库函数(不好的做法)
|
||||
func callLibraryFunction(input string) (string, error) {
|
||||
// 这个函数直接 panic,不是好的做法
|
||||
if input == "invalid" {
|
||||
panic("库函数中的 panic")
|
||||
}
|
||||
return "处理结果: " + input, nil
|
||||
}
|
||||
|
||||
// safeLibraryFunction 安全的库函数
|
||||
func safeLibraryFunction(input string) (result string, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("库函数内部错误: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// 调用可能 panic 的函数
|
||||
return callLibraryFunction(input)
|
||||
}
|
||||
|
||||
// logPanic 记录 panic 信息
|
||||
func logPanic(r interface{}) {
|
||||
fmt.Printf(" [PANIC LOG] 时间: %s\\n", time.Now().Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf(" [PANIC LOG] 错误: %v\\n", r)
|
||||
fmt.Printf(" [PANIC LOG] 类型: %T\\n", r)
|
||||
|
||||
// 获取调用栈
|
||||
buf := make([]byte, 1024)
|
||||
n := runtime.Stack(buf, false)
|
||||
fmt.Printf(" [PANIC LOG] 调用栈:\\n%s\\n", buf[:n])
|
||||
}
|
||||
|
||||
// Server 服务器结构
|
||||
type Server struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (s *Server) Start() {
|
||||
fmt.Printf(" 服务器 %s 启动\\n", s.name)
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
fmt.Printf(" 服务器 %s 停止\\n", s.name)
|
||||
}
|
||||
|
||||
func (s *Server) handlePanic() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf(" 服务器 %s 捕获 panic: %v\\n", s.name, r)
|
||||
fmt.Printf(" 服务器继续运行\\n")
|
||||
}
|
||||
}
|
||||
|
||||
// divide 除法运算
|
||||
func divide(a, b float64) (float64, error) {
|
||||
if b == 0 {
|
||||
return 0, fmt.Errorf("除数不能为零")
|
||||
}
|
||||
return a / b, nil
|
||||
}
|
||||
|
||||
// assertPositive 断言为正数
|
||||
func assertPositive(n int) {
|
||||
if n <= 0 {
|
||||
panic(fmt.Sprintf("断言失败: %d 不是正数", n))
|
||||
}
|
||||
fmt.Printf(" 断言通过: %d 是正数\\n", n)
|
||||
}
|
||||
|
||||
// Calculator 计算器
|
||||
type Calculator struct{}
|
||||
|
||||
func (c *Calculator) Calculate(expression string) (result float64, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("计算表达式 '%s' 时发生内部错误: %v", expression, r)
|
||||
}
|
||||
}()
|
||||
|
||||
switch expression {
|
||||
case "10 + 5":
|
||||
return 15, nil
|
||||
case "invalid":
|
||||
return 0, fmt.Errorf("无效的表达式: %s", expression)
|
||||
case "panic":
|
||||
panic("计算器内部 panic")
|
||||
default:
|
||||
return 0, fmt.Errorf("不支持的表达式: %s", expression)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
运行这个程序:
|
||||
go run 03-panic-recover.go
|
||||
|
||||
学习要点:
|
||||
1. panic 是运行时错误,会导致程序崩溃
|
||||
2. recover 只能在 defer 函数中使用来捕获 panic
|
||||
3. defer 语句按照 LIFO 顺序执行
|
||||
4. panic 会沿着调用栈向上传播
|
||||
5. panic/recover 应该谨慎使用,优先使用错误处理
|
||||
|
||||
Panic 的特性:
|
||||
1. 停止当前函数的正常执行
|
||||
2. 执行当前函数的所有 defer 语句
|
||||
3. 向上传播到调用函数
|
||||
4. 如果没有被 recover,程序会崩溃
|
||||
5. 可以携带任意类型的值
|
||||
|
||||
Recover 的特性:
|
||||
1. 只能在 defer 函数中直接调用
|
||||
2. 返回 panic 的值,如果没有 panic 返回 nil
|
||||
3. 只能捕获当前 goroutine 的 panic
|
||||
4. 捕获后程序可以继续执行
|
||||
5. 可以根据 panic 值进行不同处理
|
||||
|
||||
常见的 Panic 场景:
|
||||
1. 数组或切片越界访问
|
||||
2. 空指针解引用
|
||||
3. 类型断言失败(不安全断言)
|
||||
4. 向已关闭的 channel 发送数据
|
||||
5. 除零操作(整数除法)
|
||||
|
||||
使用场景:
|
||||
1. Panic 适用场景:
|
||||
- 程序逻辑错误
|
||||
- 不可恢复的错误
|
||||
- 初始化失败
|
||||
- 断言失败
|
||||
|
||||
2. Error 适用场景:
|
||||
- 预期可能发生的错误
|
||||
- 用户输入错误
|
||||
- 网络或I/O错误
|
||||
- 业务逻辑错误
|
||||
|
||||
最佳实践:
|
||||
1. 优先使用错误返回值而不是 panic
|
||||
2. 只在真正异常的情况下使用 panic
|
||||
3. 在库代码中避免 panic,转换为错误
|
||||
4. 在应用边界使用 recover 保护
|
||||
5. 记录 panic 信息用于调试
|
||||
6. 使用 defer 进行资源清理
|
||||
|
||||
注意事项:
|
||||
1. recover 只能在 defer 中直接调用
|
||||
2. 不要忽略 recover 的返回值
|
||||
3. panic 会影响程序性能
|
||||
4. 过度使用 panic/recover 会使代码难以理解
|
||||
5. 在 goroutine 中的 panic 需要单独处理
|
||||
|
||||
实际应用:
|
||||
1. Web 服务器的 panic 恢复中间件
|
||||
2. 工作池中的任务 panic 处理
|
||||
3. 数据解析中的异常处理
|
||||
4. 递归函数的栈溢出保护
|
||||
5. 库函数的 panic 转错误
|
||||
|
||||
性能考虑:
|
||||
1. panic/recover 有一定的性能开销
|
||||
2. 不应该用于正常的控制流
|
||||
3. 频繁的 panic/recover 会影响性能
|
||||
4. 错误处理通常比 panic/recover 更高效
|
||||
5. 在性能敏感的代码中谨慎使用
|
||||
*/
|
Reference in New Issue
Block a user