1. 程式人生 > >【Go專家程式設計】defer這裡有個坑

【Go專家程式設計】defer這裡有個坑

前言

專案中,有時為了讓程式更健壯,也即不panic,我們或許會使用recover()來接收異常並處理。

比如以下程式碼:

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

func Dived(n int) {
	defer NoPanic()

	fmt.Println(1/n)
}

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

業務處理函式中只要使用了defer NoPanic(),那麼就不會再有panic

發生。

關於是否應該使用recover接收異常,以及什麼場景下使用等問題不在本節討論範圍內。 本節關注的是這種用法的一個變體,曾經出現在筆者經歷的一個真實專案,在該變體下,recover再也無法接收異常。

recover使用誤區

在專案中,有眾多的資料庫更新操作,正常的更新操作需要提交,而失敗的就需要回滾,如果異常分支比較多, 就會有很多重複的回滾程式碼,所以有人嘗試了一個做法:即在defer中判斷是否出現異常,有異常則回滾,否則提交。

簡化程式碼如下所示:

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()函式的問題,而是其呼叫的位置不對。

recover 失效的條件

上面程式碼IsPanic()失效了,其原因是違反了recover的一個限制,導致recover()失效(永遠返回nil)。

以下三個條件會讓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專案,求星~ 點我即