The error processing of Go has always been criticized by people. However, from another perspective, error of Go is actually quite useful. Error has two important features:

  • Error is just a plain value that has no overhead to process
  • Error is very extensible and can be customized for different scenarios

And after Go1.13, some functions are provided in the errors package to make error handling and tracking a little easier. This article discusses common use of error in Go, in conjunction with the Errors function.

Note that the Errors package here refers to the native Errors package in Go, not github.com/pkg/errors, which is a wrapper around native errors that is easier to use.

1. The original error

The following code dominates the error handling of Go:

iferr ! =nil {
   //....
   return err
}
Copy the code

This kind of error handling is actually the most recommended approach when meeting business requirements, as this direct pass-through approach leads to less coupling between code. In many cases, if you don’t care about the specific information in the error, this is fine.

2. Define the error in advance

Native error is not very convenient in some cases, such as when I need to get specific error information. If I use error in the same way as above, I might get the following code:

iferr ! =nil && err.Error() == "invalid param" {
		/ /...
}
Copy the code

Anyone who has written any code knows that the above code is very inelegant, for one thing, the magic value is used, and the logic of the code is broken if the wrong message changes.

We can do this by defining errors as variables:

var (
    ErrInvalidParam = errors.New("invalid param"))Copy the code

The above code would look like this:

iferr ! =nil && err == ErrInvalidParam {
   / /...
}
Copy the code

If a large number of errors need to be handled at one time, switch can be used to handle them:

iferr ! =nil {
		switch err {
		case ErrInvalidParam:
			/ /..
			return
		case ErrNetWork:
			/ /...
			return
		case ErrFileNotExist:
			/ /..
			return
		default:
			/ /...
			return}}Copy the code

This approach is not perfect, however, because error may be wrapped to carry more stack information as it passes, such as the following:

iferr ! =nil {
    // Use %w for formatting errors
		return fmt.Errorf("add error info: %+v, origin error: %w"."other info", err)
}
Copy the code

Assuming that the error wrapped above is ErrInvalidParam, the following code cannot be used to determine the error in the call:

iferr ! =nil && err == ErrInvalidParam {
   / /...
}
Copy the code

To solve this problem, the errors.Is function can determine if there Is an expected error in the wrapped error:

if errors.Is(err, ErrInvalidParam) {
		/ /..
}
Copy the code

Try to use errors.Is instead of error comparisons.

3. Use custom error types

The error usage above may not be sufficient in some cases. If the business wants to know which parameter is invalid for the error parameter above, the error defined directly will not suffice.

Error is an interface, as long as the error method is implemented, it is an error type:

type error interface {
	Error() string
}
Copy the code

You can then customize an error type:

type ErrInvalidParam struct {
    ParamName  string
    ParamValue string
}

func (e *ErrInvalidParam) Error(a) string {
    return fmt.Sprintf("invalid param: %+v, value: %+v", e.ParamName, e.ParamValue)
}
Copy the code

You can then use type assertion or type selection mechanisms to handle different types of errors:

e, ok := err.(*ErrInvalidParam)
ifok && e ! =nil {
	/ /...
}
Copy the code

Also available in switch:

iferr ! =nil {
	switch err.(type) {
	case *ErrInvalidParam:
		/ /..
		return
	default:
		/ /...
		return}}Copy the code

As can be used to solve this problem. It can be used to determine whether there is a certain error type in the packaged error:

var e *ErrInvalidParam
if errors.As(err, &e) {
	/ /..
}
Copy the code

4. More flexible error types

The above approach can handle error handling in most scenarios, but in some complex cases, more information may need to be retrieved from the error, and some logical processing may be involved.

In the Go NET package, there is an interface like this:

type Error interface {
    error
    Timeout() bool  
    Temporary() bool
}
Copy the code

In this interface, there are two methods, and these two methods will handle this error type, determine whether it’s a timeout error or a temporary error, and the error that implements this interface will implement these two methods, implement the specific judgment logic.

When handling a specific error, the corresponding method is called to determine:

if ne, ok := e.(net.Error); ok && ne.Temporary() { 
     // Handle temporary errors
}
Copy the code
if ne, ok := e.(net.Error); ok && ne.Timeout() { 
     // Handle timeout errors
}
Copy the code

This type of error is rarely used. In general, try not to use such a complex processing method.

5. Other abilities in Errors

In the errors package, in addition to the two useful functions errors.Is and errors.As mentioned above, there Is also a more useful function errors.Unwrap. This function resolves the original error from the wrapper error.

Errorf can be used to wrap an error, using %w formatting:

return fmt.Errorf("add error info: %+v, origin error: %w"."other info", err) 
Copy the code

During subsequent error handling, the errors.Unwrap function can be called to get the pre-wrapped error:

err = errors.Unwrap(err)
fmt.Printf("origin error: %+v\n", err)
Copy the code

If you think it is ok, please click a “like” to encourage. If you have any questions, please leave a comment

The text/Rayjun

[1] go. Dev/blog/errors…

[2] go. / dev/blog go1.13…