SoFunction
Updated on 2025-03-04

Optimization method for repeated error handling in Golang

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.