error是可预期、可恢复的普通返回值,需手动检查;panic是不可恢复的运行时崩溃信号,会中断goroutine并需defer+recover捕获。

error 是值,不是异常:先搞清 panic 和 error 的区别
Go 里根本不存在“异常处理”这个说法——只有 error 和 panic 两种完全不同的机制。混淆它们是新手最常踩的坑。
-
error是函数返回的普通值,比如os.Open返回的第二个值。它代表“可预期、可恢复”的问题,比如文件不存在、参数校验失败、网络超时。你必须手动检查err != nil,不检查也不会崩,但逻辑就错了。 -
panic是运行时崩溃信号,比如访问 nil 指针、切片越界、手动调用panic("xxx")。它会立刻中断当前 goroutine,除非你在defer里用recover()捕获,否则整个程序就挂了。
框架里的“异常处理”其实只做两件事:一是统一包装和传播 error,二是用 recover() 拦住不该冒出来的 panic。别指望它帮你自动 catch 一个没检查的 err。
HTTP handler 中怎么安全地处理 error(以 net/http 为例)
标准库没有中间件概念,所以错误要靠显式传递 + 提前 return,而不是 try-catch 那套。
- 每个 handler 函数里,只要调用可能出错的函数(如
json.Unmarshal、db.QueryRow),就必须立刻判断err并决定是返回 HTTP 错误码,还是记录日志后提前退出。 - 别在 handler 末尾统一处理 error——因为中间某步出错后,后续代码可能已执行副作用(比如发了邮件、改了状态),再处理就晚了。
- 常见错误写法:
if err != nil { log.Println(err); http.Error(...) }—— 这样没return,后面代码还会继续跑,极易引发 panic 或数据错乱。
正确姿势是:
func handleUser(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
if id == "" {
http.Error(w, "missing id", http.StatusBadRequest)
return // ← 关键:立刻退出
}
user, err := db.GetUser(id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
http.Error(w, "user not found", http.StatusNotFound)
} else {
log.Printf("db error: %v", err)
http.Error(w, "internal error", http.StatusInternalServerError)
}
return // ← 又一个 return
}
json.NewEncoder(w).Encode(user)
}
为什么框架要用 errors.Is / errors.As 而不是字符串匹配
直接比较 err.Error() == "file not found" 是反模式。标准库错误(如 os.IsNotExist)和自定义错误都支持语义化判断,这才是 Go 的设计意图。
-
errors.Is(err, os.ErrNotExist):判断是否是“文件不存在”这一类错误,不管它是不是被包装过(比如fmt.Errorf("read config: %w", err))。 -
errors.As(err, &os.PathError{}):提取底层具体错误类型,用于获取路径、操作名等上下文信息。 - 哨兵错误(如
io.EOF)必须用==判断,因为它是一个导出的变量,不是字符串。
用字符串匹配的问题:一旦错误消息变英文、加标点、本地化,你的判断就失效;而且无法区分同类错误的不同原因(比如“permission denied”可能是目录不可读,也可能是文件不可写)。
Gin/Echo 等框架的 recover 中间件到底在防什么
它只防 panic,不防 error。很多人误以为装了 recover 中间件就“万事大吉”,结果业务逻辑里漏判 err 导致空指针 panic,才被它兜住——这其实是掩盖 bug,不是健壮性。
- 真正该用
recover()的场景极少:比如第三方库内部 panic、模板渲染时语法错误、JSON 序列化深度超限等不可控情况。 - 中间件里
recover()后,建议至少记录 panic 栈(debug.PrintStack()),否则你永远不知道哪行代码崩了。 - 别在中间件里吞掉 panic 后假装成功返回 200——应该统一返回 500,并带上 trace ID 方便排查。
最易被忽略的一点:recover() 只在 defer 函数里有效,且只能捕获当前 goroutine 的 panic。HTTP handler 是独立 goroutine,所以中间件的 defer 才能生效;但如果你在 goroutine 里开新协程又没加 recover,那 panic 依然会让服务进程退出。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











