recover必须在defer函数内调用才有效,仅能捕获当前goroutine中正在传播的panic;需类型断言区分业务错误与程序崩溃,避免误恢复或资源泄漏。

defer 本身不能捕获 panic,recover 必须在 defer 函数里调用才有效
很多人以为 defer 是“自动兜底”,其实它只是延迟执行函数;真正恢复 panic 的是 recover(),但它只在 defer 函数中、且 panic 正在传播时才返回非 nil 值。一旦 panic 已经被上层 recover,当前 defer 里的 recover 就拿不到任何东西。
常见错误现象:recover() 返回 nil,日志没打、错误被吞掉、程序静默失败。
- 必须把
recover()放在defer的匿名函数内部,不能写成defer recover() - 不能在普通函数里调用
recover()—— 它只在 goroutine 的 panic 传播路径中生效 - 如果 panic 发生在子 goroutine 里,主 goroutine 的 defer + recover 完全无效
想只恢复特定 panic,得靠类型断言和 error 匹配
Go 的 panic 可以是任意值:string、error、自定义 struct,甚至 nil。直接 recover() 拿到的是 interface{},不判断就处理,容易误恢复不该恢复的 panic(比如真正的崩溃)。
使用场景:你只想拦截 ErrValidationFailed 这类业务错误,但不想掩盖 nil pointer dereference 这类程序 bug。
立即学习“go语言免费学习笔记(深入)”;
- 先用
v := recover()拿到值,再用if err, ok := v.(error); ok && errors.Is(err, ErrValidationFailed)判断 - 如果是字符串 panic(如
panic("timeout")),用if s, ok := v.(string); ok && strings.Contains(s, "timeout") - 避免直接
log.Println(v)后继续运行 —— 有些 panic 表明状态已损坏,强行续跑可能引发二次 panic
recover 后继续 panic 是常见且合理的做法
不是所有 panic 都该被吃掉。很多库(比如 http.Server)在 handler 中 recover 后会 log 并返回 500,但不会阻止 panic 向上冒泡 —— 因为上层可能有更合适的兜底逻辑。
性能 / 兼容性影响:recover 本身开销极小,但频繁 panic + recover 是严重反模式,会拖慢 10 倍以上(对比正常错误返回)。别把它当 try/catch 用。
- 恢复后想透传原 panic?用
panic(v),不是panic(err)—— 否则栈信息丢失 - 想包装 panic?用
panic(fmt.Errorf("wrapped: %w", v)),但注意%w只对 error 类型有效 - HTTP handler 中 recover 是标准做法;CLI 命令入口也建议加一层,防止 panic 打印堆栈吓到用户
goroutine 泄漏:recover 不等于问题终结
recover 只终止 panic 传播,不清理资源。如果 panic 发生在持有 channel、mutex、文件句柄的代码段里,recover 后这些资源很可能没被释放。
容易踩的坑:defer 在 panic 时仍会执行,但如果你的 defer 依赖某个已 panic 的变量(比如 defer f.Close() 中 f 是 nil),那 defer 自己也会 panic,导致 recover 失效。
- 所有关键资源操作前做非空/有效检查,尤其在 defer 前置逻辑里
- 不要在 defer 中调用可能 panic 的函数(如未判空的 map 访问、未初始化的 interface 调用)
- 用
go tool trace或pprof查 goroutine 数量持续上涨,往往就是 recover 后没 clean up 导致的










