The most troublesome problem of Golang error handling is that the code is full of "if err != nil", which destroys the readability of the code. This article collects several examples to let everyone understand how to optimize such problems.
Let's take a lookErrors are values An example mentioned in:
_, err = (p0[a:b]) if err != nil { return err } _, err = (p1[c:d]) if err != nil { return err } _, err = (p2[e:f]) if err != nil { return err }
As the above code can't intuitively see what its original intention is at first glance, the improved version:
type errWriter struct { w err error } func (ew *errWriter) write(buf []byte) { if != nil { return } _, = (buf) } ew := &errWriter{w: fd} (p0[a:b]) (p1[c:d]) (p2[e:f]) if != nil { return }
Encapsulated by custom type errWriter and error is encapsulated. The new type has a write method, but its method signature does not return an error, but it returns immediately once there is a problem within the method. With these preparations, we can put forward the error judgments that were originally interspersed in the business logic and put them to the end for unified call, so as to visually ensure that people can intuitively see what the original intention of the code is.
Let's take a look againEliminate error handling by eliminating errors Another example mentioned in:
type Header struct { Key, Value string } type Status struct { Code int Reason string } func WriteResponse(w , st Status, headers []Header, body ) error { _, err := (w, "HTTP/1.1 %d %s\r\n", , ) if err != nil { return err } for _, h := range headers { _, err := (w, "%s: %s\r\n", , ) if err != nil { return err } } if _, err := (w, "\r\n"); err != nil { return err } _, err = (w, body) return err }
The first feeling is that since the errors are returned and are we going to re-encapsulate them? In fact, the real source is their parameters. Because if the Writer method is called directly, there is a return value error in the method signature, so each step and operation have to be repeated error processing, which looks bad. The improved version:
type errWriter struct { err error } func (e *errWriter) Write(buf []byte) (int, error) { if != nil { return 0, } var n int n, = (buf) return n, nil } func WriteResponse(w , st Status, headers []Header, body ) error { ew := &errWriter{Writer: w} (ew, "HTTP/1.1 %d %s\r\n", , ) for _, h := range headers { (ew, "%s: %s\r\n", , ) } (ew, "\r\n") (ew, body) return }
Encapsulate by custom type errWriter, and encapsulate error, and rewritten the Writer method. Although there is still a return value error in the method signature, we save a copy of error separately and return it immediately once there is a problem within the method. With these preparations, the new version of WriteResponse no longer has duplicate error judgments. You just need to check the error at the end.
Similar practices are common in the Golang standard library, let's continue to look at themEliminate error handling by eliminating errors An example of and mentioned in:
func CountLines(r ) (int, error) { var ( br = (r) lines int err error ) for { _, err = ('\n') lines++ if err != nil { break } } if err != { return 0, err } return lines, nil }
We construct a , and then call the ReadString method in a loop . If the end of the file is read, then ReadString will return an error ( ). In order to judge such a situation, we have to judge "if err != nil" every time the loop, which looks like a bad taste, improved version:
func CountLines(r ) (int, error) { sc := (r) lines := 0 for () { lines++ } return lines, () }
In fact, compared with , , is a higher-order type. To put it simply, it is equivalent to abstraction. By replacing the lower-order , the loop no longer needs to judge "if err != nil" in the loop, because the Scan method signature no longer returns error, but returns bool. When the end of the file is read in the loop, the loop ends directly. In this way, we can call the Err method at the end to determine whether it is successful or failure. Let's take a look at the definition of Scanner:
type Scanner struct { r // The reader provided by the client. split SplitFunc // The function to split the tokens. maxTokenSize int // Maximum size of a token; modified by tests. token []byte // Last token returned by split. buf []byte // Buffer used as argument to split. start int // First non-processed byte in buf. end int // End of data in buf. err error // Sticky error. empties int // Count of successive empty tokens. scanCalled bool // Scan has been called; buffer is in use. done bool // Scan has finished. }
It can be seen that Scanner encapsulates , and encapsulates error , which is consistent with the practice we discussed before. One thing to explain is that if you actually look at the Scan source code, you will find that it does not judge whether it ends through err, but whether it ends through done. This is because Scan only exits when encountering an error in ending the file, and other errors will continue to be executed. Of course, this is just a specific detail and does not affect our conclusion.
Through the analysis of the above examples, we can draw an approximate routine of optimizing repeated error handling: by creating a new type to encapsulate the old type that was originally done dirty work, and at the same time encapsulate errors in the new type. The method signatures of the new and old types can be kept compatible or incompatible. This is not critical and depends on the objective situation. As for the specific logical implementation, first determine whether there is an error, exit directly if there is one, continue to execute, and save the possible error during the execution process for later operations to be used, and finally complete the error handling by calling the new type of error. As a reminder, the disadvantage of this solution is that it will not be possible to know whether there are any errors until the end. Fortunately, such a control granularity is not a big deal in most of the time.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for your study or work. Thank you for your support.