Golang 从入门到放弃 -0x13
panic 与 recover 的适用边界,以及在服务入口做异常兜底的思路。
如果说 error 是 Go 里“正常世界里的出错”,那 panic 更像是“桌子被掀了”。它不是不能用,但一定要知道什么时候该掀,什么时候只需要把椅子扶正。
panic 是什么
panic 发生时,当前函数会立刻停止往后执行,开始一层层向上展开调用栈,顺便执行沿途的 defer。如果没人接住,程序就直接退出。
func main() {
panic("something bad happened")
}这玩意一跑起来,效果非常直接,基本不给你留太多面子。
什么场景适合 panic
大体上可以抓一个原则:当程序已经处于一种“不适合继续运行”的状态时,panic 才有意义。
比如:
- 程序启动时关键配置缺失,而且没有合理降级方案
- 内部状态被破坏,继续跑只会产生更离谱的后果
- 你在写底层库,发现调用方传了绝对不该出现的非法状态
如果只是用户参数填错了、数据库暂时连不上、远程接口超时了,这些通常都不该 panic。那属于正常业务错误,老老实实返回 error 就行。
recover 怎么用
recover 只有放在 defer 里才有用,而且只能接住当前 goroutine 里的 panic。
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recover from:", r)
}
}()
panic("boom")
}这样程序就不会直接崩出去,而是会先走到你的恢复逻辑。
最常见的用途:保护服务入口
在 HTTP 服务里,recover 很常拿来做最后一层兜底。因为你总不希望某个 handler 因为一行意外 panic,把整个服务干掉。
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Println("panic:", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}这个思路很务实:内部日志记详细点,对外只回一个稳定的 500。
不要把 panic 当 if err != nil 的替代品
这是新手最容易犯的错之一。看见出错就 panic,看着写起来好像很痛快,实际上是在放弃错误处理设计。
一句话总结:
- 可预期的错误,用
error - 不可恢复的异常状态,才考虑
panic
小结
这一章不需要你天天写 panic,反而希望你写得少一点:
panic表示异常中断,不是常规业务流程。recover必须放在defer里才有意义。- HTTP 服务入口很适合做一层 recover 兜底。
- 别拿 panic 代替正常错误处理。
下一章我们聊 context。这玩意初看有点烦,但现代 Go 服务基本绕不过去。