Go聖經-學習筆記之defer和異常處理

上一篇 Go聖經-學習筆記之函數值(二)golang

下一篇 Go聖經-學習筆記之方法數組

可變參數

形參數量可變的函數稱爲可變參數函數。使用最多的可變參數函數標準庫:fmt網絡

在聲明可變參數函數時,須要在參數列表的最後一個參數類型以前加上省略符號...函數

func 函數名(t Type1, t Type2, tn ...TypeN) 返回參數列表 {}

// DEMO 
func sum(x, y ...int) int {
    count:=x
    for _, elem:= range y {
        count+=elem
    }
    return count
}

fmt.Println(sum(1,2,3,4,5,6,7,8)) // 使用方法1

elems:=[]int{2,3,4,5,6,7,8}
fmt.Println(sum(1, elems...) // 使用方法2

defer函數

defer表示函數退出時,棧內的函數列表退棧執行。這個主要介紹defer和return之間的關係源碼分析

func Incr() (i int) {
    defer func(){
       i++ 
    }()
    return 1
}

fmt.Println(Incr()) // 打印值i = 1 ?

實際上defer和return的執行順序是沒有什麼問題的。先執行defer,而後再return。重點是 return xx。前面章節說過,它不是一個原子操做。分解動做爲:學習

func Incr() (i int) {
    defer func() {
        i++
    }()
    i=1
    return
}

第二個是我在寫Go服務端時的一個小經驗,首先說一下背景,咱們通常想作日誌追蹤或者請求鏈的調用追蹤,看看程序執行到哪一個步驟而後出錯了。這時,咱們通常會這樣作:.net

func AddSaleOrder(so *SaleOrder, o *orm.Ormer) (retCode int, err error) {
    ......
    Logger.Info("[%d] enter AddSaleOrder.", so.SaleOrderId)
    defer Logger.Info("[%d] left AddSaleOrder.", so.SaleOrderId)
    ......
    return
}

可是爲了懶不想寫兩句,或者節約代碼長度什麼的。而後就採用了寫出了下面這種方式:指針

func bigSlowOp() {
    defer TraceLog("bigSlowOp")()
    fmt.Println("hello,world")
    time.Sleep(3 * time.Second)
    return
}

func TraceLog(methodName string, params ...interface{}) func() {
    start := time.Now()
    fmt.Printf("[%v] enter %s\n", params, methodName)
    return func() {
        fmt.Printf("[time: %s |  %v] left %s\n", time.Since(start), params, methodName)
    }
}

func main() {
    bigSlowOp()
    return
}
// 輸出結果
/*
[[]] enter bigSlowOp
hello,world
[time: 3.000241212s |  []] left bigSlowOp
*/

那麼,咱們之後只須要寫defer TraceLog(方法名,參數列表)()就OK了日誌

defer語句常常用於處理成對的操做,如打開、關閉、鏈接、斷開鏈接、加鎖、釋放鎖等,不管函數是正常執行完成,仍是提早退出或者panic,defer堆棧裏的函數列表都會執行。那麼資源也能夠釋放。code

同時,咱們也要注意引入defer,若是不注意,可能會帶來資源泄露問題:

for _, filename:= range filenames{
    f, err := os.Open(filename)
    if err !=nil{
        return
    }
    defer f.Close() // 由於f的生命週期一直在for循環內,因此defer在退出以前不會執行。若是文件列表過大,就是形成文件描述符大量泄露,若是是網絡,則會出現"connection peer by peer"的相關錯誤。
}

// 解決方案:使f變量的生命週期縮短,和for循環的執行週期分離開
func doFile(filename string) error {
    f, err := os.Open(filename)
    if err !=nil{
        return err
    }
    defer f.Close()
    return nil
}

for _, filename:= range filenames {
    err := doFile(filename)
}

一旦有生命週期的障礙,首先想到函數體執行的生命週期。

Panic異常

通常而言程序在運行過程當中,發生了數組越界,空指針引用錯誤等,這些錯誤會引發panic異常。當發生panic時,程序會中斷運行,並當即在Goroutine中被延遲的函數。隨後並輸出日誌信息,包括髮生錯誤值,以及上下文調用棧。

可是並非全部的panic都來自運行時,開發者也能夠直接調用panic函數引起panic異常;panic接收任何參數。爲了方便診斷問題,標準庫runtime容許開發者輸出堆棧信息。例如:

func main(){
    defer printStack()
    f(3)
}

func printStack() {
    var buf [4096]byte
    runtime.Stack(buf[:], false)
    os.Stdout.Write(buf[:n])
}

Recover捕獲異常

一般來講,不該該對panic異常作任何處理。讓異常儘可能早暴露早修復,提升軟件的可用性。但有時也須要從異常恢復,至少讓程序崩潰前作一些操做,好比: 若是網絡服務端掛掉,若是服務端不作任何處理,直接panic,則客戶端遲遲得不到服務端的響應,影響用戶體驗。

若是在defer內調用了內置函數recover,而且定義該defer的函數發生了panic,recover會使程序從panic恢復,並返回panic value。致使panic異常的函數不會再運行,但正常返回。在未發生panic時,調用recover會返回nil。

這裏要說明一下,至於panic被哪裏的recover捕獲,或者不會捕獲,能夠參考這篇文章: panic和recover工做原理

後續我會經過雨痕老師的Go源碼分析書籍,深刻分析panic和recover,這樣更容易深刻地理解。

相關文章
相關標籤/搜索