Original address: Go Errors Detailed

Error handling in Golang is very different from PHP and JAVA, there is no try...catch statement to handle errors. Therefore, Golang Error handling is a more controversial point, how to better understanding and processing error message is worth further study.

Go built-in errors

Go error is an interface type that contains a Error() method that returns string . Any type that implements this interface can be used as an error, and the Error method provides a description of the error:

// http://golang.org/pkg/builtin/#error
// error 接口的定义
type error interface {
    Error() string
}

// http://golang.org/pkg/errors/error.go
// errors 构建 error 对象
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}

error is an interface type that contains a Error() method that returns string . As long as the implementation of this interface can be used as an error, Error this method provides a description of the error.

error create

There are two ways to create error

1. errors.New()

// New returns an error that formats as the given text.
// Each call to New returns a distinct error value even if the text is identical.
func New(text string) error {
    return &errorString{text}
}

Q: Why does errors.New() return a pointer?

A: To avoid the ambiguity caused by the equivalent content of New, look at the following example to understand why:

func main() {
    errOne := errors.New("test")
    errTwo := errors.New("test")

    if errOne == errTwo {
      log.Printf("Equal \n")
    } else {
      log.Printf("notEqual \n")
    }
}

output:

notEqual

If errorString is used for comparison, when the project becomes larger and more complex, New() , and the troubleshooting of the problem at that time will also be catastrophic.

Sometimes we need more specific information. That is, specific "context" information is required, indicating specific error values.

This uses the fmt.Errorf function

2. fmt.Errorf()

fmtErr := fmt.Errorf("fmt.Errorf() err, http status is %d", 404)
fmt.Printf("fmtErr errType:%T,err: %v\n", fmtErr, fmtErr)

output:

fmtErr errType is *errors.errorString,err is fmt.Errorf() err, http status is 404

Why is the error type returned by fmtErr *errors.errorString , didn't we create it fmt.Errorf()

Let's take a look at the source code together:

// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// If the format specifier includes a %w verb with an error operand,
// the returned error will implement an Unwrap method returning the operand. It is
// invalid to include more than one %w verb or to supply it with an operand
// that does not implement the error interface. The %w verb is otherwise
// a synonym for %v.
func Errorf(format string, a ...interface{}) error {
    p := newPrinter()
    p.wrapErrs = true
    p.doPrintf(format, a)
    s := string(p.buf)
    var err error
    if p.wrappedErr == nil {
      err = errors.New(s)
    } else {
      err = &wrapError{s, p.wrappedErr}
    }
    p.free()
    return err
}

By source can be found, p.wrappedErr to nil when calls errors.New() to create an error.

The question is, what is p.wrappedErr

Let's look at an example:

wErrOne := errors.New("this is one ")
wErrTwo := fmt.Errorf("this is two %w", wErrOne)
fmt.Printf("wErrOne type is %T err is %v \n", wErrOne, wErrOne)
fmt.Printf("wErrTwo type is %T err is %v \n", wErrTwo, wErrTwo)

output:

wErrOne type is *errors.errorString err is this is one  
wErrTwo type is *fmt.wrapError err is this is two this is one  

Did you find it? error object returned by %w, the output type is *fmt.wrapError

%w is a new error handling feature added in go 1.13.

Go Error Handling Practices

How to get more detailed error information, such as stack trace , to help locate the cause of the error?

Some people say that layer by layer, but this will cause the logs to be scattered everywhere, which is difficult to maintain.

Some people say , use recover capture panic , but this will lead to the abuse of panic

panic only used in truly abnormal situations, such as

  • When the program starts, if a strongly dependent service fails, panic exits
  • When the program starts, if it is found that the configuration obviously does not meet the requirements, you can panic (defense programming)
  • At the program entry, for example, gin middleware needs to use recovery prevent the panic program from exiting

pkg/errors library

Here, we try to solve the above problem with a small package github.com/pkg/errors

Look at a case:

package main

import (
    "github.com/pkg/errors"
    "log"
    "os"
)

func main() {
    err := mid()
    if err != nil {
      // 返回 err 的根本原因
      log.Printf("cause is %+v \n", errors.Cause(err))

      // 返回 err 调用的堆栈信息
      log.Printf("strace tt %+v \n", err)
    }
}

func mid() (err error) {
        return test()
}


func test() (err error) {
        _, err = os.Open("test/test.txt")
    if err != nil {
          return errors.Wrap(err, "open error")
    }

    return nil
}

output:

2022/01/17 00:26:17 cause is open test.test: no such file or directory 
2022/01/17 00:26:17 strace tt open test.test: no such file or directory
open error
main.test
        /path/err/wrap_t/main.go:41
main.mid
        /path/err/wrap_t/main.go:35
main.main
        /path/err/wrap_t/main.go:13
runtime.main
        /usr/local/Cellar/go/1.17.2/libexec/src/runtime/proc.go:255
runtime.goexit
        /usr/local/Cellar/go/1.17.2/libexec/src/runtime/asm_amd64.s:1581 

pkg/errors

The upper caller can use the errors.Cause(err) method to get the culprit of this error.

// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
   if err == nil {
      return nil
   }
   err = &withMessage{
      cause: err,
      msg:   message,
   }
   return &withStack{
      err,
      callers(),
   }
}
// Is 指出当前的错误链是否存在目标错误。
func Is(err, target error) bool

// As 检查当前错误链上是否存在目标类型。若存在则ok为true,e为类型转换后的结果。若不存在则ok为false,e为空值
func As(type E)(err error) (e E, ok bool)

refer to

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

https://github.com/golang/go/labels/LanguageChange

https://github.com/pkg/errors

https://go.googlesource.com/proposal/+/master/design/go2draft-error-values-overview.md

https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling.md


WilburXu
124 声望29 粉丝

do not go gentle into that good night.