【go】defer捕获panic案例,自存档

使用 defer 和 recover 捕获 panic 的示例

在 Go 语言中,deferrecover 是处理 panic 的两个重要机制。下面是一个完整的示例,展示如何使用 defer 语句中的 recover 来捕获并处理 panic:

package main

import (
	"fmt"
)

func main() {
	// 正常执行
	fmt.Println("调用safeDivide(10, 2)")
	safeDivide(10, 2)

	// 触发 panic 的情况
	fmt.Println("\n调用safeDivide(10, 0)")
	safeDivide(10, 0)

	// 程序继续执行
	fmt.Println("\n程序继续执行...")
}

func safeDivide(a, b int) {
	// 使用 defer 定义一个匿名函数来处理可能的 panic
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("捕获到 panic: %v\n", r)
			fmt.Println("已恢复执行")
		}
	}()

	fmt.Printf("计算 %d / %d\n", a, b)
	result := divide(a, b)
	fmt.Printf("结果是: %d\n", result)
}

func divide(a, b int) int {
	if b == 0 {
		// 当除数为0时,主动触发 panic
		panic("除数不能为零!")
	}
	return a / b
}

输出结果:

调用safeDivide(10, 2)
计算 10 / 2
结果是: 5

调用safeDivide(10, 0)
计算 10 / 0
捕获到 panic: 除数不能为零!
已恢复执行

程序继续执行...

关键点说明:

  1. defer 语句确保函数退出时执行恢复逻辑
  2. recover() 只在 defer 函数中有效,它会捕获 panic 并返回 panic 的值
  3. 如果没有发生 panic,recover() 返回 nil
  4. 使用这种模式可以防止 panic 导致整个程序崩溃
  5. 恢复后程序会从 panic 点之后继续执行(实际上是继续执行 defer 之后的代码)

实际应用场景

这种技术常用于:

  • 防止第三方库的 panic 导致程序崩溃
  • 在关键服务中实现优雅的错误恢复
  • 在测试中捕获预期中的 panic

注意:不应该滥用 panic/recover 作为常规错误处理机制,Go 中通常使用 error 返回值来处理可预期的错误。


案例

1. 关键服务中实现优雅的错误恢复

package main

import (
    "fmt"
    "log"
    "net/http"
)

// recoverMiddleware 是一个中间件,用来捕获处理过程中所有的 panic
func recoverMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if err := recover(); err != nil {
                // 记录错误日志,避免服务崩溃
                log.Printf("recover from panic: %v, request: %s %s", err, r.Method, r.URL.Path)
                // 返回 500 错误响应给客户端,防止崩溃信息泄露
                http.Error(w, "Internal Server Error", http.StatusInternalServerError)
            }
        }()

        // 调用下一个处理器
        next.ServeHTTP(w, r)
    })
}

// simulateThirdPartyLibrary 是模拟的第三方库,可能会 panic
func simulateThirdPartyLibrary() {
    panic("第三方库炸了: connection pool corrupted")
}

// handleRequest 是业务处理函数,调用了可能 panic 的第三方库
func handleRequest(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "业务处理中...")
    simulateThirdPartyLibrary() // 调用第三方库
    fmt.Fprintln(w, "业务处理完成") // 不会执行到这里
}

func main() {
    // 注册路由,包上 recover 中间件
    http.Handle("/", recoverMiddleware(http.HandlerFunc(handleRequest)))

    log.Println("服务启动在 :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatalf("服务启动失败: %v", err)
    }
}

  • recoverMiddleware 是标准写法:统一捕获每一次 HTTP 请求里的 panic,不影响其他请求继续工作。

  • 如果 simulateThirdPartyLibrary() 发生了 panic,recover 能记录日志并且返回安全的 500 错误,而不会让服务器整体挂掉。

  • 防止了单次异常影响整个服务,特别适合防御那些质量差但必须依赖的第三方库。


2. 防止第三方库 panic 导致程序崩溃

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

// 第三方服务客户端
type ThirdPartyClient struct{}

func (c *ThirdPartyClient) ParseResponse(data []byte) (map[string]interface{}, error) {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("第三方库发生 panic: %v", r)
		}
	}()

	// 模拟第三方库内部可能 panic 的情况
	var result map[string]interface{}
	if err := json.Unmarshal(data, &result); err != nil {
		return nil, fmt.Errorf("解析失败: %w", err)
	}
	
	// 模拟第三方库可能 panic 的逻辑
	if _, ok := result["critical"]; !ok {
		panic("缺少 critical 字段") // 第三方库的不合理设计
	}

	return result, nil
}

func main() {
	client := &ThirdPartyClient{}

	// 测试正常情况
	goodData := []byte(`{"critical": true, "value": "正常数据"}`)
	if res, err := client.ParseResponse(goodData); err != nil {
		log.Printf("处理正常数据时出错: %v", err)
	} else {
		log.Printf("正常数据结果: %v", res)
	}

	// 测试会触发第三方库 panic 的情况
	badData := []byte(`{"value": "缺少critical字段"}`)
	if res, err := client.ParseResponse(badData); err != nil {
		log.Printf("处理异常数据时出错: %v", err)
	} else {
		log.Printf("异常数据结果: %v", res)
	}

	// 程序继续执行
	log.Println("主程序继续运行...")
}

https://github.com/0voice

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值