GO_05_2:Golang 中 panic、recover、defer 的用法
函數 defer
1. 它的執行方式類似其他語言中的折構函數,在函數體執行結束後按照調用順序的 相反順序 逐個執行
2. 即使函數發生 嚴重錯誤 也會被執行,類似於 java 中 try{...} catch(){} finally{} 結構的 finally
3. 支持匿名函數的調用
4. 常用於資源清理、文件關閉、解鎖以及記錄時間等善後操作
5. 通過與匿名函數配合可在 return 之後修改函數計算結果
6. 如果函數體內某個變量作為 defer 時匿名函數的參數,則在定義 defer 時即已經獲得了拷貝,否則則是引用某個變量的地址
7. 需要註意,Go 沒有異常機制,但有 panic/recover 模式來處理錯誤
8. panic 可以在任何地方引發,但 recover 只有在 defer 調用的函數中有效
首先我們來驗證一下 defer函數的執行順序
package main import "fmt" func main() { fmt.Println("a") defer fmt.Println("b") defer fmt.Println("c") defer fmt.Println("d") }
a d c b
我們從結果就可以看出來 defer函數 執行順序為倒著來的,即和棧相似,先進後出的順序。
panic/recover 函數
Golang 有2個內置的函數 panic() 和 recover(),用以報告和捕獲運行時發生的程序錯誤,與 error 不同,panic-recover 一般用在函數內部。一定要註意不要濫用 panic-recover,可能會導致性能問題,我一般只在未知輸入和不可靠請求時使用。
golang 的錯誤處理流程:當一個函數在執行過程中出現了異常或遇到 panic(),正常語句就會立即終止,然後執行 defer 語句,再報告異常信息,最後退出 goroutine。如果在 defer 中使用了 recover() 函數,則會捕獲錯誤信息,使該錯誤信息終止報告。
package main import ( "log" "strconv" ) //捕獲因未知輸入導致的程序異常 func catch(nums ...int) int { defer func() { if r := recover(); r != nil { log.Println("[E]", r) } }() return nums[1] * nums[2] * nums[3] //index out of range } //主動拋出 panic,不推薦使用,可能會導致性能問題 func toFloat64(num string) (float64, error) { defer func() { if r := recover(); r != nil { log.Println("[W]", r) } }() if num == "" { panic("param is null") //主動拋出 panic } return strconv.ParseFloat(num, 10) } func main() { catch(2, 8) toFloat64("") }
2017/03/24 13:07:49 [E] runtime error: index out of range 2017/03/24 13:07:49 [W] param is null
Go語言追求簡潔優雅,所以,Go語言不支持傳統的 try…catch…finally 這種異常,因為Go語言的設計者們認為,將異常與控制結構混在一起會很容易使得代碼變得混亂。因為開發者很容易濫用異常,甚至一個小小的錯誤都拋出一個異常。在Go語言中,使用多值返回來返回錯誤。不要用異常代替錯誤,更不要用來控制流程。在極個別的情況下,也就是說,遇到真正的異常的情況下(比如除數為0了)。才使用Go中引入的Exception處理:defer, panic, recover。這幾個異常的使用場景可以這麽簡單描述:Go中可以拋出一個panic的異常,然後在defer中通過recover捕獲這個異常,然後正常處理。
package main import "fmt" func main(){ defer func(){ // 必須要先聲明defer,否則不能捕獲到panic異常 fmt.Println("c") if err:=recover();err!=nil{ fmt.Println(err) // 這裏的err其實就是panic傳入的內容,55 } fmt.Println("d") }() f() } func f(){ fmt.Println("a") panic(55) fmt.Println("b") fmt.Println("f") }
結果打印如下:
a c 55 d exit code 0, process exited normally.
用Go實現類似 try catch 的異常處理的例子如下:
package main //實現 try catch 例子 func Try(fun func(), handler func(interface{})) { defer func() { if err := recover(); err != nil { handler(err) } }() fun() } func main() { Try(func() { panic("foo") }, func(e interface{}) { print(e) }) }
GO_05_2:Golang 中 panic、recover、defer 的用法