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.