go panic與recover分析及錯誤處理
先介紹一下什麼叫error 型別
error 是一種型別,表示錯誤狀態的型別,如果沒有錯誤則是nil。直白點將:error 型別就是描述錯誤的一種型別。
panic 在golang goroutine 的作用
panic 官方文件介紹:
panic 是用來停止當前程式的執行。當一個方法呼叫panic。 當函式F呼叫panic時,F的正常執行立即停止。 但是任何有F推遲的函式都會執行,意思是F定義有defer關鍵字宣告的函式會執行,然後F返回給它的呼叫者。 對於呼叫者G來說,F的呼叫就像呼叫panic 一樣,終止G的執行並執行任何延遲(帶有defer 關鍵字)的函式。 這種情況會持續下去,直到正在執行的goroutine中的所有功能都以相反的順序停止。 此時,程式終止並報告錯誤情況,包括panic的引數值。最後這種情況可以通過呼叫recover 來恢復函式的執行。
函式 recover 介紹
recover內建函式允許一段程式管理一個正在paincing goroutine的行為。
在defer 定義的函式(不是由它呼叫的任何函式)內部執行一段recover 函式,通過recover函式執行來停止panic 函式的執行,並且可以找出給panic所傳遞的錯誤值。 如果在defer 函式之外呼叫恢復,它不會停止panic的執行。 在這種情況下,或者當goroutine沒有panicing時,或者提供給panicing的引數為零時,恢復返回nil。 因此,recover函式的返回值報告協程是否正在遭遇panicing 。
panic函式就是往外扔錯誤,一層接一層往上扔直到當前程式不能執行為止,不想讓panic 函式扔的錯誤導致程式掛掉,就得使用recover 函式來接收panic 錯誤或者說是阻擋panicing ,並且recover 函式可以將錯誤轉化為error 型別。因為panic 錯誤不會讓defer 關鍵字定義的函式也停止執行,就是說defer 關鍵字宣告的函式或者程式碼即使遇到錯誤也會執行。
一個函式裡面有defer 關鍵字宣告一個函式(假設叫catch 函式)和要執行出錯的程式碼,在catch 函式裡面呼叫recover 函式。recover 會攔截錯誤,不會讓錯誤往上扔,返回給呼叫者error(裡面有錯誤的資訊)型別 ,從而使goroutine 不掛掉。
上程式碼:
package main
import (
"fmt"
"errors"
)
func main() {
testError()
afterErrorfunc()
}
func testError() {
//defer catch()
panic(" \"panic 錯誤\"")
fmt.Println("丟擲一個錯誤後繼續執行程式碼")
}
func catch() {
if r := recover(); r != nil {
fmt.Println("testError() 遇到錯誤:", r)
var err error
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("")
}
if err != nil {
fmt.Println("recover後的錯誤:",err)
}
}
}
func afterErrorfunc(){
fmt.Println("遇到錯誤之後 func ")
}
執行結果:
panic: "panic 錯誤"
goroutine 1 [running]:
main.testError()
E:/goCode/src/MyTestGo/src/com.dylan.main/panic/testpanic.go:16 +0x40
main.main()
E:/goCode/src/MyTestGo/src/com.dylan.main/panic/testpanic.go:10 +0x27
Process finished with exit code 2
當panic 函式執行的時候導致後面函式 afterErrorfunc() 不能執行,main函式也丟擲一個錯誤,整個程式異常退出。
通過defer 關鍵字呼叫 catch函式。
func testError() {
defer catch()
panic(" \"panic 錯誤\"")
fmt.Println("丟擲一個錯誤後繼續執行程式碼")
}
程式執行結果:
testError() 遇到錯誤: "panic 錯誤"
recover後的錯誤: "panic 錯誤"
遇到錯誤之後 func
Process finished with exit code 0
分析:程式正常結束,沒有因為panic(錯誤)而到導致程式終止掛掉。錯誤被recover 函式接收,轉化為error型別的錯誤,最後輸出“ recover後的錯誤: “panic 錯誤” ” 而且後面 afterErrorfunc()執行。
一般在寫的時候這麼寫 不用定義catch 函式:
func main() {
testError()
afterErrorfunc()
}
func testError() {
defer func() {
if r := recover(); r != nil {
fmt.Println("testError() 遇到錯誤:", r)
}
}()
panic(" \"panic 錯誤\"")
fmt.Println("丟擲一個錯誤後繼續執行程式碼")
}
func catch(err error) {
if r := recover(); r != nil {
fmt.Println("testError() 遇到錯誤:", r)
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("")
}
}
}
func afterErrorfunc() {
fmt.Println("遇到錯誤之後 func ")
}
在發生panic 函式裡面加入下述程式碼就可以攔截panicing, 並且不讓程式掛掉和顯示錯誤資訊。
defer func() {
if r := recover(); r != nil {
fmt.Println("testError() 遇到錯誤:", r)
}
}()
最後如果想將錯誤資訊返回給呼叫者可以這麼做:
func main() {
err := testError()
if err != nil {
fmt.Println("main 函式得到錯誤型別:", err)
}
afterErrorfunc()
}
func testError() (err error) {
defer func() {
if r := recover(); r != nil {
fmt.Println("testError() 遇到錯誤:", r)
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("")
}
}
}()
panic(" \"panic 錯誤\"")
fmt.Println("丟擲一個錯誤後繼續執行程式碼")
return nil
}
func catch(err error) {
if r := recover(); r != nil {
fmt.Println("testError() 遇到錯誤:", r)
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("")
}
}
}
func afterErrorfunc() {
fmt.Println("遇到錯誤之後 func ")
}
提前宣告一個error 型別的變數。把錯誤資訊傳遞給error變數,再把error變數返回給呼叫者。