可以详细讲一讲什么时候用os.Exit(1),什么时候用panic,什么时候只是返加err?
从 Java/PHP 转到 Go,理解这三者的界限是进阶的核心。Java/PHP 习惯用 try-catch 或直接报错,而 Go 讲究"各司其职"。
1. 什么时候返回 error?
场景:99% 的业务逻辑。
在 Go 中,错误被视为正常的数据流。只要是"预料之中"的问题,都应该返回 error。
- 例子:数据库查不到数据、用户密码错误、输入参数不合法、网络请求超时。
- 原则:不要因为这些事让程序死掉。你应该把
err一层层往上传,直到Handler层,然后调用你写的res.ApiError返回给前端。
2. 什么时候用 panic?
场景:真正的异常(不该发生的事)。
panic 意味着程序遇到了逻辑死胡同,无法继续执行当前的逻辑。
- 例子:
- 代码 Bug:比如访问了
nil指针、数组越界。 - 核心环境崩溃:程序运行到一半,发现某个必须存在的配置文件被删了。
- 代码 Bug:比如访问了
- 处理:在 Web 开发中,我们通常会在最外层用 Recovery 中间件 把
panic拦住,转成 500 错误,防止整个进程挂掉。
3. 什么时候用 os.Exit(1)?
场景:启动即崩溃(初始化阶段)。
这是最狠的一招,直接杀掉进程,不给任何后悔的机会(连 defer 都不会执行)。
- 例子:
main函数启动时,发现数据库连不上、端口被占用、环境变量没配置。 - 原则:如果程序的基础设施都不健全,跑起来也是带病工作,不如直接"自杀"。它只应该出现在
main.go或初始化的那一刻。
4. 必须补上的"保命符":Recovery 中间件
你现在"裸奔"没加 Recovery,万一某个接口的代码写了 a := nil; fmt.Println(a.Name),你的整个后端就直接没了。
建议把这个中间件加在 RequestLogger 之前:
// middleware/recovery.go
func Recovery(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// 1. 记录详细的崩溃日志
infra.Log("panic").Error("程序崩溃了",
"err", err,
"uri", r.RequestURI,
)
// 2. 给前端回个 500,别让请求一直挂着
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
// 这里可以用你之前的 res.ApiError
json.NewEncoder(w).Encode(map[string]any{
"code": 500,
"msg": "服务器内部错误,请稍后再试",
})
}
}()
next.ServeHTTP(w, r)
})
}
总结建议:
- 启动时:连不上 DB 这种死罪,用
os.Exit(1)或log.Fatal。 - 写业务时:遇到问题(如查不到用户),一律
return err。 - 万一写出 Bug:靠
Recovery中间件兜底,把panic变成日志和 500。