go错误处理应避免panic/recover处理业务错误,必须显式检查error,用%w包装支持errors.is/as,敏感信息不硬编码,http错误需映射为结构化响应与状态码。

Go 的错误处理没有“规范”可言,只有被广泛接受的实践;强行套用其他语言的 try/catch 模式反而会导致代码更难维护、更易出错。
为什么不能用 panic/recover 做业务错误处理
panic 是为程序无法继续运行的严重异常设计的(比如 nil 指针解引用、切片越界),不是用来表达“用户名已存在”“库存不足”这类预期中的业务失败。
常见错误现象:recover() 被滥用在 HTTP handler 里吞掉所有错误,结果日志没打、监控没触发、上游收不到 400 状态码,只看到空响应或 500。
- recover 无法跨 goroutine 捕获 panic,一旦在子 goroutine 中 panic,主流程照样崩溃
- 每次 recover 都要手动清空 panic 值、检查类型、还原状态,极易遗漏
- HTTP handler 中用 defer + recover 会掩盖真实错误位置,stack trace 断在
runtime.gopanic,而不是你写错的那行db.QueryRow
error 返回值必须显式检查,不许忽略
Go 的 error 是接口类型,函数返回 error 就意味着调用者有责任判断它是否为 nil。忽略它等于默认“一定成功”,这是绝大多数线上 bug 的源头。
典型反模式:json.Unmarshal(data, &v) 后不检查 error,导致 v 是零值却继续往下用。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 用
if err != nil立即处理或返回,不要攒到函数末尾统一判 - 不要用
_忽略 error,除非你明确知道这个 error 永远不会发生(比如fmt.Sprintf) - 对第三方库调用尤其要小心:有些 SDK 的 error 返回是包装过的(如
aws-sdk-go的awserr.Error),直接== nil可能误判
自定义错误要支持 errors.Is / errors.As,别只用字符串匹配
早期很多人用 strings.Contains(err.Error(), "timeout") 判断超时,这脆弱得离谱:只要错误信息微调(加个标点、换种语序),逻辑就失效。
正确做法是让自定义错误实现 Unwrap() 方法,并用 errors.Is(err, myTimeoutErr) 或 errors.As(err, &e) 判断。
var ErrTimeout = fmt.Errorf("operation timeout")
func DoSomething() error {
if timedOut {
return fmt.Errorf("do something: %w", ErrTimeout) // 注意 %w
}
return nil
}
// 调用方
if errors.Is(err, ErrTimeout) {
log.Warn("skip retry on timeout")
}
- 用
%w而非%s包装错误,才能让errors.Is和errors.As正常工作 - 不要在错误信息里硬编码用户 ID、订单号等敏感字段,避免日志泄露;要用结构化字段(如 zap.String("order_id", id))单独记录
- 如果错误需要携带上下文(如重试次数、请求 ID),优先用
fmt.Errorf("...: %w", err)包装,而不是拼接字符串
HTTP handler 中的错误要映射成合适的状态码和响应体
Go 标准库的 http.Error 只能返回文本,不利于前端解析。业务系统应该统一错误响应格式,并把 error 映射为可预期的 JSON 结构。
关键点不是“怎么封装响应”,而是“怎么从 error 推出 status code”:
- 使用
errors.Is(err, sql.ErrNoRows)→404 - 使用
errors.Is(err, ErrValidation)→400 - 使用
errors.Is(err, context.DeadlineExceeded)→408或504(取决于超时来源) - 所有未识别的 error 默认走
500,但必须打全 stack trace(用debug.PrintStack()或runtime/debug.Stack())
最常被忽略的是:错误日志里没带 traceID,导致排查时根本串不起一次请求的完整链路。哪怕不用 OpenTelemetry,至少在每个 error 日志里补上 zap.String("trace_id", r.Header.Get("X-Trace-ID"))。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










