recover必须在defer中调用才有效,仅对同goroutine内panic生效;漏掉defer则recover返回nil无效;应优先用error处理业务错误,panic仅用于程序性崩溃。

recover 必须在 defer 中调用才有效
Go 的 recover 不是全局异常捕获,它只在 defer 函数中调用时才可能生效,且仅对当前 goroutine 中由 panic 触发的终止有作用。如果漏掉 defer,recover 会直接返回 nil,看似“执行了”实则毫无效果。
常见错误写法:
func bad() {
recover() // 这里永远无效
panic("boom")
}
正确姿势:
- 必须把
recover放在defer的匿名函数或命名函数里 - 必须确保
defer注册在panic发生前(即在同个函数内、panic 调用之前) - 多个
defer时,recover所在的那一个需处于 panic 路径上(比如不能被提前 return 跳过)
不要在主 goroutine 外随意 recover
HTTP handler、goroutine 启动点这类地方,盲目加 defer/recover 容易掩盖真正问题。比如:
go func() {
defer func() {
if r := recover(); r != nil {
log.Printf("ignored panic: %v", r)
}
}()
riskyOperation()
}()
这会导致 panic 被静默吞掉,错误日志缺失,监控失灵。更合理的做法是:
立即学习“go语言免费学习笔记(深入)”;
- 仅在明确知道哪些 panic 是可预期且可处理的场景下使用(如模板渲染、JSON 解析等边界操作)
- 对不可控的 panic(如空指针解引用、切片越界),应让程序崩溃并靠外部监控告警,而不是 recover 后继续跑
- 若必须保护子 goroutine,recover 后建议记录完整堆栈(
debug.PrintStack())而非仅打印r
error 类型比 panic 更适合业务错误流
很多开发者一遇到“出错了”就 reflexively 用 panic,但 Go 的哲学是:**业务错误走 error,程序错误才考虑 panic**。比如数据库查询失败、参数校验不通过、第三方 API 返回 404 —— 这些都该返回 error,而不是 panic。
滥用 panic 的后果:
- 调用方无法用常规 error 判断和处理,被迫加 recover,逻辑变复杂
- 无法与标准库(如
net/http、database/sql)的 error 流程对齐 - 测试困难:需要构造 panic 场景,而不是检查 error 值
示例对比:
// ❌ 错误:用 panic 表达业务失败
func GetUser(id int) *User {
if id <= 0 {
panic("invalid user id")
}
// ...
}
// ✅ 正确:返回 error,调用方自然处理
func GetUser(id int) (*User, error) {
if id <= 0 {
return nil, fmt.Errorf("invalid user id: %d", id)
}
// ...
}
自定义 error + Unwrap 实现错误恢复策略链
当需要分层处理错误(比如:网络超时 → 重试;认证失败 → 刷新 token),单纯返回 error 不够,得让错误可识别、可展开。Go 1.13+ 的 Unwrap 接口是关键。
例如设计一个带重试语义的错误类型:
type RetryableError struct {
Err error
Attempts int
}
func (e *RetryableError) Error() string {
return fmt.Sprintf("retryable error after %d attempts: %v", e.Attempts, e.Err)
}
func (e *RetryableError) Unwrap() error { return e.Err }
这样就能用 errors.Is 或 errors.As 判断原始错误类型:
-
errors.Is(err, context.DeadlineExceeded)可识别是否因超时失败 -
errors.As(err, &RetryableError{})可提取重试上下文 - 中间件或 client 层据此决定是否重试、降级或返回特定 HTTP 状态码
注意:不要在 Unwrap 里做副作用(如 log、计数),它可能被多次调用;也不要把 recover 后的 panic 信息硬塞进业务 error,会混淆错误性质。










