【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
:
- panic時指定的引數為
nil
;(一般panic語句如panic("xxx failed...")
- 當前協程沒有發生panic;
- recover沒有被defer方法直接呼叫;
前兩條都比較容易理解,上述例子正是匹配第3個條件。
本例中,recover() 呼叫棧為“defer (匿名)函式” --> IsPanic() --> recover()。也就是說,recover並沒有被defer方法直接呼叫。符合第3個條件,所以recover() 永遠返回nil。
贈人玫瑰手留餘香,如果覺得不錯請給個贊~
本篇文章已歸檔到GitHub專案,求星~ 點我即