Go 1.13 之后的 error 检查
文章目录
起步
如果说 Go 有很多诟病的地方,那么 Go 中 error 的处理一定可以挤进吐槽榜单前十。既然 try
语句提议被一拒再拒,我们也只好用着古老的 if 筛选错误。Go 官方并非没有意识到 error 的鸡肋问题,于是在 Go 1.13 提出了新解决方案,总的说来就是“三个 api + 一个格式占位符”。
error 从何而来
在 Go 中,error 从何而来呢?熟练使用 Go 的人一定知道以下几种方式:
- errors.New
- fmt.Errorf
- 直接返回函数调用后得到的 error
- 定义一个结构体,实现 error 接口(
type error Interface { Error() string }
)
以“打开一个文件”为例,按照上面的处理方式代码可以分别是:
// errors.New
func openConfigFile(path string) error {
_, err := os.Open(path)
if err != nil {
// 返回新的 error 实例
return errors.New("can not open the specific file")
}
return nil
}
// fmt.Errorf
func openConfigFile(path string) error {
_, err := os.Open(path)
if err != nil {
// 返回新的 error 实例,同时包含了实际 error
return fmt.Errorf("can not open the specific file, reason: %v", err)
}
return nil
}
// return error that called function returned
func openConfigFile(path string) error {
_, err := os.Open(path)
if err != nil {
// 直接返回得到的 error,不做任何处理
return err
}
return nil
}
// 自定义 error
type OpenErr struct {
Err error // 存放原始 error
}
// 实现 error 接口
func (*OpenErr) Error() string {
return "can not open the specific file"
}
func openConfigFile(path string) error {
_, err := os.Open(path)
if err != nil {
return &OpenErr{
Err:err}
}
return nil
}
上述四种方法各有千秋。如方法一,errors.New 会返回一个新的 error,其存放的数据就是我们传入的 text(“can not open the specific file”)。采用这种方式通常是为了告诉调用者出错了,但实际的错误细节不愿暴露。对调用者来说,他可能不太关心出了什么错,只在乎有没有出错。
方法二跟方法一相同,也会隐藏原始错误(这里指错误类型),但通常会将原始错误的字符串说明一起返回。在该处理方式中,“can not open the specific file” 为额外提示语,“reason: %v” 显示错误细节。一般来讲,调用 fmt.Errorf 更大几率是要产生一个给人看的错误,而不是让代码去解析(尽管并非不能)。
方法三,通常是函数调用者需要根据 error 的实际类型,或实际值,确定下一步的执行策略。也就是说它需要解析 error,所以函数返回“原味” error。这样做的缺点是,没办法对 error 添加额外信息。
方法四可以认为是上述三种方法的集合,既可以保留原始 error,还可以添加额外信息。通过这些元数据随意组合,调用者想要的样子它都有。缺点就是代码要多写一些。
如何检查 error
现在 error 有了,我们应该如何检查错误呢?在 1.13 之前,常见有:1. 比较值;2. 比较类型。
拿官方源码举例更具说服力。我们先来看看比较值。
func (db *DB) QueryContext(ctx context.Context,
query string,
args ...interface{
}) (*Rows, error) {
var rows *Rows
var err error
for i := 0; i < maxBadConnRetries; i++ {
rows, err = db.query(ctx, query, args, cachedOrNewConn)
if err != driver.ErrBadConn {
// 比较值
break
}
}
if err == driver.ErrBadConn {
// 比较值
return db.query(ctx, query, args, alwaysNewConn)
}
return rows, err
}
func (db *DB) Query(query string,
args ...interface{
}) (*Rows, error) {
</