recover不能实现业务逻辑降级——它只捕获panic,而降级必须处理error;正确做法是显式判断error并调用统一签名的fallback函数,recover仅适用于第三方库panic等极少数止损场景。

recover 不能实现业务逻辑降级——它只捕获 panic,而降级必须处理 error。 把 recover 当作 fallback 用,90% 的场景会静默失败或二次 panic。
recover 只对 panic 有效,对正常 error 完全无感
HTTP 请求超时、数据库返回 sql.ErrNoRows、JSON 解析失败——这些全是 error 类型,recover() 根本不会触发。Go 的错误模型是显式返回 + if 判断,不是靠 panic/recover 拦截。
- 常见翻车:在 handler 里写
defer func() { recover() }(),然后期望它能兜住http.Get失败 → 实际什么也不做,err != nil被忽略,后续代码 panic - 真正该做的:检查
err,再决定是否调用 fallback 函数,例如:if err != nil && ShouldFallback(err) { return fallback() } - panic 是“程序已无法继续”的信号(如空指针解引用、越界访问),不是“服务暂时不可用”的替代品
想做降级?得用显式 error 判断 + 独立 fallback 函数
降级本质是「主调用失败 → 条件判断 → 执行备用路径」,和 recover 没有技术耦合。关键在于函数签名统一、上下文隔离、错误分类明确。
- fallback 函数必须和主函数类型一致,推荐定义:
type Call[T any] func(ctx context.Context) (T, error) - 主调用和 fallback 必须各自带独立
context.Context,避免 fallback 被主 ctx 提前 cancel - 判定是否降级不能只看
err != nil,要区分错误类型:net.Error.Timeout()可降级,errors.Is(err, ErrUnauthorized)不该降级 - 示例片段:
func DoWithFallback(ctx context.Context, main, fallback Call[string]) (string, error) { result, err := main(ctx) if err == nil { return result, nil } if !ShouldFallback(err) { return "", err } return fallback(ctx) }
recover 唯一适合的“软着陆”场景:防止 panic 波及整个 goroutine
它只在极少数边界情况有用:比如你依赖的第三方库内部 panic(你又没法改源码),或者模板渲染、反射调用等不可控执行点。此时 recover 是止损,不是降级。
- 必须写成:
defer func() { if r := recover(); r != nil { log.Printf("panic: %v", r) } }()—— 缺少if r != nil或写成defer recover()都无效 - recover 后绝不能继续用原状态:slice 已越界、map 正在并发写入、channel 已 close → 这些破坏不可逆
- HTTP handler 中加 recover 是为了单请求隔离,不是为了返回 fallback 数据;正确做法是
http.Error(w, "500", 500),而不是尝试重试 DB 查询 - 子 goroutine 必须自己包 recover:
go func() { defer func() { recover() }(); doWork() }(),主线程的 defer 对它完全无效
最容易被忽略的一点:recover 后的代码不是“恢复业务”,而是“清理并退出”。任何试图在 recover 分支里继续调用业务函数、写日志到同一文件句柄、或向已 panic 的 struct 字段赋值的操作,都在制造隐藏崩溃点。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











