1. Overview
Golang’s error handling has been a topic of much discussion. When I first started working with Golang, I read some documentation about error handling, but I didn’t really care about it. After using Golang for a while, I felt that I might not be able to ignore this issue. So, this article is mainly to organize some common error handling tips and principles in Golang.
2. Techniques and principles of error handling
2.1 Using wrappers to avoid repetitive error judgments
The most common line of code in a Golang project is definitely if err ! = nil
. Golang takes errors as return values, so you have to deal with them. But sometimes, error handling judgments can take up half of your code, which makes it look messy. There is an example of this in the official blog.
Yes, you read that right, it’s actually a 3-line call to fd.Write
, but you have to write 9 error judgments. So the official blog also gives a more elegant solution: wrap io.Writer
in another layer.
|
|
Now it looks much better, the write(buf []byte)
method determines the error value internally, to avoid multiple errors outside. Of course, this way of writing may have its drawbacks, for example, you have no way to know which line the error is called on. In most cases, you just need to check for errors and then handle them. The Golang standard library has many similar tricks. For example
Where b.Write
is returned with an error value, this is just to comply with the io.Writer
interface. You can do it again when you call b.Flush()
for the error value.
2.2 Error handling before Golang 1.13
Checking for errors
In most cases, we just need to make a simple determination of the error. Because we don’t need to do anything else with the error, we just need to make sure that the code logic is executed correctly.
However, there are times when we need to handle the error differently depending on the type of error, for example, if the error is caused by a network connection not connected/disconnected, we should perform a reconnection operation when we determine that it is a network not connected/network disconnected. When it comes to the determination of the error type, we usually have two methods.
-
compare the error with a known value
-
Determine the specific type of error
Adding information
When an error returns up the call stack after multiple levels, we usually add some additional information to that error to help the developer determine where the program was running and what happened when the error occurred. The easiest way to do this is to construct a new error using the information from the previous error.
Using fmt.Errorf
only keeps the text of the previous error and discards all other information. If we want to keep all the information from the previous error, we can use the following.
2.3 Error Handling in Golang 1.13
In Golang 1.13, if an error contains another error, the underlying error can be returned by implementing the Unwrap()
method. If e1.Unwrap()
returns e2, we can say that e1 contains e2.
Using Is and As to check for errors
After mentioning common ways of handling error messages in 2.2, several methods were added to the standard library in Golang 1.13 to help us do the above more quickly. The current prerequisite is that your custom Error correctly implements the Unwrap()
method
errors.Is
is used to compare an error to a value.
errors.As
is used to determine if an error is of a specific type.
When manipulating a wrapped error, Is
and As
consider all the errors in the error chain. A complete example is as follows.
|
|
The output is as follows.
Wrapping errors with %w
The %w
was added in Go 1.13. Errors returned by fmt.Errorf
when %w occurs will have the Unwrap
method, which returns the value corresponding to %w
. The following is a simple example.
|
|
The output is as follows.
Whether to wrap the error
When you add additional contextual information to an error, either by using fmt.Errorf
or by implementing a custom error type, it is up to you to decide whether this new error should wrap the original error information. This is a question that has no standard answer, it depends on the context in which the new error is created.
The purpose of wrapping an error is to expose it to the caller. This allows the caller to handle the error differently depending on the original error, for example os.Open(file)
will return a specific error like file does not exist, so that the caller can create the file to allow the code to execute correctly down the line.
If you don’t want to expose implementation details, don’t wrap the error. Because exposing an error with details means that the caller is coupled to our code. This also violates the principle of abstraction.