Defer to invoke
Typically used for resource management, defer ensures that it is called at the end of the function
func tryDefer(){
defer fmt.Println(1)
defer fmt.Println(2)
fmt.Println(3)
}
func main() {
tryDefer()
}
Copy the code
Looking at the print order, you can see that defer can ensure that the program will be executed, even if the return or panic is in the middle
Take a look at defer in action
Func writeFile(filename string) {// Create a file file, err := os.create (filename) if err! = nil {panic (err)} / / since the open a file It must remember Close defer file. The Close () / / write data to the cache writer: = bufio. NewWriter (file) / / since using the cache Remember to flush defer writer.flush () // Write the corresponding data to the cache fmt.fprintln (writer, "hello golang wuyue111")}Copy the code
Still, it works fine, and compared to Java, this defer feature ensures that we don’t have the bug of forgetting to close the resource
Also note that the parameters are evaluated in the defer statement
For example:
func tryDefer() {
for i := 0; i < 10; i++ {
defer fmt.Println(i)
if i==5 {
panic("too many times")
}
}
}
Copy the code
Take a look at the execution result:
Error handling
As can be seen from the previous programs, Panic directly causes the program to terminate, just like in Java, an exception that no one can handle directly causes the program to stop running. Normally we should avoid this situation. That’s why you need correct error handling
Such as:
Func writeFile2(filename string) {// Create a file file, err := os.openFile (filename string) os.O_EXCL|os.O_CREATE, 0666) if err ! = nil { fmt.Println("file aleady exists") } defer file.Close() }Copy the code
This is a more friendly way of indicating that we can see what the error is
Well, it’s an interface, but the interface is an Error method
Func writeFile2(filename string) {// Create a file file, err := os.openFile (filename string) os.O_EXCL|os.O_CREATE, 0666) if err ! = nil { fmt.Println(err.Error()) } defer file.Close() }Copy the code
So here we are, we can continue to look at this openFile function
That means if there’s an error there it must be a patherror pointer type
Func writeFile2(filename string) {// Create a file file, err := os.openFile (filename string) os.O_EXCL|os.O_CREATE, 0666) if err ! = nil { pathError, ok := err.(*os.PathError) if ! ok { panic(err) } else { fmt.Println("op:", pathError.Op, " path:", pathError.Path, " err:", pathError.Err) } } defer file.Close() }Copy the code
Let’s move on:
The other thing is that normally when we do an error we add a return statement to make sure that we don’t get into the wrong logic later in the program
Func writeFile2(filename string) {// Create a file file, err := os.openFile (filename string) os.O_EXCL|os.O_CREATE, 0666) if err ! = nil { pathError, ok := err.(*os.PathError) if ! ok { panic(err) } else { fmt.Println("op:", pathError.Op, " path:", pathError.Path, " err:", Patheror.err)} return return} defer file.close ()}Copy the code
Of course, we can also create an error ourselves
Func writeFile2(filename string) {// Create a file file, err := os.openFile (filename string) os.O_EXCL|os.O_CREATE, Error if filename == "test.txt" {err = errors.New("file name can not be test.txt")} if err! = nil { pathError, ok := err.(*os.PathError) if ! ok { panic(err) } else { fmt.Println("op:", pathError.Op, " path:", pathError.Path, " err:", Patheror.err)} return return} defer file.close ()}Copy the code
Unified handling of error logic
Let’s write a simple Web application
So I have all these go files down here and I want to see the go file when I type in the path in the browser
The procedure is simple:
func main() { http.HandleFunc("/list/", func(writer http.ResponseWriter, Path := request.url. path [len("/list/"):] err := os.Open(path) if err ! = nil {panic(err)} defer file.close () // All, err := ioutil.readall (file) if err! Write(all)}) err := HTTP.ListenAndServe(":8888", nil) if err! = nil { panic(err) } }Copy the code
You can also see the effect:
So what do you get if you put in the wrong path?
Obviously this is not correct, look at our server side:
Although the HTTP library protects against panic on the server from directly crashing the server, the experience on the browser for visitors here is extremely poor
To improve:
func main() { http.HandleFunc("/list/", func(writer http.ResponseWriter, Path := request.url. path [len("/list/"):] err := os.Open(path) if err ! Http.error (writer, err.error (), HTTP. StatusInternalServerError) return} to defer the file. The Close () / / read the file content all, err: = ioutil. ReadAll (file) if err! Write(all)}) err := HTTP.ListenAndServe(":8888", nil) if err! = nil { panic(err) } }Copy the code
It seems so much more normal
Besides, the error handling here is obviously too much to change, and normally we should write a function to handle it all
First we can return all the errors of handle
func handlerFileListing(writer http.ResponseWriter, Path := request.url. path [len("/list/"):] err := os.Open(path) if err ! = nil {// If there is an error then simply return err} defer file.close () // Read the contents of the file. Writer.write (all) writer.write (all) if there is no error, return nil}Copy the code
For convenience, we’ll give this function an alias:
AppHandler func(writer http.responseWriter, request * http.request) errorCopy the code
And finally let’s deal with the error of this function
Func errWrapper(handle appHandler) func(http.responsewriter, *http.Request) { return func(writer http.ResponseWriter, request *http.Request) { err := handle(writer, request) if err ! = nil { logger := zap.NewExample() code := http.StatusOK switch { case os.IsNotExist(err): logger.Warn("handle file not exist") code = http.StatusNotFound http.Error(writer, http.StatusText(code), code) default: code = http.StatusInternalServerError } } } }Copy the code
Finally, call her in a different form:
func main() { http.HandleFunc("/list/", errWrapper(handlerFileListing)) err := http.ListenAndServe(":8888", nil) if err ! = nil { panic(err) } }Copy the code
panic
We have seen the use of panic several times in the past, so to summarize the use of the panic keyword:
Stop the execution of the current function. Panic will continue to go back up and execute each layer’s defer. If recover is not found, the program will exit.
Corresponding to this is the recover key function
Recover can only be used in the defer call and can also get the value of Panic, so you can re-pani if panic is not handled
func tryRecover() {
defer func() {
r := recover()
if err, ok := r.(error); ok {
fmt.Println("find error",err)
}else{
panic(r)
}
}()
//panic(errors.New("new error"))
panic("just string")
}
Copy the code
You can look at the running state of the program in two different panic scenarios
And the other thing is that panic could be nil, so we have to null it
func tryRecover() {
defer func() {
r := recover()
if r == nil {
fmt.Println("panic is nil")
return
}
if err, ok := r.(error); ok {
fmt.Println("find error", err)
} else {
panic(r)
}
}()
//panic(errors.New("new error"))
//panic("just string")
}
Copy the code
Finally, to sum up, when I design the program:
For unexpected errors, use error; for unexpected errors, use panic