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…