Go1.13 之 Error Wrapping
收藏


本文挑重点来看go1.13版本中对于错误处理部分提供的新功能(Error wrapping)(proposal参考资料1)。



提案中对于error新功能主要分两点:

  1. error可以包裹着其他error。而不是以前的做法,以字符串拼接方式往上传递。

  2. 使用%+v打印error时,带有堆栈信息,精确到函数名与行号。


其实这两块功能,很早前Dave Cheney就在一篇博文中论述过他对error处理的理解,并给出相关的开源包github.com/pkg/errors(这个库现在也处于维护状态,不再接受新功能)。

用过的同学应该比较熟悉,它解决的问题也是上面提到两点error新功能。


功能1

go1.13之前,方法内部逻辑中,遇到其它方法返回error时,一种粗暴的处理方式,直接return fmt.Errorf("operate failed %v", err)。这种做法问题是把err转换成为另一个字符串,原始的err被抹掉。

如果想添加额外的错误信息,又不想抹掉原始的err,可以封装一个struct,上层通过err.Err.(type)的方式来检查,但显然加大编码复杂度。


而用了go1.13之后,解决这个问题的方案就变成下面这样

func foo() error {  err := openSomething()  // %v 变成了 %w  return fmt.Errorf("operate failed %w", err)}
func main() { err := foo() if errors.Is(err, *os.PathError) { var pe os.PathError errors.As(err, &pe) // dosomething } // 或者更直接 // var pe os.PathError // if errors.As(err, &pe) { // dosomething // }}

这样原始的err不会变抹掉,通过errors.Is()方法可以检查出来。其次通过fmt.Println()输出是仍是字符串,样式与之前使用%v`时相比较没有改变。


还可以通过errors.As()方法将对应的原始err提取出来。


所以用户在升级新版本后,有两个地方的代码需要转换下

// beforeif err == io.ErrUnexpectedEOF// afterif errors.Is(err, io.ErrUnexpectedEOF)
// beforeif e, ok := err.(*os.PathError); ok// aftervar e *os.PathErrorif errors.As(err, &e)

用了这两种做法,即使API提供方后面更改返回的error含义,兼容成本较低。

基于go1.13之前不同error不同的使用场景,官方写了FAQ,参考资料3。


功能2

打印error时带上堆栈信息功能很实用,之前遇到error时,排查都需要顺藤摸瓜的找到源头,比较浪费时间。


坏消息,功能2在go1.13官方标准库中被腰斩了,推迟到go1.14。详细原因可参考资料2。


好消息是官方提供了golang.org/x/xerrors。这个包完整实现了这两个新功能点,主要生产环境不易升级go版本的用户,可以尝鲜,或者是对已经升级为新版本的下游做兼容。


看一下使用xerrors包打印error时带堆栈的效果。

var myerror = xerrors.New("myerror")func foo() error {  return myerror}func foo1() error {  return xerrors.Errorf("foo1 : %w",foo())}func foo2() error {  return xerrors.Errorf("foo2 : %w",foo1())}func main() {  err := foo2()  fmt.Printf("%v\n", err)  fmt.Printf("%+v\n", err)}
// 以下是输出foo2 : foo1 : myerrorfoo2 : main.foo2 /Users/cbsheng/goproject/src/test/main.go:116 - foo1 : main.foo1 /Users/cbsheng/goproject/src/test/main.go:113 - myerror: main.init /Users/cbsheng/goproject/src/test/main.go:108

注意,xerrors.Errorf("foo1 : %w",foo())中必须以: %w的格式占位,否则不起作用。



资料1:https://go.googlesource.com/proposal/+/master/design/29934-error-values.md

资料2:https://github.com/golang/go/issues/29934#issuecomment-489682919

资料3:https://github.com/golang/go/wiki/ErrorValueFAQ