本文挑重点来看go1.13版本中对于错误处理部分提供的新功能(Error wrapping)(proposal参考资料1)。
提案中对于error新功能主要分两点:
-
error可以包裹着其他error。而不是以前的做法,以字符串拼接方式往上传递。
-
使用
%+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 变成了 %wreturn fmt.Errorf("operate failed %w", err)}func main() {err := foo()if errors.Is(err, *os.PathError) {var pe os.PathErrorerrors.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
文章评论