More elegant error handling in go language

Time:2020-4-6

From the current situation

One of the criticisms of go language is its error handling mechanism. If you explicitly check and deal with each error, it’s really daunting. Next, we will show you how to handle errors more gracefully in the go language.

For the error handling principles in golang, developers have published several articles (error handling and go and defer, panic, and recover, errors are values) to introduce them. In this paper, we introduce the mechanism of dealing with the commonly predicted errors and the errors when encountering the crash in golang.

In general, let’s take the example of error handling in the official blog:

func main() {
 f, err := os.Open("filename.ext")
 if err != nil {
 log.Fatal(err)
 //Or more simply:
 // return err
 }
 ...
}

Of course, there is another way to simplify the number of lines of code:


func main() {
 ...
 if f, err = os.Open("filename.ext"); err != nil{
 log.Fatal(err)
 }
 ...
}

Under normal circumstances, golang’s existing philosophy requires you to handle all error returns manually as much as possible, which slightly increases the mental burden on developers. For the discussion of this part of the design, please refer to the reference link provided at the beginning of this article.

In essence, the error type in golang is an interface type:


type error interface {
 Error() string
}

As long as all the values defined by this interface are satisfied, an error type location can be passed in. The description of errors is also mentioned in go Proverbs: errors are values. How to understand this sentence?

Errors are values

In fact, in practice, you may also find that for golang, all the information is very insufficient. For example:


buf := make([]byte, 100)
n, err := r.Read(buf)
buf = buf[:n]
if err == io.EOF {
 log.Fatal("read failed:", err)
}

In fact, it only prints information2017/02/08 13:53:54 read failed:EOF, which has no significance for debugging and analysis of errors in our real environment. When we check the log to get error information, we can get very limited information.

As a result, some error handling forms that provide context are very common in many class libraries:


err := os.Remove("/tmp/nonexist")
log.Println(err)

Output:


2017/02/08 14:09:22 remove /tmp/nonexist: no such file or directory

This method provides a more intuitive context information, such as specific error content, error file and so on. By looking at the implementation of remove, we can see:

// PathError records an error and the operation and file path that caused it.
type PathError struct {
 Op string
 Path string
 Err error
}

func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

//The implementation of file_unix.go for * Nix system
// Remove removes the named file or directory.
// If there is an error, it will be of type *PathError.
func Remove(name string) error {
 // System call interface forces us to know
 // whether name is a file or directory.
 // Try both: it is cheaper on average than
 // doing a Stat plus the right one.
 e := syscall.Unlink(name)
 if e == nil {
 return nil
 }
 e1 := syscall.Rmdir(name)
 if e1 == nil {
 return nil
 }

 // Both failed: figure out which error to return.
 // OS X and Linux differ on whether unlink(dir)
 // returns EISDIR, so can't use that. However,
 // both agree that rmdir(file) returns ENOTDIR,
 // so we can use that to decide which error is real.
 // Rmdir might also return ENOTDIR if given a bad
 // file path, like /etc/passwd/foo, but in that case,
 // both errors will be ENOTDIR, so it's okay to
 // use the error from unlink.
 if e1 != syscall.ENOTDIR {
 e = e1
 }
 return &PathError{"remove", name, e}
}

In fact, a structure named patherror is returned in the golang standard library, which defines the operation type, path and original error information, and then integrates all the information through the error method.

But there are also problems, such as the need for separate type complex classification processing. For example, in the above example, the need for separate processing of patherror. You may need a separate type derivation:


err := xxxx()
if err != nil {
 swtich err := err.(type) {
 case *os.PathError:
 ...
 default:
 ...
 }
}

This in turn increases the complexity of error handling. At the same time, these errors must be changed to export type, which will increase the complexity of the whole system.

Another problem is that when we make an error, we usually want to get more stack information to facilitate our subsequent fault tracking. In the existing error system, this is relatively complex: it is difficult for you to get the complete call stack through an interface type. At this time, we may need a third-party warehouse to solve these problems.

In another case, we hope to attach some information to the error handling process, which will be relatively troublesome.

More elegant error handling

Previously, we mentioned the error handling methods and some problems encountered in a variety of practical application scenarios. Here, we recommend using a third-party library to solve some problems:github.com/pkg/errors

For example, when we have problems, we can simply use errors.Newperhapserrors.ErrorfGenerate an error variable:


err := errors.New("whoops")
// or
err := errors.Errorf("whoops: %s", "foo")

When we need additional information, we can use:


cause := errors.New("whoops")
err := errors.Wrap(cause, "oh noes")

When you need to get the call stack, you can use:


err := errors.New("whoops")
fmt.Printf("%+v", err)

Other suggestions

When doing type derivation above, we found that multiple error types may be required when dealing with a type of error, which may be relatively complex in some cases. In many cases, we can use the interface form to facilitate processing:


type temporary interface {
 Temporary() bool
}

// IsTemporary returns true if err is temporary.
func IsTemporary(err error) bool {
 te, ok := errors.Cause(err).(temporary)
 return ok && te.Temporary()
}

This can provide more convenient error resolution and processing.

summary

The above is the whole content of this article. I hope that the content of this article can bring some help to your study or work. If you have any questions, you can leave a message for communication.

Recommended Today

Python basics Chinese series tutorial · translation completed

Original: Python basics Python tutorial Protocol: CC by-nc-sa 4.0 Welcome anyone to participate and improve: a person can go very fast, but a group of people can go further. Online reading Apache CN learning resources catalog introduce Seven reasons to learn Python Why Python is great Learn Python introduction Executing Python scripts variable character string […]