More elegant error handling in the Go language

  • 2020-06-07 04:37:11
  • OfStack

Start with the status quo

One of the most criticized aspects of Go is its error handling mechanism. If each error were explicitly examined and processed, this would be daunting indeed. Now we will introduce you to the more elegant error handling in the Go language.

The error handling principles in Golang have been published in several articles (Error handling and Go and Defer, Panic, and Recover, Errors are values). The error handling mechanisms in Golang for handling 1 - type predicted errors and for handling crashes are described respectively.

In general, let's take the error handling example in the official blog as an example:


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

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


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

Normally, Golang's existing philosophy requires you to handle all error returns by hand as much as possible, which puts a slight burden on the developer's mind. For a discussion of this section of the design, please refer to the reference links provided at the beginning of this article, without much discussion here.

Essentially, the error type in Golang is 1 interface type:


type error interface {
 Error() string
}

All values that satisfy this 1 interface definition can be passed in as error type locations. The error description 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 all the information for Golang is grossly inadequate. Take the following example:


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

It actually just prints the information 2017/02/08 13:53:54 read failed:EOF , which is of no significance to our error debugging and analysis in the real environment. The information we can get when looking at the log to get error information is limited by 10 points.

As a result, error handling forms that provide context are common in many 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 approach provides a more intuitive context, such as the specific content of the error, or the 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() }

// file_unix.go  for  *nix  Implementation of the 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, the Golang standard library returns a struct named PathError, which defines the type of operation, the path, and the original error message, and then consolidates all the information through the Error method.

However, there are also problems, such as the need for a separate type of complex classification processing, such as in the above example, the need for a separate type 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 have to be exported, which adds complexity to the entire system.

Another problem is that when we make an error, we usually also want to get more stack information so that we can follow up on the failure trace. In the current system of errors, this is relatively complex: it is difficult to get a full call stack from one interface type. At this point, we may need a third repository area to resolve these error handling issues encountered.

In another case, we want to add some information to the error handling process as well, which is also relatively cumbersome.

More elegant error handling

The error handling methods and problems encountered in various practical application scenarios have been mentioned before. It is recommended to use the third square library to solve some of the problems: github.com/pkg/errors .

For example, when we have problems, we can simply use it errors.New or errors.Errorf Generate 1 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:


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

Other Suggestions

When doing the type derivation above, we found that there may be several error types needed to deal with the type 1 error, which may be relatively complex in some cases. In many cases, we can use the interface form to facilitate the processing:


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

This provides easier error resolution and handling.

conclusion

The above is the whole content of this article, I hope the content of this article can bring 1 definite help to your study or work, if you have any questions, you can leave a message to communicate.


Related articles: