跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

Go语言如何做优雅的错误处理_Go语言错误处理最佳实践教程【经典】

Go中error是值而非异常,业务错误应返回error而非panic;panic仅用于致命错误且需recover;错误包装用%w保留原始类型;自定义错误须实现Error()方法;HTTP层需映射错误为状态码并脱敏敏感信息。 Go 里
error
不是异常,别用
panic
拦错 Go 的错误处理逻辑和 Python/Java 完全不同:
error
是值,不是控制流。用
panic
处理业务错误(比如文件不存在、参数校验失败)会导致调用栈炸开、无法被上层恢复,还容易掩盖真正该
panic
的场景(如空指针解引用)。 常见错误现象:
panic: open config.yaml: no such file or directory
被直接抛到 main 函数外,日志里只有一堆 goroutine stack trace,根本看不出是哪个模块在读配置。 业务错误一律返回
error
,由调用方决定重试、降级或透传
panic
只用于程序无法继续运行的致命状态(如初始化失败、全局资源泄漏),且必须配
recover
(仅限顶层 goroutine) 不要为了“简洁”把
if err != nil { return err }
写成
if err != nil { panic(err) }
包装错误要用
fmt.Errorf
+
%w
,不是字符串拼接 错误链(error wrapping)是 Go 1.13 引入的关键能力,目的是保留原始错误类型和上下文。如果用
+ " failed"
fmt.Sprintf
拼接,原始错误就丢了——你再也查不到底层是不是
os.PathError
,也无法用
errors.Is
errors.As
判断。 使用场景:HTTP handler 中调用数据库,DB 层返回
sql.ErrNoRows
,你想加一层 “user not found”,又得保留可判定性。 立即学习 “ go语言免费学习笔记(深入) ”; 正确写法:
return fmt.Errorf("get user %d: %w", id, err)
错误写法:
return fmt.Errorf("get user %d: %s", id, err.Error())
检查包装后的错误是否为某类:
errors.Is(err, sql.ErrNoRows)
—— 只有用了
%w
才有效 提取底层错误实例:
var perr *os.PathError; if errors.As(err, &perr) { ... }
自定义错误类型要实现
Error()
方法,别只靠字段 当需要携带结构化信息(如错误码、trace ID、重试建议)时,光靠包装不够。但很多人只加字段不实现
Error()
,结果
fmt.Println(err)
打印出来是
{Code:404 Message:"not found"}
这种难读格式,而且
errors.Is
无法匹配。 go语言参考手册 中文CHM版 Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译 下载 性能影响:自定义错误对象本身开销极小;但若在高频路径(如每请求都 new 一个)中带大量字段或大结构体,会增加 GC 压力。 必须实现
func (e *MyError) Error() string
,返回人类可读文本 如果需支持判定,额外实现
Unwrap() error
返回底层错误(用于
%w
链式包装) 避免在
Error()
方法里做耗时操作(如 JSON 序列化、网络请求) 示例:
type ValidationError struct { Code int; Field string }; func (e *ValidationError) Error() string { return fmt.Sprintf("validation failed on %s: code %d", e.Field, e.Code) }
HTTP handler 里别裸露底层错误,要映射成状态码和响应体 把
os.IsPermission(err)
redis.Nil
直接塞进 HTTP 响应体,前端拿到的是 “permission denied” 或 “nil”,既不安全也不友好。更糟的是,有些错误(如 DB 连接超时)本该返回 503,却因没判断被当成 400 返回。 兼容性注意:不同中间件对错误的处理方式不同。Gin 默认把 panic 转 500,Echo 需手动注册
HTTPErrorHandler
;标准库
net/http
则完全不管错误,全靠你自己写
if err != nil
分支。 建一个错误映射表:
map[error]int{sql.ErrNoRows: 404, io.ErrUnexpectedEOF: 400, ...}
errors.Is
匹配包装链里的任意一层,再查表定状态码 敏感错误(如 DB 密码错误、文件路径泄露)必须抹掉细节,统一返回泛化消息 别在 middleware 里吞掉所有
error
并返回 500——这会让前端无法区分是客户端错还是服务端错 最常被忽略的一点:错误变量命名。别叫
err
就完事,尤其在多 err 场景下(比如先查缓存再查 DB),
cacheErr
dbErr
能立刻帮你避开「到底哪个 err 没判」的坑。

相关文章