panic仅用于无法恢复的致命错误,如初始化失败;其他场景应返回error,避免在HTTP handler、类型断言、map/slice访问中误用panic。

什么时候该用 panic,什么时候不该用
panic 只应在程序遇到**无法恢复的致命状态**时使用,比如初始化阶段读不到必需配置、TLS 证书加载失败、数据库连接池构建失败。这些错误继续运行只会让后续逻辑更不可靠。其他所有能返回 error 让调用方处理的场景,都不该用 panic。
常见误用包括:
- HTTP handler 中对用户输入校验失败就调用
panic - 第三方 API 返回 404 或 500 就直接
panic - 把
json.Unmarshal因结构体 tag 错误导致的 panic 当作运行时错误来 recover - 用
fmt.Sprintf格式错误触发 panic(应提前静态检查或用fmt.Sprintf的替代方案)
用 error 替代 panic 的典型写法
Go 标准库全部采用显式 error 返回,你写的函数也应如此。关键不是“少写一行 if”,而是把控制权交还给调用者。
以下场景统一返回 error:
立即学习“go语言免费学习笔记(深入)”;
- 输入参数校验失败 → 返回
fmt.Errorf("invalid %s: %v", field, value) - I/O 操作失败 → 直接透传底层
error,必要时用errors.Wrap(err, "read config")补充上下文 - 业务规则不满足(如余额不足)→ 定义自定义 error 类型,实现
Is方法便于判断:var ErrInsufficientBalance = errors.New("insufficient balance") - JSON 解析失败 → 用
json.Unmarshal原生返回的error,而不是包一层panic再recover
HTTP 中间件里 recover 怎么写才有效
HTTP handler 默认运行在独立 goroutine 中,中间件必须在该 goroutine 内部、panic 发生前就注册 defer + recover,否则整个程序会崩溃。
正确写法要同时满足三个条件:
-
recover()必须在defer函数中调用,且立刻检查返回值是否为非 nil - 恢复后必须主动写 HTTP 响应(如
http.Error(w, "Internal Server Error", http.StatusInternalServerError)),否则连接挂起 - 必须记录日志并打印堆栈:
log.Printf("panic recovered: %v\n%s", r, debug.Stack())
错误写法示例:defer recover()(没取返回值)、go func() { defer recover() }()(跨 goroutine 无效)、在中间件外层 goroutine 中 recover(完全捕获不到 handler 内 panic)。
类型断言和 map/slice 访问如何避免 panic
很多 panic 来自运行时错误,但其实完全可预防:
- 接口类型断言必须用安全模式:
v, ok := i.(T),再检查ok;不要直接写i.(T) - 访问 map 元素前先判断是否存在:
if v, ok := m[key]; ok { ... },而不是直接m[key]后再判断零值 - 切片访问前检查长度:
if len(s) > idx { _ = s[idx] },避免index out of range - 指针解引用前判断是否为 nil:
if p != nil { use(*p) }
这些都不是“异常”,而是可控的边界检查。一旦漏掉,线上出现 panic: runtime error: invalid memory address,基本意味着代码没经过充分路径覆盖。


















