Golang 从入门到放弃 -0x12
错误包装、errors.Is/As 和自定义错误类型,把 error 真正用得像样。
前面我们已经知道 Go 里处理错误的基本姿势是 if err != nil。但真正的项目一复杂,错误就不只是“有或没有”这么简单了。它还牵涉到:错误从哪儿来的、要不要包装、上层怎么判断、该不该暴露给用户。
先从包装错误说起
很多时候你收到一个底层错误,但你还想给它补一点上下文。这个时候 fmt.Errorf 配合 %w 很好用。
func loadConfig(path string) error {
_, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read config %s: %w", path, err)
}
return nil
}这样上层看到的就不再只是一个冷冰冰的“file not found”,而是知道“读配置文件的时候出错了”。
errors.Is:判断是不是某类错误
包装了以后,直接拿 == 去比往往就不靠谱了。这时候该用 errors.Is。
if errors.Is(err, os.ErrNotExist) {
fmt.Println("文件不存在")
}这很适合判断一些已知哨兵错误。比如文件不存在、超时、上下文取消之类的情况。
errors.As:拿到具体错误类型
如果你定义了自己的错误类型,或者想从一串包装错误里挖出某个具体类型,可以用 errors.As。
type BizError struct {
Code int
Msg string
}
func (e *BizError) Error() string {
return e.Msg
}
var bizErr *BizError
if errors.As(err, &bizErr) {
fmt.Println("code:", bizErr.Code)
}这个场景在 API 服务里很常见。你可能内部一路都在传 error,但到了 HTTP 层想知道它是不是业务错误,是的话回 4xx,不是的话回 500。
自定义错误类型别为了高级而高级
不是每个错误都值得定义一个 struct。很多时候一条 errors.New 或 fmt.Errorf 足够了。真正适合自定义类型的,通常是那些你后面还要根据字段做分支处理的错误。
如果只是想把一句话说完整,那就别把错误设计成论文答辩。
别吞错误
有一种非常危险的代码味道,叫“出错了但假装没看见”。
data, _ := os.ReadFile("config.json")下划线不是原罪,但别拿它掩耳盗铃。你现在图省的那一秒,未来很可能要用半小时排查来还。
用户看到的错误,和日志里的错误,不一定是一回事
系统内部的错误信息往往很细,适合日志排查;返回给前端或用户的信息,通常要更克制一些。别把数据库连接串、SQL 语句、内部路径一股脑返回出去。
一个比较健康的模式是:
- 日志里记录详细错误和上下文
- 接口里返回简洁、稳定的错误描述
小结
这一章其实是在给错误处理补上“工程味”:
%w用来包装错误和保留原始链路。errors.Is判断类别,errors.As提取类型。- 不是所有错误都要自定义类型。
- 别为了图快把错误直接吞掉。
下一章我们聊 panic 和 recover。这俩东西不常用,但一旦用错,效果往往很炸裂。