recover必须在defer匿名函数内调用才有效;goroutine内panic无法被外层recover捕获;HTTP handler中需检查w.Header().WasWritten()再调用http.Error。

recover 必须写在 defer 匿名函数里,不能提前提取变量
直接写 defer func() { r := recover(); ... }() 是安全的;但写成 r := recover(); defer func() { if r != nil { ... } }() 就完全无效——recover() 在 panic 发生前调用,永远返回 nil。
-
recover()只有在 panic 正在展开、且当前 defer 函数尚未返回时才有效 - 它不是“监听器”,而是一个“栈展开时的快照读取器”,时机错一点就失效
- 常见翻车:把
recover()放在 if 判断里、或赋值给变量再判断,结果 panic 仍直穿出去
HTTP handler 中 recover 要检查 w.Header().WasWritten()
中间件或 handler 里先写了部分响应(比如日志中间件调用了 w.WriteHeader(200)),再 panic,此时 http.Error(w, ...) 会触发二次 panic —— 因为 header 已发送,无法再改状态码。
- 必须在调用
http.Error()前加if !w.Header().WasWritten()判断 - 否则线上可能看到
http: superfluous response.WriteHeader call这类错误日志 - 更稳妥的做法是封装一个带检查的响应函数,避免每个中间件重复写判断
goroutine 内 panic 无法被外层 recover 捕获
你在 handler 里 go func() { panic("db timeout") }(),主 goroutine 的 defer+recover 完全无感——这个 panic 只属于那个新 goroutine,它崩溃后静默消失,不打日志、不告警、不重试。
- recover 只作用于**当前 goroutine**,这是硬限制,不是 bug
- 异步操作(如发消息、清缓存、写审计日志)必须各自包一层
defer+recover - 或者改用同步方式 + context.WithTimeout 控制,避免无兜底的 goroutine
recover 后别继续执行业务逻辑
panic 往往发生在状态不一致点(比如 map 写到一半、锁未释放、事务未提交),recover 后若继续往下跑,大概率导致数据错乱或死锁。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法是:记录 stack(用
debug.Stack(),别用debug.PrintStack())、返回错误、立即 return - 不要试图“降级”或“fallback”——那该在 panic 前用
if err != nil显式处理 - 尤其注意全局变量、sync.Map、未 close 的 channel 等资源,panic 后它们很可能已处于损坏态


















