“Don’t panic.” —— Go 谚语
但……如果我真的想 panic 呢?
在 Go 的世界里,panic() 就像厨房里的灭火器:平时你不会用它炒菜,但如果油锅着火了,你肯定得拉它一把。今天我们就来聊聊:Go 里什么时候 panic 是合理的?
🤔 为什么大家总说 “别 panic”?
先看个日常例子:
// 获取某个时区的当前时间
func timeIn(zone string) (time.Time, error) {
loc, err := time.LoadLocation(zone)
if err != nil {
return time.Time{}, err // 👈 正常做法:返回 error
}
return time.Now().In(loc), nil
}
这是 Go 的“标准姿势”:错误是值,不是灾难。你把错误交给调用者,让他决定是重试、记录、还是直接退出。
但如果你写成这样:
func timeIn(zone string) time.Time {
loc, err := time.LoadLocation(zone)
if err != nil {
panic(err) // 💥 直接炸了!
}
return time.Now().In(loc)
}
程序会立刻停止,打印堆栈,然后退出——连日志都来不及写。这在生产环境简直是“自爆卡车”。
所以,Go 社区才反复强调:别 panic!
🧠 那……什么时候可以 panic?
关键在于区分两类错误:
| 错误类型 | 说明 | 举例 |
|---|---|---|
| 操作型错误(Operational Errors) | 程序运行中可能发生的正常异常 | 网络超时、数据库连接失败、用户输错密码 |
| 程序员错误(Programmer Errors) | 代码逻辑有 bug,本不该发生 | 数组越界、除零、nil 指针解引用 |
✅ 操作型错误 → 必须返回 error
❌ 程序员错误 → 可以考虑 panic
💡 简单记:“用户能搞砸的,别 panic;你写错的,可以 panic。”
🛠️ 什么时候 panic 是合理选择?
场景 1️⃣:不可恢复的程序员错误
比如:
// 从 context 中取用户信息(假设中间件已确保存在)
func contextGetUser(r *http.Request) user.User {
u, ok := r.Context().Value(userKey).(user.User)
if !ok {
panic("context 中居然没有 user!中间件漏了?") // 🚨 这是 bug!
}
return u
}
✅ 优势:避免每个调用点都写
if err != nil,代码更清爽。
⚠️ 前提:你100% 确信这个值一定存在(比如由认证中间件注入)。
📌 图示建议:画一个 HTTP 请求流程图,标出“认证中间件 → handler → contextGetUser”,并用红色爆炸图标标出 panic 路径。
场景 2️⃣:启动阶段配置错误
func getEnvInt(key string, def int) int {
s, exists := os.LookupEnv(key)
if !exists {
return def
}
n, err := strconv.Atoi(s)
if err != nil {
panic(fmt.Sprintf("环境变量 %s 不是整数: %v", key, err)) // 🚨 启动就挂
}
return n
}
// main.go
port := getEnvInt("PORT", 8080) // 如果 PORT="abc",直接 panic
✅ 优势:程序根本不能用错误配置跑起来,不如早点死,别污染日志或数据库。
🔧 适用时机:main 函数初始化阶段,日志/监控还没就绪时。
场景 3️⃣:安全兜底的“守门员”
var safeCol = regexp.MustCompile(`^[a-z_]+$`)
type Sort struct {
Column string
Asc bool
}
func (s Sort) OrderBySQL() string {
if !safeCol.MatchString(s.Column) {
panic("危险的排序字段!疑似 SQL 注入!") // 🛡️ 最后一道防线
}
dir := "ASC"
if !s.Asc { dir = "DESC" }
return fmt.Sprintf("ORDER BY %s %s", s.Column, dir)
}
✅ 优势:即使上游校验漏了,这里也能阻止攻击。
💬 这不是“处理错误”,而是“防止灾难”。
📌 图示建议:画一个“用户输入 → 校验层 → SQL 生成”流程,panic 作为红色警报挡在最后。
❌ 什么情况绝对不能 panic?
- 你写的库被别人 import(别人不希望你直接 kill 他们的程序)
- 处理用户输入(比如表单、API 参数)
- 网络/IO 操作(超时、断连等)
- 任何“可能”在生产环境发生的错误
🧪 测试 tip:用
recover()捕获 panic 写单元测试很麻烦,而if err != nil一目了然。
✅ 总结:panic 使用 Checklist
| 条件 | 可以 panic? |
|---|---|
| 这是程序员逻辑错误(比如 nil 解引用) | ✅ |
| 错误本不该在生产出现 | ✅ |
| 返回 error 会让代码变得极其啰嗦 | ✅(谨慎) |
| 程序处于启动初始化阶段 | ✅ |
| 涉及安全防护(如 SQL 注入) | ✅ |
| 用户输入导致的错误 | ❌ |
| 你正在写一个公共库 | ❌ |
| 错误可恢复(重试/降级) | ❌ |
🎯 一句话记住:
“panic 不是错误处理,而是 bug 自曝。”
用得好,它是安全网;用不好,它是定时炸弹💣。
