Go语言中常见100问题-#49 wrap error

2022年3月23日 269点热度 0人点赞 0条评论

通常来说,有以下两种场景需要对error进行包装.

  • 向error中添加上下文信息
  • 将error转为一个特定的error

作者对上面的场景各举了一个例子进行说明。对需要向error中添加上下文信息的情况,以数据库操作为例,某个角色身份的人请求数据库操作,但是它没有查询权限,当它在查询的时候会返回一个没有访问权限的error. 为了方便debug问题,通过log记录错误信息,最好是记录上下文信息。本例中,我们可以通过wrap error记录是谁在访问什么资源的信息。

图片


另一个例子是将error转为一个特定的error,作者以实现HTTP handler为例,该函数需要对调用的函数进行返回值进行检查,如果是对资源没有访问权限的error,将其包装为Forbidden类型的error,然后可以返回403状态码。这种情况下,可以通过wrap error操作将原error包装到Forbidden中。

图片


上面的两个例子中,原始error是能够继续访问到的。调用者caller可以通过unwrap操作就能获取到原始错误error信息。需要注意的是,有时候我们可以将上面两个例子中的方法结合起来使用,即添加上下文信息又将它转换为一个特定的错误。

前面介绍了wrap error使用的两种场景,下面分析了error返回时的4种处理方法。分别是「直接返回、通过自定义error包装返回、通过%w和%v返回」。以下面的代码为例

func Foo() error {
       err := bar()
       if err != nil {
               // ?
       }
       // ...
}

上面代码中?的地方,有哪些处理方法?

第一种是直接返回,不做任何处理. 这种方法适合没有有用的上下文信息需要添加,也不需要新产生一个error的情况。

if err != nil {
        return err
}

图片

第二种是自定义一个error类型返回。在Go1.13之前,wrap error需要先定义一个结构体,实现Error() string方法。这种通过自定义类型的优点是非常灵活,可以根据需要添加任何额外的信息。缺点是想重复这个操作比较麻烦,必须创建一个特定的错误类型。

type BarError struct {
        Err error
}

func (b BarError) Error() string {
        return "bar failed:" + b.Err.Error()
}

将原error包装放入BarError中的Err字段中, 代码如下。

if err != nil {
        return BarError{Err: err}
}

图片

第三种方法是使用直接%w, 这个是在Go1.13引入的,性质同第二种方法,优点是不用定义一个结构体实现Error方法,并且原始的错误任然是可以访问的。使用者可以通过unwrap error拿到原始的error,然后将原始的error与某种具体的类型或value进行比较。

if err != nil {
        return fmt.Errorf("bar failed: %w", err)
}

图片

第四种方法是使用%v,示例代码如下. 与第三种方法的区别在于,使用%v返回的error不是对原error的wrap操作,而是将其转成了另一个error, 原error无法在访问了, 即调用方不能unwrap当前的error将它与原始的bar error进行比较。虽然原error不能访问,但是原error描述的信息是可以获取的。

if err != nil {
        return fmt.Errorf("bar failed: %v", err)
}

图片

「因此,%v比%w限制更多,那是不是说在%w发布以后,我们全部使用%w就更好?」 并不完全是这样的。wrap error意味着调用者可以访问原error,这意味着调用方和被调用方存在潜在的耦合。上面的例子中,假如使用wrap error操作,调用Foo函数检查原error是否是bar error,现在实现有调整,使用其他的函数返回了另一种类型的错误,这将可能会破坏调用方。

「如果我们想让调用方不要依赖于被调用函数的具体实现细节,不应该将error 进行wrap返回,而是直接转换返回,这种情况下,使用%v而不是%w」

下面是上面四种方法的归纳总结,wrap error使用在需要添加上下文信息和将error作为一个具体类型这两种场景中。如果需要标记一个错误,我们需要自定义一个错误类型。如果我们仅仅是添加上下文信息,应该使用fmt.Errorf+%w。如果我们不想让调用者和被调用者存在耦合,不应该使用wrap error而应该直接使用fmt.Errorf+%v.

option extra context marking an error(标记一个错误) source error available
直接返回error 不可以 不可以 可以
自定义错误类型 可以,如果结构体中包含有承载额外信息的字段 可以 可以,如果结构体中含有承载原error的字段,并且是可以导出的,或者提供有原error访问方法
fmt.Errorf+%w 可以 不可以 可以
fmt.Errorf+%v 可以 不可以 不可以

28260Go语言中常见100问题-#49 wrap error

这个人很懒,什么都没留下

文章评论