recover() 必须写在 defer 函数中才有效,否则恒返回 nil;它仅在 panic 正在传播且当前 defer 执行时生效,且仅能捕获同 goroutine 的 panic。

recover() 必须写在 defer 函数里,否则永远返回 nil
这是最常被忽略的硬性规则:把 recover() 放在普通代码流中(比如函数开头、if 分支里、甚至 main 函数顶层),它一定返回 nil,什么也捞不到。因为 recover() 只有在 panic 正在传播、且当前 defer 正被执行时才有效。
正确写法只有一种结构:
defer func() {
if r := recover(); r != nil {
// 处理 panic 值
}
}()
这个 defer 语句必须出现在可能触发 panic 的代码之前——哪怕只差一行,晚了就失效。
- 不能写成
if r := recover(); r != nil { ... }单独一行 - 不能在 goroutine 外层函数里注册 defer 去捕获子 goroutine 的 panic
- 多个 panic 路径(如不同分支调用 risky 函数)要确保都被同一层 defer 覆盖
每个 goroutine 都得单独加 defer+recover
Go 的 panic 是 goroutine 局部的。主线程里写的 defer+recover() 对子 goroutine 完全无效。HTTP handler 里起一个 go func() { panic("x") }(),外面根本收不到;后台 worker 没包 recover,可能静默退出、连接卡死、事务没回滚。
立即学习“go语言免费学习笔记(深入)”;
典型防护方式:
诊断并恢复通过 SSH 隧道连接的 OpenClaw 节点。用于解决配对必需错误、隧道冲突、远程端点错误以及 SSH 目标配置错误等问题。
- 在每个 goroutine 入口手动加
defer func(){...}() - 用
errgroup.Group封装并发任务,它的Go()方法会自动 recover - 封装
goSafe(func(){})工具函数,统一注入 defer+recover
recover 后不能继续使用已损坏的状态
recover() 让程序“继续往下跑”,但不等于状态安全。越界访问 slice 后 recover,那个 slice 可能已处于未定义行为边缘;向已 close 的 channel 再写入,大概率二次 panic。
实际操作建议:
- recover 后立即清理资源(如关闭文件、释放锁),不要复用原变量
- 避免在 recover 块里继续调用可能依赖损坏状态的函数
- 推荐显式 return 或 panic 新错误,而不是“硬撑”执行后续逻辑
- 如果必须继续,先做状态校验(比如检查 map 是否为 nil、channel 是否 closed)
recover 返回值必须显式处理,别当 error 用
recover() 返回的是 interface{},不是 error。直接打印或记录时,建议用 fmt.Sprint(r) 或 fmt.Sprintf("%v", r),避免类型断言失败导致二次 panic。
常见疏漏点:
- 只写
if r := recover() { ... },没判r != nil,结果r是nil也被当成 panic 值处理 - 把
r当作error直接传给日志库,某些库会 panic - recover 后没 return,函数按命名返回变量默认值继续走,可能掩盖问题
真正容易被忽略的,是 panic 发生后函数剩余代码不会执行,但 defer 会执行——这意味着你得靠 defer 里的 recover 拿到上下文,而不是指望 panic 行之后还能补救。

















