avatarChe Dan

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

5615

Abstract

js-params">()</span></span> <span class="hljs-type">error</span> { <span class="hljs-keyword">return</span> errors.Wrap(sql.ErrNoRows, <span class="hljs-string">"foo failed"</span>) }

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">bar</span><span class="hljs-params">()</span></span> <span class="hljs-type">error</span> { <span class="hljs-keyword">return</span> errors.WithMessage(foo(), <span class="hljs-string">"bar failed"</span>) }

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> { err := bar() <span class="hljs-keyword">if</span> errors.Cause(err) == sql.ErrNoRows { fmt.Printf(<span class="hljs-string">"data not found, %v\n"</span>, err) fmt.Printf(<span class="hljs-string">"%+v\n"</span>, err) <span class="hljs-keyword">return</span> } <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { <span class="hljs-comment">// unknown error</span> } }</pre></div><div id="990c"><pre><span class="hljs-section">/Output:</span></pre></div><div id="ec14"><pre>data not found, bar failed: foo failed: sql: no rows <span class="hljs-keyword">in</span> result set sql: no rows <span class="hljs-keyword">in</span> result set foo failed <span class="hljs-selector-tag">main</span><span class="hljs-selector-class">.foo</span> /usr/three/<span class="hljs-selector-tag">main</span><span class="hljs-selector-class">.go</span>:<span class="hljs-number">11</span> <span class="hljs-selector-tag">main</span><span class="hljs-selector-class">.bar</span> /usr/three/<span class="hljs-selector-tag">main</span><span class="hljs-selector-class">.go</span>:<span class="hljs-number">15</span> <span class="hljs-selector-tag">main</span><span class="hljs-selector-class">.main</span> /usr/three/<span class="hljs-selector-tag">main</span><span class="hljs-selector-class">.go</span>:<span class="hljs-number">19</span> runtime<span class="hljs-selector-class">.main</span> ...</pre></div><div id="36c3"><pre><span class="hljs-comment">/</span></pre></div><p id="f13c">If we use <code>%v</code> as format parameter, we’ll get an one-line output string, contains all contextual text in the order of call stack. If change the format parameter to <code>%+v</code> , we’ll get the complete call stack.</p><p id="0e0f">If you want to simply wrap error with attach call stack, no additional contextual text is needed, then use <code>WithStack</code></p><div id="196d"><pre><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">foo</span><span class="hljs-params">()</span></span> <span class="hljs-type">error</span> { <span class="hljs-keyword">return</span> errors.WithStack(sql.ErrNoRows) }</pre></div><p id="00f8">Note: When use <code>Wrap</code>, <code>WithMessage</code> or <code>WithStack</code>, if the err parameter is nil, then nil will be returned, which means that we don’t need to check <code>err != nil</code> condition before calling the method. Keeping the code simple</p><h1 id="40e4">2. golang.org/x/xerrors</h1><p id="45e9">After listening the feedback from community, the Go team published a <a href="https://go.googlesource.com/proposal/+/master/design/29934-error-values.md">proposal</a> to simplify error handling in Go 2. Go core team member <a href="https://research.swtch.com/">Russ Cox</a> partially implemented the proposal in <code>golang.org/x/xerrors</code>. It solves the same problem with a similar approach to <code>github.com/pkg/errors</code>, introduces a format verb <code>: %w</code>, and it use method <code>Is</code> to determine the underlying error.</p><div id="0236"><pre><span class="hljs-keyword">import</span> ( <span class="hljs-string">"database/sql"</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-string">"golang.org/x/xerrors"</span> )

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">bar</span><span class="hljs-params">()</span></span> <span class="hljs-type">error</span> { <span class="hljs-keyword">if</span> err := foo(); err != <span class="hljs-literal">nil</span> { <span class="hljs-keyword">return</span> xerrors.Errorf(<span class="hljs-string">"bar failed: %w"</span>, foo()) } <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span> }

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">foo</span><span class="hljs-params">()</span></span> <span class="hljs-type">error</span> { <span class="hljs-keyword">return</span> xerrors.Errorf(<span class="hljs-string">"foo failed: %w"</span>, sql.ErrNoRows) }

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> { err := bar() <span class="hljs-keyword">if</span> xerrors.Is(err, sql.ErrNoRows) { fmt.Printf(<span class="hljs-string">"data not found, %v\n"</span>, err) fmt.Printf(<span class="hljs-string">"%+v\n"</span>, err) <span class="hljs-keyword">return</span> } <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { <span class="hljs-comment">// unknown error</span> } } <span class="hljs-comment">/* Outputs:</span></pre></div><div id="6501"><pre>data <span class="hljs-keyword">not</span> found, bar failed: foo failed: sql: <span class="hljs-keyword">no</span> rows in result <span class="hljs-keyword">set</span> b

Options

ar <span class="hljs-comment">failed:</span> main.bar /usr/four/main.go:<span class="hljs-number">12</span>

  • foo failed: main.foo /usr/four/main.go:18
  • sql: no <span class="hljs-comment">rows in result set</span> <span class="hljs-comment">*/</span></pre></div><p id="878a">Comparing to <code>github.com/pkg/errors</code>, it has several disadvantages:</p><ol><li>Replace <code>Wrap</code> with format parameter <code>: %w</code> . Looks simplify the code, but this approach <b>loses compile-time checking</b>. If <code>: %w</code> is not the tail of format string(<code>e.g., "foo : %w bar"</code>), or colon is missing (<code>e.g., "foo %w"</code>), or the space between colon and percent sign is missing (<code>e.g., "foo:%w"</code>), the wrapping will fail without any warning</li><li>What’s more serious is that you have to check the condition <code>err != nil </code>before calling <code>xerrors.Errorf</code>. This actually does not simplify the work of the developer at all</li></ol><h1 id="5157">3. Built-in Error Wrapping support in Go 1.13</h1><p id="8929">As of Go 1.13, some (not all) features of <code>xerrors</code> have been integrated into the standard library. It inherits all the shortcomings of <code>xerrors</code> and contributes an additional one☹️. Therefore, I recommend against using it at the moment</p><div id="ae85"><pre><span class="hljs-keyword">import</span> ( <span class="hljs-string">"database/sql"</span> <span class="hljs-string">"errors"</span> <span class="hljs-string">"fmt"</span> )

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">bar</span><span class="hljs-params">()</span></span> <span class="hljs-type">error</span> { <span class="hljs-keyword">if</span> err := foo(); err != <span class="hljs-literal">nil</span> { <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"bar failed: %w"</span>, foo()) } <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span> }

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">foo</span><span class="hljs-params">()</span></span> <span class="hljs-type">error</span> { <span class="hljs-keyword">return</span> fmt.Errorf(<span class="hljs-string">"foo failed: %w"</span>, sql.ErrNoRows) }

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> { err := bar() <span class="hljs-keyword">if</span> errors.Is(err, sql.ErrNoRows) { fmt.Printf(<span class="hljs-string">"data not found, %+v\n"</span>, err) <span class="hljs-keyword">return</span> } <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> { <span class="hljs-comment">// unknown error</span> } } <span class="hljs-comment">/* Outputs: data not found, bar failed: foo failed: sql: no rows in result set */</span></pre></div><p id="8969">Similar to the <code>xerrors</code> version. However, it <a href="https://github.com/golang/go/issues/29934#issuecomment-489682919"><b>does not support call stack output</b></a>. And according to the <a href="https://github.com/golang/go/issues/34349">official statement</a>, there is no schedule for this. So <code>github.com/pkg/errors</code> is much better choice at the moment</p><h1 id="28d2">Summing-up</h1><p id="308d">With above comparison, I believe you have made your choice. Let me be clear. My selection order is 1> 2> 3</p><ol><li>If you are using <code>github.com/pkg/errors</code>, keep it. There is no solution better than it for now</li><li>If you’ve already used <code>golang.org/x/xerrors</code> heavily, don’t switch to the built-in solution in haste, which doesn’t deserve it</li></ol><p id="16cf">To be fair, Go has been quite mature and robust in most aspects since its birth. Hesitation and sway rarely occurred during its evolution. but error handling is an exception.</p><p id="374d">Not to mention the widely complained <code>if err != nil</code>, even its improvement roadmap is so controversial. Actually the Go team <a href="https://github.com/golang/go/issues/32437#issuecomment-512035919">have adjusted</a> a <a href="https://github.com/golang/go/issues/32437">proposal</a> due to overwhelming objections.</p><p id="ce25">Fortunately, the Go team is more willing to listen to the community than before, and they even built a <a href="https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback">feedback page</a> specifically on this issue. I believe we will find a better solution eventually</p><h1 id="869f">The last note</h1><p id="5f61">Although we discussed how to wrap error efficiently and elegantly, but like other technologies, it should only be used where appropriate. It should not be regarded as a general principle.</p><p id="8109">Why? When user start using <code>errors.Cause(err, sql.ErrNoRows)</code> or <code>xerrors.Is (err, sql.ErrNoRows)</code>, it means that <code>sql.ErrNoRows</code> , as an implementation detail, is exposed to the outside and it becomes part of the API.</p><p id="f901">If you are doing application development with some libraries , It’s acceptable.</p><p id="a1cb">However, If you are defining some public APIs , this issue becomes particularly important. Maybe a better approach is to define a basic error type, then derive different error instances from it, with error code attached</p><p id="7987">See also: <a href="https://readmedium.com/mastering-wire-f1226717bbac">Mastering Wire</a></p></article></body>

Golang Error Handling — Best Practice in 2020

Golang has many advantages, its popularity explains this. But error handling in Go 1 is not very efficient , we have to write many verbose , inconvenient codes in daily development.

There’re several open source solutions to tackle this. Meanwhile, the Go team is improving this from both the language and the standard library aspects.

Today we will analyze the common issues, compare the solutions, and show the best practices by now (go 1.13).

Conclusion first: my preference is github.com/pkg/errors. The reason will be explained in detail below.

Problem

When programing in Go, we need to check the returned error and handle it, a simplest example looks like this:

import (
   "database/sql"
   "fmt"
)

func foo() error {
   return sql.ErrNoRows
}

func bar() error {
   return foo()
}

func main() {
   err := bar()
   if err != nil {
      fmt.Printf("got err, %+v\n", err)
   }
}
//Outputs:
// got err, sql: no rows in result set

Sometimes we need to do different processing based on the different types of error:

import (
   "database/sql"
   "fmt"
)

func foo() error {
   return sql.ErrNoRows
}

func bar() error {
   return foo()
}

func main() {
   err := bar()
   if err == sql.ErrNoRows {
      fmt.Printf("data not found, %+v\n", err)
      return
   }
   if err != nil {
      // Unknown error
   }
}
//Outputs:
// data not found, sql: no rows in result set

In practice, we often add additional context to error before its return. The context will help caller to understand what’ going on. for example, we can rewrite function foo :

func foo() error {
   return fmt.Errorf("foo err, %v", sql.ErrNoRows)
}

Then the err == sql.ErrNoRows condition will turn to false. In addition, the call stack is dropped when an error is returned, which is the most important diagnostic information. We need a more flexible way to deal with such issues.

Solutions

There are several solutions to resolve the problem. Wrapping error with these libraries will keep the accessibility of root error and complete call stack

1. github.com/pkg/errors

from Dave Cheney , this library has 3 key methods:

  1. Wrap is used to wrap the underlying error, add contextual text information, and attach the call stack. Generally it is used to wrap calls to API from other people (standard library or third-party library).
  2. WithMessage is used to add contextual text information to underlying error without attaching call stack. Apply this method for “wrapped error”only. Note: Do not repeat Wrap, it will record redundancy call stacks
  3. Cause method is for determining the underlying error

Rewrite the example above with github.com/pkg/errors :

import (
   "database/sql"
   "fmt"

   "github.com/pkg/errors"
)

func foo() error {
   return errors.Wrap(sql.ErrNoRows, "foo failed")
}

func bar() error {
   return errors.WithMessage(foo(), "bar failed")
}

func main() {
   err := bar()
   if errors.Cause(err) == sql.ErrNoRows {
      fmt.Printf("data not found, %v\n", err)
      fmt.Printf("%+v\n", err)
      return
   }
   if err != nil {
      // unknown error
   }
}
/*Output:
data not found, bar failed: foo failed: sql: no rows in result set
sql: no rows in result set
foo failed
main.foo
    /usr/three/main.go:11
main.bar
    /usr/three/main.go:15
main.main
    /usr/three/main.go:19
runtime.main
    ...
*/

If we use %v as format parameter, we’ll get an one-line output string, contains all contextual text in the order of call stack. If change the format parameter to %+v , we’ll get the complete call stack.

If you want to simply wrap error with attach call stack, no additional contextual text is needed, then use WithStack

func foo() error {
   return errors.WithStack(sql.ErrNoRows)
}

Note: When use Wrap, WithMessage or WithStack, if the err parameter is nil, then nil will be returned, which means that we don’t need to check err != nil condition before calling the method. Keeping the code simple

2. golang.org/x/xerrors

After listening the feedback from community, the Go team published a proposal to simplify error handling in Go 2. Go core team member Russ Cox partially implemented the proposal in golang.org/x/xerrors. It solves the same problem with a similar approach to github.com/pkg/errors, introduces a format verb : %w, and it use method Is to determine the underlying error.

import (
   "database/sql"
   "fmt"

   "golang.org/x/xerrors"
)

func bar() error {
   if err := foo(); err != nil {
      return xerrors.Errorf("bar failed: %w", foo())
   }
   return nil
}

func foo() error {
   return xerrors.Errorf("foo failed: %w", sql.ErrNoRows)
}

func main() {
   err := bar()
   if xerrors.Is(err, sql.ErrNoRows) {
      fmt.Printf("data not found, %v\n", err)
      fmt.Printf("%+v\n", err)
      return
   }
   if err != nil {
      // unknown error
   }
}
/* Outputs:
data not found, bar failed: foo failed: sql: no rows in result set
bar failed:
    main.bar
        /usr/four/main.go:12
  - foo failed:
    main.foo
        /usr/four/main.go:18
  - sql: no rows in result set
*/

Comparing to github.com/pkg/errors, it has several disadvantages:

  1. Replace Wrap with format parameter : %w . Looks simplify the code, but this approach loses compile-time checking. If : %w is not the tail of format string(e.g., "foo : %w bar"), or colon is missing (e.g., "foo %w"), or the space between colon and percent sign is missing (e.g., "foo:%w"), the wrapping will fail without any warning
  2. What’s more serious is that you have to check the condition err != nil before calling xerrors.Errorf. This actually does not simplify the work of the developer at all

3. Built-in Error Wrapping support in Go 1.13

As of Go 1.13, some (not all) features of xerrors have been integrated into the standard library. It inherits all the shortcomings of xerrors and contributes an additional one☹️. Therefore, I recommend against using it at the moment

import (
   "database/sql"
   "errors"
   "fmt"
)

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

func foo() error {
   return fmt.Errorf("foo failed: %w", sql.ErrNoRows)
}

func main() {
   err := bar()
   if errors.Is(err, sql.ErrNoRows) {
      fmt.Printf("data not found,  %+v\n", err)
      return
   }
   if err != nil {
      // unknown error
   }
}
/* Outputs:
data not found,  bar failed: foo failed: sql: no rows in result set
*/

Similar to the xerrors version. However, it does not support call stack output. And according to the official statement, there is no schedule for this. So github.com/pkg/errors is much better choice at the moment

Summing-up

With above comparison, I believe you have made your choice. Let me be clear. My selection order is 1> 2> 3

  1. If you are using github.com/pkg/errors, keep it. There is no solution better than it for now
  2. If you’ve already used golang.org/x/xerrors heavily, don’t switch to the built-in solution in haste, which doesn’t deserve it

To be fair, Go has been quite mature and robust in most aspects since its birth. Hesitation and sway rarely occurred during its evolution. but error handling is an exception.

Not to mention the widely complained if err != nil, even its improvement roadmap is so controversial. Actually the Go team have adjusted a proposal due to overwhelming objections.

Fortunately, the Go team is more willing to listen to the community than before, and they even built a feedback page specifically on this issue. I believe we will find a better solution eventually

The last note

Although we discussed how to wrap error efficiently and elegantly, but like other technologies, it should only be used where appropriate. It should not be regarded as a general principle.

Why? When user start using errors.Cause(err, sql.ErrNoRows) or xerrors.Is (err, sql.ErrNoRows), it means that sql.ErrNoRows , as an implementation detail, is exposed to the outside and it becomes part of the API.

If you are doing application development with some libraries , It’s acceptable.

However, If you are defining some public APIs , this issue becomes particularly important. Maybe a better approach is to define a basic error type, then derive different error instances from it, with error code attached

See also: Mastering Wire

Golang
Programming
Errorhanding
Recommended from ReadMedium