正确做法是显式返回http错误而非panic:handler中用writeheader(statusnotfound)和json.encode报错,工具函数应返回error而非panic,越界访问等须提前校验,goroutine内需手动recover或用errgroup.group。

HTTP handler 里直接 panic("not found")
这会让整个服务进程崩溃,而不是返回 404。Go 的 http.ServeMux 和主流框架(如 Gin、Echo)都不 recover 你手写的 panic,一旦触发,goroutine 退出、连接中断、日志断档,监控看到的只是“connection reset”。
- 正确做法:在 handler 中检查业务条件,用
w.WriteHeader(http.StatusNotFound)+json.NewEncoder(w).Encode(err)显式响应 - 错误现象:本地测试看似正常,上线后某次参数异常导致全量请求 502,但 error log 里空空如也
- recover 无效:即使你在 handler 外层加了
defer func() { recover() }(),也无法捕获——因为 panic 发生在你的业务逻辑里,而 Go 的 HTTP server 启动的是新 goroutine,它不共享你的 defer 链
库函数中对非法参数 panic 而非返回 error
比如一个解析 ID 的工具函数 ParseID(string) (int64, error),传入空字符串或乱码时,你写 if s == "" { panic("empty ID") } ——这就越权了。调用方可能是 Web 请求、CLI 参数、后台任务,它需要决定是重试、降级还是记录告警,不是由你替它终止程序。
- 违反 Go 生态契约:所有公开函数都应遵循 “don’t panic, return error” 的隐性接口约定
- 无法被测试覆盖:
testify/assert.Panics可以测,但真实调用链中一旦 panic,上层没写 recover 就崩;写了 recover 又让错误流变得不可预测 - 替代方案:提前校验 +
return 0, fmt.Errorf("invalid ID: %q", s),把决策权交还给调用方
用 panic 替代边界检查:slice[i]、map[k]、*T 操作前不判空
这不是“省代码”,是把运行时错误当控制流用。Go 的 panic 在这里不是 bug 提示,而是你主动放弃防御责任。
-
slice[i]越界 panic:应先写if i = len(s) { return nil, errors.New("index out of range") } -
m[k]查不到 key 就 panic:map 访问本身不 panic,只有对nil map赋值才 panic;查不到是常态,该用v, ok := m[k]分支处理 -
*p解引用前不检查p != nil:这不是“用户输入错误”,是你自己漏了空指针防护,应提前返回 error 或 panic 前加注释说明这是开发期断言(仅限 internal 包)
跨 goroutine 的 panic 不拦截,以为会自动传播
在 go func() { ... }() 里 panic,主 goroutine 完全感知不到。它不会像 Java 的 UncaughtExceptionHandler 那样兜底,也不会写入标准 error log——这个 goroutine 静默死亡,堆栈丢失,只剩一条 “fatal error: all goroutines are asleep” 的终局提示(如果恰好是最后一个)。
立即学习“go语言免费学习笔记(深入)”;
- 必须手动封装:每个 goroutine 入口加
defer func() { if r := recover(); r != nil { log.Printf("panic in worker: %+v", r) } }() - 更推荐用
errgroup.Group:它内部已做了 recover,并聚合所有 error 到g.Wait()返回值 - 注意限制:
recover()只在 defer 函数中有效,且只能捕获当前 goroutine 的 panic;别指望在 main 里 recover 子 goroutine 的 panic










