应避免用 panic 处理预期错误,它会终止 goroutine 且难以恢复;error 支持包装、传播与分类处理,适用于业务异常;panic 仅限启动失败、初始化错误或严重代码缺陷等不可恢复场景。

panic 是运行时崩溃,不是错误处理
用 panic 处理本该预期的失败(比如文件不存在、网络超时、JSON 解析失败),等于把程序异常当成控制流——它会立即终止当前 goroutine,触发 defer 链,如果没被 recover 捕获,整个程序就挂了。这不是“处理”,是放弃。
常见错误现象:panic: runtime error: invalid memory address 或自定义 panic 后服务突然 500,日志里找不到上下文,监控看不到错误分类。
- HTTP handler 里对
http.Get结果不做err != nil判断,直接panic(err)→ 单个请求崩掉整个 goroutine,但连接可能还挂着,超时堆积 - 数据库查询返回
sql.ErrNoRows,不检查就panic→ 业务上“查不到”是合法状态,不是故障 - 把
json.Unmarshal的错误转成panic→ 前端传了个字段类型错的 JSON,API 直接 crash,而不是返回 400
error 类型天然支持组合与传播
error 是接口,可以包装、嵌套、延迟判断。你能在任意一层决定“现在处理”还是“继续往上抛”,而 panic 只能向上逃逸,无法在中间层做重试、降级或日志增强。
使用场景:微服务调用链中,下游返回错误,你可能要加 trace ID、记录耗时、触发告警,但不中断流程;或者根据错误类型走 fallback 逻辑——这些都依赖 error 的可传递性。
立即学习“go语言免费学习笔记(深入)”;
- 用
fmt.Errorf("failed to fetch user: %w", err)包装,保留原始错误链,调试时能errors.Is或errors.As判断 - HTTP 中间件统一处理
error返回 JSON 错误体;若用panic,就得每层加defer + recover,代码重复且易漏 -
os.Open返回*os.PathError,你可以用errors.Is(err, os.ErrNotExist)做精确分支,panic后只剩字符串匹配,脆弱
recover 不是 error 的替代品
有人觉得“我用 defer + recover 捕住 panic,再转成 error,不就一样了?”,但这是在模拟 error 行为,却付出了更高成本:栈被展开、goroutine 状态丢失、性能下降(panic/recover 比 if err != nil 慢 10–100 倍),而且 recover 只能捕获当前 goroutine 的 panic,跨 goroutine 失效。
参数差异:recover() 返回 interface{},你得自己断言、转换、补上下文;而 error 从函数签名就明确,IDE 能提示,静态分析能检查是否被忽略(如 errcheck 工具)。
- 在
go func() { ... }()里 panic,外层 recover 不到 → 错误静默丢失 - recover 后若不重新 panic 或返回 error,相当于吞掉错误,后续逻辑继续跑,状态可能不一致
- 测试时难 mock:你得写 recover 测试用例,而 error 只需构造返回值,单元测试干净利落
哪些情况真的该用 panic
仅限三种:程序启动阶段不可恢复的配置错误(如监听端口被占)、包初始化失败(init() 里)、或开发者明显写错了(比如传了 nil 指针给绝不接受 nil 的函数)。这些不是“运行时错误”,是“代码缺陷”。
性能 / 兼容性影响:标准库只在极少数地方用 panic,比如 sync.(*Mutex).Lock 对已 lock 的 mutex 再 lock(说明用法错),或 reflect.Value.Interface() 对 invalid value 调用。它们的 panic 是防御性断言,不是错误处理路径。
- Web 框架路由注册时发现重复 path,
panic—— 启动就该暴露,不该让服务带病运行 - 全局配置解析失败(如 YAML 语法错),
panic—— 后续所有逻辑都依赖它,无法降级 - 绝不要在 HTTP handler、数据库事务、消息消费循环里用
panic代替if err != nil
if err != nil 时,想清楚这个 err 是“应该挡住用户”的业务异常,还是“必须立刻停机”的系统缺陷——边界模糊的地方,恰恰是设计最该发力的地方。










