【Go專家編程】defer這裏有個坑

前言

項目中,有時爲了讓程序更健壯,也即不panic,咱們或許會使用recover()來接收異常並處理。git

好比如下代碼:github

func NoPanic() {
	if err := recover(); err != nil {
		fmt.Println("Recover success...")
	}
}

func Dived(n int) {
	defer NoPanic()

	fmt.Println(1/n)
}

func NoPanic() 會自動接收異常,並打印相關日誌,算是一個通用的異常處理函數。golang

業務處理函數中只要使用了defer NoPanic(),那麼就不會再有panic發生。數據庫

關因而否應該使用recover接收異常,以及什麼場景下使用等問題不在本節討論範圍內。 本節關注的是這種用法的一個變體,曾經出如今筆者經歷的一個真實項目,在該變體下,recover再也沒法接收異常。函數

recover使用誤區

在項目中,有衆多的數據庫更新操做,正常的更新操做須要提交,而失敗的就須要回滾,若是異常分支比較多, 就會有不少重複的回滾代碼,因此有人嘗試了一個作法:即在defer中判斷是否出現異常,有異常則回滾,不然提交。日誌

簡化代碼以下所示:code

func IsPanic() bool {
	if err := recover(); err != nil {
		fmt.Println("Recover success...")
		return true
	}

	return false
}

func UpdateTable() {
    // defer中決定提交仍是回滾
	defer func() {
		if IsPanic() {
			// Rollback transaction
		} else {
			// Commit transaction
		}
	}()

	// Database update operation...
}

func IsPanic() bool 用來接收異常,返回值用來講明是否發生了異常。func UpdateTable()函數中,使用defer來判斷最終應該提交仍是回滾。協程

上面代碼初步看起來還算合理,可是此處的IsPanic()不再會返回true,不是IsPanic()函數的問題,而是其調用的位置不對。get

recover 失效的條件

上面代碼IsPanic()失效了,其緣由是違反了recover的一個限制,致使recover()失效(永遠返回nil)。it

如下三個條件會讓recover()返回nil:

  1. panic時指定的參數爲nil;(通常panic語句如panic("xxx failed...")
  2. 當前協程沒有發生panic;
  3. recover沒有被defer方法直接調用;

前兩條都比較容易理解,上述例子正是匹配第3個條件。

本例中,recover() 調用棧爲「defer (匿名)函數」 --> IsPanic() --> recover()。也就是說,recover並無被defer方法直接調用。符合第3個條件,因此recover() 永遠返回nil。

贈人玫瑰手留餘香,若是以爲不錯請給個贊~

本篇文章已歸檔到GitHub項目,求星~ 點我即達

相關文章
相關標籤/搜索