Go 里什么时候可以“panic”?

“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 自曝。”

用得好,它是安全网;用不好,它是定时炸弹💣。


0
0
0
0
评论
未登录
暂无评论