Files
cs-note/hzh/GolangStar/Go语言基础/Go语言异常捕获.md
T

3.9 KiB
Raw Blame History

tags, create time
tags create time
go
golang
go基础语法
异常捕获
2026-06-07 15:00

Go 语言异常捕获

概述

Go 用 panic 表示不可恢复的错误,用 recover + defer 捕获 panic。本文讲解 panic 的传递规则、recover 的正确用法,以及何时不该用 recover。

正文

panic vs error:该用哪个?

这是 Go 开发者最常见的困惑之一:

error panic
用途 预期内的错误(文件不存在、参数无效) 不应该发生的严重错误(nil 指针解引用、数组越界)
处理方式 if err != nil 检查 defer + recover 捕获
是否继续执行 是(处理后可继续) 否(当前 goroutine 栈展开)

[!tip] 💡 黄金法则 99% 的场景你应该用 error,而不是 panic。 panic 应该留给"程序状态已损坏、无法安全继续"的情况。

recover:唯一的安全网

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到 panic:", r)
        }
    }()

    panic("出事了!")
    fmt.Println("这行不会执行")
}
// 输出:
// 捕获到 panic: 出事了!

[!note] 📝 recover 的关键限制

  • recover() 只能在 defer 中调用才有效
  • 它恢复的是当前 goroutine 的 panic
  • 被 recover 后,函数正常返回,调用方继续执行

panic 的传递规则

graph TD
    A["testPanic3 触发 panic"] --> B{"有 recover?"}
    B -->|否| C["栈展开,向上找"]
    C --> D["testPanic2 有 recover → 捕获"]
    D --> E["testPanic2 返回"]
    E --> F["testPanic1 继续执行"]
    F --> G["main 继续执行"]

当 panic 没有被当前函数 recover 时,它会沿调用链向上传播,直到遇到 recover 或到达 main:

func testPanic3() {
    fmt.Println("上半部分")
    panic("boom!")
    fmt.Println("下半部分")  // ❌ 不会执行
}

func testPanic2() {
    defer func() { recover() }()  // ✅ 捕获了来自 testPanic3 的 panic
    testPanic3()
    fmt.Println("testPanic2 继续")  // 会执行
}

func testPanic1() {
    testPanic2()
    fmt.Println("testPanic1 继续")  // 会执行
}

输出:

上半部分
testPanic2 继续
testPanic1 继续

[!warning] ⚠️ 没有 recover 的后果 如果 panic 一路传播到 main 都没有被 recover,程序会崩溃并打印堆栈信息:

panic: boom!
goroutine 1 [running]:
...

实战:安全的数学运算

func SafeDivide(a, b int) (result int, errMsg string) {
    defer func() {
        if r := recover(); r != nil {
            errMsg = fmt.Sprintf("除零错误: %v", r)
        }
    }()
    return a / b, ""
}

val, msg := SafeDivide(10, 0)
if msg != "" {
    log.Println(msg)  // 除零错误: runtime error: integer divide by zero
}

常见使用场景

场景 说明
服务器中间件 捕获请求级 panic,避免整个服务崩溃
并发池 Worker 协程中 recover,防止单个 goroutine 挂掉影响其他任务
插件系统 用户代码可能 panic,需要隔离保护
// 并发池中的典型模式
func Worker(id int, jobs <-chan int, results chan<- int) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("worker %d panicked: %v", id, r)
        }
    }()
    for j := range jobs {
        results <- process(j)
    }
}

关键要点总结

[!warning] ⚠️ 不要滥用 recover

  • 业务逻辑错误 → 用 error
  • 编程错误(bug)→ panic 是可以的
  • 第三方库 panic → recover 保护你的程序
  • 每个函数都加 recover → 掩盖 bug,不利于调试

[!info] ℹ️ 最佳实践

  1. 在入口点(如 HTTP handler、main 函数顶层)加一层 recover
  2. 在并发 goroutine 中加 recover,防止单个协程 crash 扩散
  3. 记录完整的 panic 堆栈(可用 runtime.Stack())后再恢复