Recently, I wrote a program, because it is urgent (it seems that there is no need to be in a hurry...), so this program is copied by me in the east and glued in the west. After the code was written, I felt like the code was shit, and I almost cried myself. I really wrote it in my heart, don’t scold me, first I scold the people who worked on this project before for being stupid, the project was like shit, and then the code ran, and after the smooth delivery, I became scold me for being stupid, so it’s not impossible to write like this use!

picture
It's not that it can't be used

However, in this process, let's not mention the unreasonable business logic and interface design in the project. I think that when the time is tight and the personnel change quickly, normal people will stick to it if they can. Pack another layer, don't fix the online problem. One thing I made myself cry stupidly is that this error handling in Go is too stupid. I wrote seven or eight wrong judgments in a program. Let me describe it in pseudo code for you:

err, file :=  接收传文件(文件)
if err != nil {
  记日志
  返回错误码相应
}

err, fh :=  打开上传文件(file)
if err != nil {
  记日志
  返回错误码相应
}

err, data := 把文件里的行记录解析/转换一下(row)
if err != nil {
  记日志
  返回错误码相应
}

err, data3 := 调一下第三方接口拿数据
if err != nil {
  记日志
  返回错误码相应
}

err, data2 := 调一下内部其他服务拿数据
if err != nil {
  记日志
  返回错误码相应
}

err := 写库
if err != nil {
  记日志
  返回错误码相应
}

The above example is no exaggeration. I believe you must have seen it in your own projects. If you are doing business development, it will be more common.

Some people here will definitely ask, Go's error handling is like this, can you still cry stupidly when you see it on the first day. Hey, it's not that after cost reduction and efficiency improvement, the number of staff is reduced by half. Our gang of dotted leaders who haven't been mixed up in the level, don't they start writing code by themselves again, they used to be stupid and not stupid enough. In addition, the previous system, project layering, and service isolation are still working together. It will not be like the above, adjusting so many business objects at the control layer, and concentrating the stupid code... The sense will be different immediately. .

So I was thinking, is there any design pattern or something that can hide these things? There should be. There is nothing that cannot be solved by wrapping one layer of code. I accidentally said the essence of design patterns.

Several options for graceful handling of errors in Go

I have read a lot on the Internet these days, Go error handling, but basically they are about how to customize the packaging of errors, pass errors, etc., and compare articles on how to write Go code more elegantly and beautifully Less, the best ones are the two methods introduced by Mr. Left-eared Mouse in his blog.

The following part of the code refers to the teacher's blog: https://coolshell.cn/articles/21140.html

One is to use the Closure of functional programming to abstract the same code like if err !=nil and redefine a function, but this method will lead to new problems-internal functions and functions need to be introduced in each function. An error variable, so we won't say more. If you are interested, you can go to the original blog post.

Here is another better solution that is not too big for project intrusion. The implementation of object error handling in the official library of the Go language  bufio can  Scannergive us a little inspiration, and it is probably implemented like this.

scanner := bufio.NewScanner(input)

for scanner.Scan() {
    token := scanner.Text()
    // process token
}

if err := scanner.Err(); err != nil {
    // process the error
}

From the above code, we can see that scannerwhen operating the underlying I/O, there is nothing in the for-loop,  and there is a  check if err !=nil after exiting the loop  . scanner.Err()It seems to use the structure of the way.

Let's look at  Scannerthe definition of a type

type Scanner struct {
 r            io.Reader
  ...//其他字段省略
 err          error    
}

This type internally holds an error error that will be recorded in this error when an error is encountered during the iterative execution of the Scan method.

func (s *Scanner) Scan() bool {
  ...// 其余代码省略
 for {
   if err != nil {
    s.setErr(err)
    return false
   }
}
  
func (s *Scanner) Err() error {
 if s.err == io.EOF {
  return nil
 }
 return s.err
}

So we can refer to this idea to continue. For example, to read a business object

picture

The above example is easy for everyone to understand. However, its usage scenario can only simplify error handling under the continuous operation of the same business object. For multiple business objects, various  if err != nilmethods are still required.

So what is the solution? We said it once before: there is nothing that cannot be solved by wrapping one layer of code. If it is not possible, wrap it in two layers. Then we will do another layer of packaging. The following is my understanding of solving this problem. I will learn from the concept of layering in DDD to solve this problem.

Easier to implement

The problem with the example just now is that it is only suitable for reducing the if err != nill judgment in the logical operation of a single business object, so for this, we can put operations involving multiple business objects in an application service, and put the operations in the business object just now in one application service. The error handling judgment made by the object is transferred to the application service, so that in the business object, such as the lower-level module such as Model, the code can be written according to the normal process, and there is no need to judge at the beginning of each method.

Let me say in advance that in some architectural designs, there will be application services and domain services. The concepts of the two are completely different. Application services are implemented for use cases that meet product requirements. They are responsible for the task coordination of business use case flow, which is how we implement API. When the control layer is used, the application service is often adjusted by the control layer, and multiple different business objects can be placed in one application service. The domain service is dedicated to one domain, so I won't explain more about it. I have also read a few books on DDD and the implementation of the COLA framework, but I still don't understand it.

In short, keep in mind that multiple business objects can be coordinated to perform tasks through application services. At the same time, the error handling added by the business objects above is extracted into the application service layer, so that the business objects can focus more on their own responsibilities. In this case, your service layer code may have to become like this

picture

Then our control layer calls the application service layer to get the result, and at this time determines whether there is any error in the execution of the entire requirement task, records the error if there is, and returns an error response to the client.

picture

Basics of Error Handling in Go

I shared   some suggestions for error handling in Go programs before, which  said how we should make good use of Go's error interface, customize errors, wrap the entire error chain and other related skills. In conjunction with the content of this article, you may have a more global understanding of error handling, which is also recommended here.

Summarize

Today I share with you some of the things I learned and thought about making error handling in Go code more elegant. In fact, you can find that we have scattered multiple if err != nil into multiple methods, so that the code looks better from the sense of at least than writing seven or eight wrong judgments in one method.

What are your opinions on error handling? You are welcome to actively speak in the comment area. If you like this article, please like and share it. The next content is still beckoning to you 🙋‍♂️.

- END -


Scan the QR code to follow the official account "Network Management Naobi Nao"

picture

Give the network manager a star and absorb my knowledge as soon as possible 👆

The network administrator has compiled a "Go Development Reference Book" and collected more than 70 development practices. Go to the official account to reply to [gocookbook] to get it! There is also a "k8s Getting Started Practice" that explains the deployment process of common software on K8s, and you can get it by replying to [k8s] on the official account!


If you find it useful, just click and   watch👇👇👇