deferpanicrecover 经常一起出现,但如果只把它们当成三个孤立知识点,就很容易在真实代码里写出半懂不懂的恢复逻辑。

先抓主线

  • defer:函数返回前执行
  • panic:中断当前正常流程,沿调用栈向上展开
  • recover:只能在 defer 里拦住当前 goroutine 的 panic

defer 是后进先出

1func main() {
2    defer fmt.Println("first")
3    defer fmt.Println("second")
4    fmt.Println("body")
5}

输出顺序是:

1body
2second
3first

这点在释放资源时很好用,因为你可以按“获取资源”的顺序写代码,最终会按相反顺序清理。

panic 发生后会怎样

当函数里发生 panic,当前函数不会继续往下执行,但已经注册的 defer 仍然会执行。然后 panic 继续向上冒泡。

1func main() {
2    defer fmt.Println("cleanup")
3    panic("boom")
4}

recover 什么时候有效

只有在 defer 触发的函数里调用 recover(),并且当前 goroutine 正在发生 panic,它才会真正拿到值。

1func main() {
2    defer func() {
3        if r := recover(); r != nil {
4            fmt.Println("recovered:", r)
5        }
6    }()
7
8    panic("boom")
9}

它不能做什么

不能跨 goroutine recover

一个 goroutine 里的 recover,拦不住另一个 goroutine 的 panic。每个 goroutine 都要自己兜底。

不适合把 panic 当普通错误流

业务分支错误应该返回 errorpanic/recover 更适合处理真正的“不该发生”的状态,或者在框架层做最后一道保护。

Web 服务里常见的用法

HTTP 中间件里经常会用 recover 防止某个 handler 的 panic 直接把整个服务打崩,然后统一记日志、返回 500。

但这层兜底的意义是“保住进程”,不是鼓励业务代码随便 panic

我的总结

  • defer 是资源回收和收尾逻辑的自然落点
  • panic 会触发 defer,再沿调用栈继续传播
  • recover 只在 defer 中、只对当前 goroutine 生效
  • 日常业务错误优先返回 error,不要把 panic 当流程控制