go中无传统异常捕获,panic/recover仅是可控兜底机制:recover必须在defer中、同goroutine且panic后立即调用,常用于http中间件或cli入口封装;业务错误应返回error,仅底层不可恢复错误才用panic,并建议用自定义类型区分。

Go里没有传统意义上的“异常捕获”,panic和recover不是try/catch
Go不提供try/catch语法,所谓“异常捕获模块”本质是围绕panic和recover做可控兜底。直接在任意位置调用recover()无效——它只在defer函数中、且当前goroutine刚发生panic时才返回非nil值。
常见错误现象:recover()返回nil,看似“捕获失败”,其实是调用时机或位置不对。
- 必须在
defer函数内调用recover() - 必须在
panic发生后的同一goroutine中执行(跨goroutine的panic无法被其他goroutine的recover捕获) - 不能在普通函数里裸调
recover()——它不会拦截后续可能发生的panic
用defer+recover封装成可复用的“兜底函数”
把recover逻辑抽成带日志、错误分类、上下文注入的闭包,比每次手写defer func(){...}()更可靠。
典型场景:HTTP handler、CLI命令执行、定时任务入口点。
示例(简化版):
func WithRecovery(handler func()) {
defer func() {
if r := recover(); r != nil {
// 这里可以加log、上报、类型判断
log.Printf("panic recovered: %v", r)
}
}()
handler()
}
- 不要试图在
WithRecovery里“恢复”业务状态——panic已破坏栈,只能记录和退出 - 如果
handler本身启动了新goroutine,那些goroutine里的panic不会被这个recover捕获 - 避免在
recover后继续执行原逻辑——多数情况下应直接返回或终止当前流程
区分panic来源:业务主动panic vs 程序bug
Go项目里混用panic很危险:有些是开发者故意触发的“业务中断”(如配置缺失),有些是空指针/越界等真正bug。统一recover会掩盖后者。
建议做法:业务错误走error返回,仅底层不可恢复错误(如初始化失败)才用panic;若必须用panic传递业务意图,统一用自定义类型:
type BusinessPanic struct{ Msg string }
func (e BusinessPanic) Error() string { return e.Msg }
// 触发
panic(BusinessPanic{"config not found"})
// 捕获时区分
if r := recover(); r != nil {
switch x := r.(type) {
case BusinessPanic:
log.Warnf("business panic: %s", x.Msg)
default:
log.Errorf("unexpected panic: %+v", r)
// 可选:dump stack、通知告警
}
}
- 不要用字符串匹配判断
panic类型——类型断言更安全 - 第三方库抛出的
panic(如json.Unmarshal遇到非法输入)属于程序bug范畴,不应被静默吞掉 - 测试时可用
assert.Panics(testify)验证预期panic是否发生
HTTP服务中全局panic兜底的正确姿势
net/http的Handler默认不捕获panic,会导致整个goroutine崩溃,连接中断,但服务器仍存活——用户看到500,你却没日志。
必须在每个handler入口或中间件里做recover,而非依赖全局钩子(Go HTTP没有类似Express的uncaughtException事件)。
推荐结构:
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
log.Printf("HTTP panic on %s %s: %+v", r.Method, r.URL.Path, r)
}
}()
next.ServeHTTP(w, r)
})
}
- 别在
RecoveryMiddleware里尝试修改响应头或写body后再panic——WriteHeader可能已调用,再写会报http: multiple response.WriteHeader calls - 如果用了
gin/echo等框架,优先用它们内置的Recovery()中间件——它们已处理了writer锁定、header冲突等问题 - gRPC服务同理:需在
UnaryInterceptor或StreamInterceptor里做recover,且要区分Status码返回
recover不是异常处理机制,而是最后防线。它没法让程序“继续运行”,只能帮你少丢日志、少让用户困惑。真正健壮,靠的是少用panic、多用error、提前校验、隔离风险goroutine——这些地方比怎么写recover更重要。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











