Go语言(golang)新发布的1.13中的Error Wrapping深度分析
<p id="activity-name" class="rich_media_title"> </p><div id="js_content" class="rich_media_content ">
<p>Go 1.13发布的功能还有一个值得深入研究的,就是对Error的增强,也是今天我们要分析的 Error Wrapping.</p>
<h2>背景</h2>
<p>做Go语言开发的,肯定经常用<code>error</code>,但是我们也知道<code>error</code>非常弱,只能自带一串文本其他什么都做不了,比如给已经存在的<code>error</code>增加一些附加文本,增加堆栈信息等都做不了。如果我们想给<code>error</code>增加一些附加文本怎么做呢?有两种办法:</p>
<p>第一种:</p>
<pre><code>newErr:=fmt.Errorf(<span>"数据上传问题: %v", err)<br></span></code></pre>
<p>通过<code>fmt.Errorf</code>函数,基于已经存在的<code>err</code>再生成一个新的<code>newErr</code>,然后附加上我们想添加的文本信息。这种办法比较方便,但是问题也很明显,我们丢失了原来的<code>err</code>,因为它已经被我们的<code>fmt.Errorf</code>函数转成一个新的字符串了。</p>
<p>第二种:</p>
<pre><code><span><span>func <span>main<span>() {<br> newErr := MyError{err, <span>"数据上传问题"}<br>}<br><br><span>type MyError <span>struct {<br> err error<br> msg <span>string<br>}<br><br><span><span>func <span>(e *MyError) <span>Error<span>() <span>string {<br> <span>return e.err.Error() + e.msg<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>这种方式就是我们自定义自己的<code>struct</code>,添加用于存储我们需要额外信息的字段,我这里是<code>err</code>和<code>msg</code>,分别存储原始<code>err</code>和新附加的出错信息。然后这个<code>MyError</code>还会实现<code>error</code>接口,表示他是一个<code>error</code>,这样我们就可以自由的使用了。</p>
<p>这种方式有点很明显,大家可以看到了。缺点呢,就是我们要自定义很多<code>struct</code>,而且我们自己定义的,和第三方的可能还不太一样,无法统一和兼容。基于这个背景,Golang 1.13 为我们提供了Error Wrapping,翻译过来我更愿意叫Error嵌套。</p>
<h2>如何生成一个Wrapping Error</h2>
<p>Error Wrapping,顾名思义,就是为我们提供了,可以一个<code>error</code>嵌套另一个<code>error</code>功能,好处就是我们可以根据嵌套的<code>error</code>序列,生成一个<code>error</code>错误跟踪链,也可以理解为错误堆栈信息,这样可以便于我们跟踪调试,哪些错误引起了什么问题,根本的问题原因在哪里。</p>
<p>因为<code>error</code>可以嵌套,所以每次嵌套的时候,我们都可以提供新的错误信息,并且保留原来的<code>error</code>。现在我们看下如何生成一个嵌套的<code>error</code>。</p>
<pre><code>e := errors.New(<span>"原始错误e")<br>w := fmt.Errorf(<span>"Wrap了一个错误%w", e)<br></span></span></code></pre>
<p>Golang并没有提供什么<code>Wrap</code>函数,而是扩展了<code>fmt.Errorf</code>函数,加了一个<code>%w</code>来生成一个可以Wrapping Error,通过这种方式,我们可以创建一个个以Wrapping Error。</p>
<h2>Wrapping Error原理</h2>
<p>按照这种不丢失原<code>error</code>的思路,那么Wrapping Error的实现原理应该类似我们上面的自定义<code>error</code>.我们看下<code>fmt.Errorf</code>函数的源代码验证下我们的猜测是否正确。</p>
<pre><code><span><span>func <span>Errorf<span>(format <span>string, a ...<span>interface{}) <span>error {<br> <span>//省略无关代码<br> <span>var err error<br> <span>if p.wrappedErr == <span>nil {<br> err = errors.New(s)<br> } <span>else {<br> err = &wrapError{s, p.wrappedErr}<br> }<br> p.free()<br> <span>return err<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>这里的关键核心代码就是<code>p.wrappedErr</code>的判断,这个值是否存在,决定是否要生成一个wrapping error。这个值是怎么来的呢?就是根据我们设置的<code>%w</code>解析出来的。</p>
<p>有了这个值之后,就生成了一个<code>&wrapError{s, p.wrappedErr}</code>返回了,这里有个结构体<code>wrapError</code></p>
<pre><code><span>type wrapError <span>struct {<br> msg <span>string<br> err error<br>}<br><br><span><span>func <span>(e *wrapError) <span>Error<span>() <span>string {<br> <span>return e.msg<br>}<br><br><span><span>func <span>(e *wrapError) <span>Unwrap<span>() <span>error {<br> <span>return e.err<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>如上所示,和我们想的一样。实现了<code>Error</code>方法说明它是一个<code>error</code>。<code>Unwrap</code>方法是一个特别的方法,所有的wrapping error 都会有这么一个方法,用于获得被嵌套的error。</p>
<h2>Unwrap 函数</h2>
<p>Golang 1.13引入了wrapping error后,同时为<code>errors</code>包添加了3个工具函数,他们分别是<code>Unwrap</code>、<code>Is</code>和<code>As</code>,先来聊聊<code>Unwrap</code>。</p>
<p>顾名思义,它的功能就是为了获得被嵌套的error。</p>
<pre><code><span><span>func <span>main<span>() {<br> e := errors.New(<span>"原始错误e")<br> w := fmt.Errorf(<span>"Wrap了一个错误%w", e)<br> fmt.Println(errors.Unwrap(w))<br>}<br></span></span></span></span></span></span></code></pre>
<p>以上这个例子,通过<code>errors.Unwrap(w)</code>后,返回的其实是个<code>e</code>,也就是被嵌套的那个error。<br>这里需要注意的是,嵌套可以有很多层,我们调用一次<code>errors.Unwrap</code>函数只能返回最外面的一层<code>error</code>,如果想获取更里面的,需要调用多次<code>errors.Unwrap</code>函数。最终如果一个<code>error</code>不是warpping error,那么返回的是<code>nil</code>。</p>
<pre><code><span><span>func <span>Unwrap<span>(err error) <span>error {<br> <span>//先判断是否是wrapping error<br> u, ok := err.(<span>interface {<br> Unwrap() error<br> })<br> <span>//如果不是,返回nil<br> <span>if !ok {<br> <span>return <span>nil<br> }<br> <span>//否则则调用该error的Unwrap方法返回被嵌套的error<br> <span>return u.Unwrap()<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>看看该函数的的源代码吧,这样就会理解的更深入一些,我加了一些注释。</p>
<h2>Is 函数</h2>
<p>在Go 1.13之前没有wrapping error的时候,我们要判断error是不是同一个error可以使用如下办法:</p>
<pre><code><span>if err == os.ErrExist<br></span></code></pre>
<p>这样我们就可以通过判断来做一些事情。但是现在有了wrapping error后这样办法就不完美的,因为你根本不知道返回的这个<code>err</code>是不是一个嵌套的error,嵌套了几层。所以基于这种情况,Golang为我们提供了<code>errors.Is</code>函数。</p>
<pre><code><span><span>func <span>Is<span>(err, target error) <span>bool<br></span></span></span></span></span></code></pre>
<ol class="list-paddingleft-2">
<li>
<p>如果<code>err</code>和<code>target</code>是同一个,那么返回<code>true</code></p>
</li>
<li>
<p>如果<code>err</code> 是一个wrap error,<code>target</code>也包含在这个嵌套error链中的话,那么也返回<code>true</code>。</p>
</li>
</ol>
<p>很简单的一个函数,要么咱俩相等,要么<code>err</code>包含<code>target</code>,这两种情况都返回<code>true</code>,其余返回<code>false</code>。</p>
<pre><code><span><span>func <span>Is<span>(err, target error) <span>bool {<br> <span>if target == <span>nil {<br> <span>return err == target<br> }<br><br> isComparable := reflectlite.TypeOf(target).Comparable()<br><br> <span>//for循环,把err一层层剥开,一个个比较,找到就返回true<br> <span>for {<br> <span>if isComparable && err == target {<br> <span>return <span>true<br> }<br> <span>//这里意味着你可以自定义error的Is方法,实现自己的比较代码<br> <span>if x, ok := err.(<span>interface{ Is(error) <span>bool }); ok && x.Is(target) {<br> <span>return <span>true<br> }<br> <span>//剥开一层,返回被嵌套的err<br> <span>if err = Unwrap(err); err == <span>nil {<br> <span>return <span>false<br> }<br> }<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p><code>Is</code>函数源代码如上,其实就是一层层反嵌套,剥开然后一个个的和<code>target</code>比较,相等就返回true。</p>
<h2>As 函数</h2>
<p>在Go 1.13之前没有wrapping error的时候,我们要把error转为另外一个error,一般都是使用type assertion 或者 type switch,其实也就是类型断言。</p>
<pre><code><span>if perr, ok := err.(*os.PathError); ok {<br> fmt.Println(perr.Path)<br>}<br></span></code></pre>
<p>比如例子中的这种方式,但是现在给你返回的err可能是已经被嵌套了,甚至好几层了,这种方式就不能用了,所以Golang为我们在<code>errors</code>包里提供了<code>As</code>函数,现在我们把上面的例子,用<code>As</code>函数实现一下。</p>
<pre><code><span>var perr *os.PathError<br><span>if errors.As(err, &perr) {<br> fmt.Println(perr.Path)<br>}<br></span></span></code></pre>
<p>这样就可以了,就可以完全实现类型断言的功能,而且还更强大,因为它可以处理wrapping error。</p>
<pre><code><span><span>func <span>As<span>(err error, target <span>interface{}) <span>bool<br></span></span></span></span></span></span></code></pre>
<p>从功能上来看,<code>As</code>所做的就是遍历err嵌套链,从里面找到类型符合的error,然后把这个error赋予target,这样我们就可以使用转换后的target了,这里有值得赋予,所以target必须是一个指针。</p>
<pre><code><span><span>func <span>As<span>(err error, target <span>interface{}) <span>bool {<br> <span>//一些判断,保证target,这里是不能为nil<br> <span>if target == <span>nil {<br> <span>panic(<span>"errors: target cannot be nil")<br> }<br> val := reflectlite.ValueOf(target)<br> typ := val.Type()<br><br> <span>//这里确保target必须是一个非nil指针<br> <span>if typ.Kind() != reflectlite.Ptr || val.IsNil() {<br> <span>panic(<span>"errors: target must be a non-nil pointer")<br> }<br><br> <span>//这里确保target是一个接口或者实现了error接口<br> <span>if e := typ.Elem(); e.Kind() != reflectlite.Interface && !e.Implements(errorType) {<br> <span>panic(<span>"errors: *target must be interface or implement error")<br> }<br> targetType := typ.Elem()<br> <span>for err != <span>nil {<br> <span>//关键部分,反射判断是否可被赋予,如果可以就赋值并且返回true<br> <span>//本质上,就是类型断言,这是反射的写法<br> <span>if reflectlite.TypeOf(err).AssignableTo(targetType) {<br> val.Elem().Set(reflectlite.ValueOf(err))<br> <span>return <span>true<br> }<br> <span>//这里意味着你可以自定义error的As方法,实现自己的类型断言代码<br> <span>if x, ok := err.(<span>interface{ As(<span>interface{}) <span>bool }); ok && x.As(target) {<br> <span>return <span>true<br> }<br> <span>//这里是遍历error链的关键,不停的Unwrap,一层层的获取err<br> err = Unwrap(err)<br> }<br> <span>return <span>false<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>这是<code>As</code>函数的源代码,看源代码比较清晰一些,我在代码里做了注释,这里就不一一分析了,大家可以结合注释读一下。</p>
<h2>旧工程迁移</h2>
<p>新特性的更新,如果要使想使用,不免会有旧项目的迁移,现在我们就针对几种常见的情况看如何进行迁移。<br>如果你以前是直接返回err,或者通过如下方式给err增加了额外信息。</p>
<pre><code><span>return err<br><br><span>return fmt.Errorf(<span>"more info: %v", err)<br></span></span></span></code></pre>
<p>这2种情况你直接切换即可。</p>
<pre><code><span>return fmt.Errorf(<span>"more info: %w", err)<br></span></span></code></pre>
<p>切换后,如果你有<code>==</code>的error判断,那么就用<code>Is</code>函数代替,比如:</p>
<p>旧工程</p>
<pre><code><span>if err == os.ErrExist<br></span></code></pre>
<p>新工程</p>
<pre><code><span>if errors.Is(err, os.ErrExist)<br></span></code></pre>
<p>同理,你旧的代码中,如果有对error进行类型断言的转换,就要用<code>As</code>函数代替,比如:</p>
<p>旧工程</p>
<pre><code><span>if perr, ok := err.(*os.PathError); ok {<br> fmt.Println(perr.Path)<br>}<br></span></code></pre>
<p>新工程</p>
<pre><code><span>var perr *os.PathError<br><span>if errors.As(err, &perr) {<br> fmt.Println(perr.Path)<br>}<br></span></span></code></pre>
<p>如果你自己自定义了一个<code>struct</code>实现了<code>error</code>接口,而且还嵌套了<code>error</code>,这个时候该怎么适配新特性呢?也就是我们上面举例的情况:</p>
<pre><code><span>type MyError <span>struct {<br> err error<br> msg <span>string<br>}<br><br><span><span>func <span>(e *MyError) <span>Error<span>() <span>string {<br> <span>return e.err.Error() + e.msg<br>}<br></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>其实对于这种方式很简单,只需要给他添加一个<code>Unwrap</code>方法就可以了,让它变成一个wrap error。</p>
<pre><code><span><span>func <span>(e *MyError) <span>Unwrap<span>() <span>error {<br> <span>return e.err<br>}<br></span></span></span></span></span></span></span></code></pre>
<p>这样就可以了。</p>
<h2>结语</h2>
<p>这篇文章深度剖析了Error Wrapping的背景、实现原理以及工程迁移的注意事项,这个新特性是很值得使用的,毕竟增强了error,提供了强大的跟踪能力,再结合一些额外的数据,可以让我们调试、变成会更方便。</p>
<p> </p>
<p>原文:https://www.flysnow.org/2019/09/06/go1.13-error-wrapping.html</p>
</div>
</div>
<div id="MySignature" role="contentinfo">
<p>本文来自博客园,作者:sunsky303,转载请注明原文链接:https://www.cnblogs.com/sunsky303/p/11571440.html</p><br><br>
来源:https://www.cnblogs.com/sunsky303/p/11571440.html
頁:
[1]