1. 程式人生 > >GO_05_2:Golang 中 panic、recover、defer 的用法

GO_05_2:Golang 中 panic、recover、defer 的用法

log logs lan finall 可能 錯誤處理 錯誤 異常處理 auto

函數 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 的用法