At first, it was a bit weird to understand how to use defer and recover, especially when using try/catch blocks. There is a pattern that can be implemented in go with the same effect as Try/catch statement blocks. But before you need to understand the essence of defer, panic and recover.
First you need to understand the role of the defer keyword, see the following code:
Package Mainimport ("FMT") func main () {test ()}func minicerror (key string) error {return FMT. Errorf ("Mimic error:%s", key)}func test () {FMT. Println ("Start Test") Err: = Minicerror ("1") defer func () {fmt. Println ("Start defer") if err! = Nil {fmt. PRINTLN ("Defer error:", Err)}} () Fmt. PRINTLN ("End Test")}
The Mimicerror method is a test method used to simulate errors. This method returns errors according to the custom of the Go language.
In go, the error type is defined as an excuse:
Type Error Interface {error () string}
If you don't understand the Go interface Right now, the following content will help. Any variable that implements the type of the error() method can be used as a variable of type error . The Mimicerror method uses the Errors.new (string) method to create a variable of type error. The type of errors can be found in the errors package.
The test method will have the following output:
Start testend teststart deferdefer error:mimic Error:
Look closely at the output of the test method and you will see when the method starts and when it ends. The defer method inside the function is called before the normal end of the test method. Two interesting things happen: First, the method of defer keyword modification is called after the test method is finished. Second, because go supports the use of closures, the ERR variable can be accessed by an internal function, and his error value "Mimic error:1" is output to stdout.
You can define a defer method at any time within your function. If the defer method requires a state, such as the ERR variable in the code above, the variable must already exist before the defer method definition.
The following is a slight modification to the test method:
2
This output is almost indistinguishable from the previous output, with only a little modification. This time the output of the defer method is "mimic error:2". It is clear that the defer method has a reference to the ERR variable. So if the state of the ERR variable changes before the Defer method call, you will see the modified value. Modify the Defer method reference to the ERR variable again. This time use the ERR variable's memory address in the test method and the defer method.
From the output below you will find that the defer method has the same ERR variable address as the test method.
0x20818a250 0x20818a250defer error:mimic error:2
As long as the defer method is placed before the end of the test method, then the defer method is bound to be executed. That's good, but what I want is that every time the test method is executed, the defer method executes first. This is the only way to put this method at the front of the calling method. As Occam said: "If you have two competing theories with exactly the same expectations, then the simpler one is better". What I need is a simple, workable pattern (pattern) that doesn't need to be considered.
The only problem is that the ERR variable needs to be defined in front of the defer statement. Fortunately, go allows the returned variables to be used directly for assignment. Take a look at the following modified code:
Package Mainimport ("FMT") func main () {if err: = Test (); Err! = nil {fmt. Printf ("Mimic error:%v\n", err)}}func Mimicerror (key string) error {return FMT. The Errorf ("Mimic error:%s", key)}func Test (err errorerrErr)}} () Fmt. Println ("Start test")errerr}
The test method defines a variable that returns a type of error. This way the ERR variable exists immediately, and you can access it in the defer statement. Also, the test method follows the convention of the go language-returning an error type to the caller.
Run this code and you will get output like this:
Start Testend teststart deferdefer error:mimic error:1mimic error:mimic error:1
Now is the time to discuss the panic . When any method of go calls the panic , the normal execution process of the program stops. Calling panic's method immediately stops and triggers the panic chain of the method call stack. All methods in the same call stack will stop one after the other, just like dominoes. The final panic chain executes to the top of the stack, and then the program crashes. A good place is all the defer methods are executed in the panic sequence, and they can stop crashing.
The following test method invokes the built-in panic method and recovers from this call:
Take a closer look at the defer method:
Defer func () {fmt. Println ("Start panic defer") if r: = Recover (); R! = Nil {fmt. PRINTLN ("Defer panic:", R)}} ()
The defer method invokes another built-in method called recover. This recover method prevents the panic-triggered burst chain from continuing to call up. The Recover method can only be called in the defer method, because only the defer method can be executed in the panic chain method.
If the recover method is called, but no panic occurs, the Recover method only returns nil. If a panic occurs, then panic stops and the assignment to panic is returned. Instead of calling the Mimicerror method, the last code simulates a panic with the built-in panic method. Output generated after running the code:
Start Teststart deferdefer panic:mimic Panic
The defer method can capture panic, print it on the screen, and stop the panic chain from continuing execution. It is also important to note that "End Test" is not displayed on the screen. The test method stops immediately when the panic is called.
It looks good, but there is one more question: I still want to show "End Test". The cool part of defer is that you can put an extra defer method in the method.
The above method can be modified as follows:
Start Teststart deferdefer error:mimic error:1start panic deferdefer panic:mimic panicmimic error:mimic error:1
Now two defer methods are placed at the beginning of the test method. The first defer is recover from the panic, and then the error is printed. A place to be aware that the go language executes in the opposite direction defined by the Defer method (FIFO).
Output after the run:
Start Teststart deferdefer error:mimic error:1start panic deferdefer panic:mimic panicmimic error:mimic error:1
The test method stops the execution of the test method itself as expected by calling panic . The defer method that handles the error is then executed first. Because the test method calls the Mimicerror method before panic, error can be printed out. After the recover method is called, the panic chain is interrupted.
There is still a problem with this piece of code. The main method simply does not know that panic has been processed. The main method only knows that an error has occurred. is the error that the Mimicerror method simulates. That's not going to work. I need the main method to know the error that caused the panic. This is a mistake that needs to be reported.
We need to assign the panic error message to the ERR variable in the defer method that handles panic. Now the output:
Start Teststart deferdefer error:mimic error:1start panic deferdefer panic:mimic panicmimic error:mimic panic
This time the main function can print out the error that caused the panic.
Although it seems to be perfect, this code is not easy to extend. There are two built-in defer methods that are cool but not practical. What I need is a single, method that can handle panic in addition to errors. Here is all the code after refining, called _catchpanic.
Package Mainimport ("FMT") func main () {if err: = Test (); Err! = nil {fmt. Printf ("Main Error:%v\n", Err)}}func catchpanic (err error, functionname string) {if r: = Recover (); r! = nil {fmt. Printf ("%s:panic defered:%v\n", functionname, R) If Err! = Nil {err = FMT. Errorf ("%v", R)}}else if err! = Nil {fmt. Printf ("%s:error:%v\n", functionname, Err)}}func Mimicerror (key string) ERROR {return FMT. Errorf ("Mimic Error:%s", key)}func test () (err error) {defer catchpanic (err, "test")FMT. Println ("Start Test") Err = Mimicerror ("1") fmt. Println ("End Test") Return err}
The new method Catchpanic both the error and the panic. In this paper, the external definition of the defer method body is used instead of the internal definition method body. Before we start testing, we need to make sure that we don't break the existing error handling. Output after running the code:
Start testend testmain error:mimic error:1
Now let's test the panic:
Func Test (err error) {Defer catchpanic (err, "test") fmt. Println ("Start Test") Err = Mimicerror ("1") Panic ("Mimic panic")//FMT. Println ("End Test") Return err}
Output results
Start testtest:panic defered:mimic panicmain error:mimic error:1
Well, we have another problem. The Main method prints the Err variable's information, not the contents of the panic. What is wrong with that thing?
func catchpanic (err error, functionname string) { if r: = Recover (); r! = Nil { FMT. Printf ("%s:panic defered:%v\n", functionname, R) if err! = Nil { C11>= FMT. Errorf ("%v", R) } }Elseif err ! = nil { FMT. Printf ("%s:error:%v\n", functionname, err) }}
Because defer calls an externally defined method. So there is no benefit of the inline method or closure. Modify the code to print out the Err address of the test method and _catchpanic this defer method.
func _catchpanic (err error, functionname string) { if r: = Recover (); r! = Nil { FMT. Printf ("%s:panic defered:%v\n", functionname, R) FMT. Println ("Err addr defer:", &Err) if Err! = Nil {= FMT . Errorf ("%v", R) } }Elseif err ! = Nil { fmt. Printf ("%s:error:%v\n", functionname, err) }}
After running you will see why the main method did not get the error that panic carried:
ERR addr: 0x20818c2200x20818c2b01
When the test method passes the ERR variable to catchpanic This defer method, it is passed as a reference to the value of the pass. In the go language, all parameters are passed by value. So catchpanic this defer method has his own copy of the ERR variable. Any modification of the copy of Err that is owned by Catchpanic is limited to the internal method.
To modify the problem of code that is caused by a pass-through value, you need to use a pass-through reference.
PackageMainImport ( "FMT") Func main () {ifERR: = Testfinal (); Err! =Nil {fmt. Printf ("Main Error:%v\n", Err)}} Func _catchpanic (err * Error, functionname string) { ifr: = Recover (); r! =Nil {fmt. Printf ("%s:panic defered:%v\n", functionname, R) fmt. Println ("ERR addr Defer:", &err)ifErr! =Nil {*err= Fmt. Errorf ("%v", R)} }Else ifErr! = Nil &&*err! = nil {fmt. Printf ("%s:error:%v\n", functionname, *err)}} Func testfinal () (err error) { defer _catchpanic ( &err, "testfinal" ) FMT. Printf ("Start test\n") Err= Minicerror ("1") Panic ("Mimic Panic")}func Minicerror (key string) error {returnFmt. Errorf ("Mimic Error:%s", key)}
After running the code output:
0x2081c4020Main error:mimic Panic
Now the main method prints out the panic error message.
If you want to capture the call stack, you need to refer to the "Runtime" package.
Use the above method to handle errors and various panic situations. In many cases, these situations only require logging or processing on the call stack. This also effectively reduces error and keeps the code clean. However, the experience I learned is that it is best to capture panic only in the above pattern. Log what is still left to the application level of logic processing it. If not, you may write the error two times to the log.
Hopefully this will help you learn the go language!
welcome Dabigatran to learn from each other and make progress together. QQ Group: 58099570 | To be kind, reprint please indicate the source!
Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.
Understanding defer, panic and recover