Go-06-错误处理、panic、recover 与日志
本节目标:掌握 Go 的错误处理哲学(error 是值不是异常),理解 panic/recover 用法,熟练用
log/slog记录日志。
1. 错误是值(error is a value)
Go 的哲学:错误是普通的返回值,不是控制流。
1 | // error 是内置接口 |
2. 错误处理惯用法
1 | f, err := os.Open(path) |
3. errors.Is 和 errors.As(Go 1.13+)
**errors.Is**:判断错误链中是否包含某个特定错误
1 | _, err := os.Open("missing.txt") |
**errors.As**:从错误链中提取特定类型的错误
1 | type MyError struct { |
fmt.Errorf 用 %w 包装(必须用 %w,不是 %v 或 %s):
1 | if err != nil { |
4. 自定义错误类型
1 | type ValidationError struct { |
5. panic 和 recover
panic:运行时错误,正常代码不应该主动调用。
recover:在 defer 里调用,能”抓住” panic,让程序不崩溃。
1 | func mayPanic() { |
应该 panic 的场景:
- 程序员的 bug(数组越界、空指针解引用)→ Go runtime 自动 panic
- “不可能发生”的状态(如配置缺失、init 失败)
- 框架初始化时的不可恢复错误
不应该 panic 的场景:
- 用户输入错误 → 返回 error
- 文件不存在 → 返回 error
- 网络超时 → 返回 error
- 可预期的失败 → 永远用 error
最佳实践:
1 | // 反例:把可恢复错误当 panic |
6. 实战:重试机制
1 | func retry(attempts int, sleep time.Duration, fn func() error) error { |
7. 用 sentinel 错误 vs 自定义类型
1 | // 简单场景:sentinel |
8. 日志:log/slog(Go 1.21+ 推荐)
旧 log 包功能有限,**新项目都用 log/slog**(结构化日志):
1 | import "log/slog" |
JSON 格式(生产环境推荐):
1 | handler := slog.NewJSONHandler(os.Stderr, nil) |
带 logger 的 context:
1 | logger := slog.With("request_id", "abc123") |
第三方日志库(如果需要更强大):
uber-go/zap:性能最佳rs/zerolog:零分配sirupsen/logrus:老牌但已停止维护
9. 实战:HTTP 服务端统一错误处理
1 | func writeError(w http.ResponseWriter, err error) { |
10. 调试技巧
runtime/debug 打印堆栈:
1 | import "runtime/debug" |
go vet 静态检查:
1 | go vet ./... |
delve 调试器(推荐学习):
1 | go install github.com/go-delve/delve/cmd/dlv@latest |
小结
| 概念 | 用法 | 哲学 |
|---|---|---|
error |
普通返回值 | 显式处理,不要忽略 |
errors.Is |
判断错误类型 | 检查错误链 |
errors.As |
提取错误实例 | 拿到错误细节 |
%w |
包装错误 | 永远用 %w 不要 %v |
panic |
不可恢复错误 | 宁可 crash 也不要数据错乱 |
recover |
在 defer 里”挽救” | 只在必须时才用 |
slog |
结构化日志 | 字段值是结构化的,便于查询 |
核心原则:
- 错误是预期的一部分,用 error 返回
- panic 是程序员的 bug或不可恢复的初始化错误
- 日志要带上下文(request_id、user_id)
- 错误要层层包装(
%w),但不要丢失原始信息
下一节讲单元测试和 TDD,这是 Go 工具链最值得称赞的部分。
- 本文作者: CoderSong
- 本文链接: https://jack-song-gif.github.io/2026/08/27/Go-06-错误处理、panic与日志/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!