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提取出来。

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

// before

if err == io.ErrUnexpectedEOF

// after

if errors.Is(err, io.ErrUnexpectedEOF)


// before if e, ok := err.(*os.PathError); ok // after var e *os.PathError if 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 : myerror foo2 : 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