preface

In this happy weekend, when everyone goes out to play, I choose to stay, output a wave of articles, not for anything else, just for that one — big tea pot.

So, what I’m going to talk about today is error handling in Go, and there are a lot of differences between error handling in Go and what I do in Javascript, so I’m going to talk about what those differences are.

Resource management and error handling

First of all, before the introduction of error handling, we must say a point is resource management, most of the time in addition to some grammar mistakes, more often, is when reading and writing some resource files, some errors, so here first to talk about resource management issues.

For example, there is a need to get some data on the Internet, and then output it into a file, the file can be accessed through the interface, this is one of the most basic read and write tasks.

So let’s first look at how to output some data into a file in Go

func writeFile(filename string)  {
	file, e := os.Create(filename)
	ife ! =nil {
		panic(e)
	}
	defer file.Close() / / close
	writer := bufio.NewWriter(file)
	defer writer.Flush() // Write to memory

	for i := 0; i < 10; i++ {
		_, _ = fmt.Fprintln(writer, i)
	}
}

func main(a)  {
	writeFile("test.txt")}Copy the code

You can see here that I used a syntax — defer, so what does this syntax do

What defer

Actually, as defer is used, it looks like the onion model in KOA2. As defer comes after the function, it is executed in the order of first in and last out of the stack.

So this is going to look like this

func test2(a)  {
	fmt.Println(1)
	fmt.Println(2)
	defer fmt.Println(3)
	fmt.Println(4)
	defer fmt.Println(5)}// Execute result 1 2 4 5 3
Copy the code

As in the example above, you can just as soon as you create the create, follow the defer close instead of waiting for the next write to finish. As in, I forgot this step.

Heavy panic

Different languages have different ways of throwing errors. For example, in Javascript, throwing is used to do this. In Go, there is also a syntax for throwing errors.

It is also very simple to use

func throw(a) error {
	e := errors.New("this is err")
	return e
}

func main(a) {
	e :=throw()
	panic(e)
}
Copy the code

Many times, however, we can’t do this because panic will cause an application to crash. Although there is a protection mechanism in HTTP Listen (more on this later), the overall experience is not very good, so generally for known errors, We are not going to panic directly.

It is important to note that even after the Panic, the deferred in the function will be executed one layer at a time.

Elegant error message

Most of the time, when we are dealing with some error messages, we will have a total error validation function, which accepts all the error messages for uniform processing.

For example, to read the above written TXT file, we generally say that

func handleData(writer http.ResponseWriter, request *http.Request) {
	path := request.URL.Path[len("/test/"):]
	file, e := os.Open(path)
	ife ! =nil {
		panic(e)
	}
	defer file.Close()
	bytes, e := ioutil.ReadAll(file)
	ife ! =nil {
		panic(e)
	}
	writer.Write(bytes)
}

func main(a) {
	http.HandleFunc("/test/", handleData)
	serve := http.ListenAndServe(": 8000".nil)
	ifserve ! =nil {
		panic(serve)
	}
}
Copy the code

In the example above, I chose to throw the error directly, but that would be unfriendly if we had a total error handling mechanism that would accept all the error problems without the logical function doing the error handling, which would just throw the error.

Then the overall implementation idea would look like this

type AppHandleError func(writer http.ResponseWriter, request *http.Request) error

func WebError(handleError AppHandleError) func(writer http.ResponseWriter, request *http.Request)  {
	return func(writer http.ResponseWriter, request *http.Request) {
		err := handleError(writer, request)
		iferr ! =nil {
			switch  { // Each error is handled differently
			case os.IsNotExist(err):
				http.Error(writer, http.StatusText(http.StatusNotFound),
                                http.StatusNotFound)
                        default:
				http.Error(writer, http.StatusText(http.StatusInternalServerError),
                                http.StatusInternalServerError)
			}
		}
	}
}
Copy the code

A few minor changes have been made here to return the error to the unified error handler, which is handled by an external error handler, so that the error handling is abstracted from the logic code.

func handleData(writer http.ResponseWriter, request *http.Request) error {
	path := request.URL.Path[len("/test/"):]
	file, e := os.Open(path)
	ife ! =nil {
		return e 
	}
	defer file.Close()
	bytes, e := ioutil.ReadAll(file)
	ife ! =nil {
		return e
	}
	writer.Write(bytes)
	return nil
}
func main(a) {
	http.HandleFunc("/test/", appError.WebError(handleData)) // Wrap it with total error handling
	serve := http.ListenAndServe(": 8000".nil)
	ifserve ! =nil {
		panic(serve)
	}
}
Copy the code

So that’s what happens in terms of errors, rather than the whole service going down.

Go for the “try… The catch…”

Believe that for try… catch… In Go, there is no try… catch… Syntax, but there is something similar, which is — recover, which is the same as try… catch… A Go syntax that does the same thing.

Recover also has a very important function to protect the process from failure, and HTTP. Listen is also used to protect the process.

Here we also use the example above as an example, for example externally we are not listening to /test/, but only a simple /.

func main(a) {
	http.HandleFunc("/", appError.WebError(handleData)) // The route is changed
	serve := http.ListenAndServe(": 8000".nil)
	ifserve ! =nil {
		panic(serve)
	}
}
Copy the code

When we make an error request, it will not be 404, it will look like this, and it will get a lot of red on the command line

This is not what we want. We want to see some clear error messages. To do this, we need to use recover.

Use instructions for recover

To illustrate the exact use of recover, start with a simple 🌰

func ThrowErr(a)  {
	defer func(a) {
		r := recover(a)if e,file := r.(error); file {
			fmt.Print("this is error",e) // this is error
		} else {
			panic("this is I down know error")}} ()panic(errors.New("this is error"))}Copy the code

Here defer to accept an anonymous function to the execution, and another one in the anonymous function recover to do out of work, we can see that when we return to a mistake is a can identify the fault, he would hit this error content directly on the command line, rather than give me a bunch of red.

Of course, if it is an unrecognized error, it is simply thrown via panic.

Combine the example, summarize again

Here I use recover in conjunction with the above example of reading a file, which looks like this

func WebError(handleError AppHandleError) func(writer http.ResponseWriter, request *http.Request)  {
	return func(writer http.ResponseWriter, request *http.Request) {
		defer func(a) {
			r := recover(a)if e,ok := r.(error); ok {
				fmt.Print("this is error \n",e)
				http.Error(writer, "please check /",http.StatusNotFound)
			} else {
				panic(e)
			}
		}()// Only need to add a bottom line processing
		err := handleError(writer, request)
		iferr ! =nil {
			switch  {
			case os.IsNotExist(err):
				http.Error(writer, http.StatusText(http.StatusNotFound),
                                http.StatusNotFound)
			default:
				http.Error(writer, http.StatusText(http.StatusInternalServerError),
                                http.StatusInternalServerError)
			}
		}
	}
}
Copy the code

A simple recover is needed to prevent your process from crashing, and HTTP Listen also uses this to protect the port from exiting in case of an error.

conclusion

While Go’s error-handling mechanism is quite cumbersome, there’s nothing like try… Catch such a shuttle, but this way can also help us write more robust code, at the same time at the time of processing, can also be convenient we fast positioning to the wrong location, the last reading than direct a poker to a lot of good, is in the development phase will be more complicated.

I am still learning Go side dish chicken, let’s refueling together, for the big teapot.