深入 Go 的錯誤處理機制,理解設計思想

a_wei發表於2019-10-14

如果有幫助,求關注部落格,求關注最下方微信公眾號,感激不盡。

上一篇文章分享了Go的異常,錯誤處理使用,未讀過的可以點選回顧一下,我們知道程式執行中,有異常,有錯誤,那麼什麼是異常,什麼是錯誤,和其他語言相比,Go的異常錯誤機制有什麼優點,缺點?我們如何更好的理解,如何用Go寫出更健壯的程式,今天來聊一聊這些問題。

異常和錯誤

關於異常和錯誤每個人都有自己的理解,很多人往往把這個混為一談,認為他們是等價的,這裡我們從Java 和Go兩種語言異常錯誤體系的設計分析來試圖回答這個問題。

Java中,Throwable是所有錯誤(Error)和異常 (Exception) 的基類,整的來說,它們都是程式執行過程中可能出現的問題,區別在哪裡呢? Exception是可預料的,而Error是不可預料的,舉個例子,工程隊要蓋一棟樓,我們把蓋這棟樓的過程比作程式一段執行的過程,在蓋樓的過程中,建築人員對於對於瓷磚損壞,下雨,斷電,設計錯誤等都是在意料之內的,並且在發生這些問題時是由恢復,補救措施的,而對於地震,地面塌陷,狂風這些問題是不可預知,意料之外的,並且這些問題真的發生了,也是無法補救,恢復的,只能重新來過。Exception我理解為在程式執行中正常情況下意料之中發生的事,是可以被程式設計師處理,補救,有機會回到正常處理流程的,而Error在程式執行中非正常成礦下發生後是無法被處理,恢復,比如記憶體溢位,棧溢位等。

Go的異常錯誤設計體系只有Error,任何一切都在方法返回值中返回可能發生的錯誤,那麼go有沒有執行過程中意料之外的錯誤呢,答案是有呢,panic和defer以及recover共同組成了這個體系,但這個體系最終還是被返回Error所處理,什麼含義呢,就是在意料之外的panic發生時,在defer中透過recover捕獲這個恐慌,轉化為錯誤透過方法返回值告訴方法呼叫者,看到這裡,其實從字面意思,Go中弱化了異常,一切皆錯誤,都被包裝成類似Code,Message的形式返回方法呼叫者,直到有呼叫者去處理, 這也是Go的設計精髓,簡化沒必要存在的。

從編碼上看看Go和Java的異常設計思想

如果你看過Go的許多程式碼,那麼 if err != ni, 應該隨處可見,尤其是業務程式碼開發中,是的,Go沒有類似 try catch 這樣的語句,Go對錯誤的價值觀是可程式設計,我們看下面的程式碼:


result1,err := func1()
if err != nil{
    return nil,,err
}
result2,err := func2()
if err != nil{
    return nil,err
}
result3,err := func3()
if err != nil{
    return nil,err
}
return result1+result2+result3,nil


try{
    result1 = method1();
    reuslt2 = method2();
    result3 = method3();
    return result1+result2+result3;
}cache(Exception e){
    log.error(e);
    return null;
}

上面的程式碼我想了解Java和Go的人都知道,我們從多個方面來看看上面的程式碼,第一:從順序角度來看,是否Go更能被理解,每一個方法都有一個結果值和一個可能發生的錯誤值,而Java需要更多的語法,意味著需要更多理解,思考;第二:從對異常錯誤處理角度來看,Go中程式設計師對err有更多的操作空間,有更多的可程式設計性,而Java中相對可程式設計性弱化了許多;第三:也是最直觀的,程式碼量我們發現Go的程式碼量比Java多了將近一半,而這種程式碼並沒有什麼技術量,重複的程式碼到處都是,是的,這也是許多開發者對Go不滿意的地方,但這種是可以透過開發者程式碼設計去規避的,這裡暫且不討論


Java中透過throw new 丟擲一個異常,透過 try cache捕獲,而Go中透過panic丟擲一個恐慌,透過defer和recover來處理,我們來看看程式碼,在分析


func test() (err error){
    defer func(){
        if e:=recover(); e != nil{
             err = e.(error)
        }
    }()    
    panic("發生恐慌了")
    return err
}


publis Result test(){
    try{
        throw new RuntimeException("發生異常了")
    }cache(Exception e){
        //處理錯誤
        1 列印錯誤,忽略
        2 throw e,繼續丟擲
        //轉化為code,message
        3 new Result(code,e.message) 
    }
}

我們分析上面兩段程式碼,當異常或者恐慌發生時,我們可以看到Go中在defer裡對透過recover捕獲panic,將其轉化為一個錯誤,透過返回值的形式返回,而在Java中異常發生時,捕獲以後處理方式為要麼列印,要麼throw出去拋給上層的方法呼叫者,站在方法全域性來看,當你是一個呼叫者時,你期望的是什麼?如果你有介面互動的開發經驗,我想你不會給呼叫介面的人丟擲一個exception 或者 panic,他會不高興的,同樣你也不希望介面返回的是一堆堆疊資訊,那麼 在上面Java的cache中最終是返回一個Result,包含code,message,這樣去看,我們對Java異常包裝後是否和Go的錯誤設計有異曲同工之妙,同樣的Go透過panic和defer,recover也可以為try,cache, throw這樣的處理,但語言層面的設計的本質是不一樣的(記住哦),相互的轉化只是人為的在包裝而已,請不要偏離正軌。

聊聊exception和panic給Java和Go帶來了什麼

不管是Go還是Java,我們知道當程式啟動後,對作業系統而言都是一個程式,Java中,在一個程式中可以啟動多個執行緒,執行緒是Java的最小單位,Go中,一個程式中依然會有多個執行緒,但這不是最小單位,協程是Go的最小單位。

Exception在Java中的作用域是當前執行緒,也就是說當Exception中發生時,只會影響到當前執行緒的執行,終止的是當前執行緒。

Panic在Go中的作用域是整個程式,當Panic發生時,如果當前協程沒捕獲,則整個Go的程式就會終止,這是非常可怕的。

所以需要開發人員在go的錯誤處理時需要謹慎,需要手工處理所有的error,尤其在對panic可能發生的地方需要捕獲,這稍微增加了開發人員的心智負擔。

同樣的,我們能看到Go的程式需要更多的嚴謹性,健壯性,所以在開發階段,快速試錯,讓儘可能多的錯誤立刻出現,然後修復。

提醒的是,尤其是在設計一些底層框架,方法時,一定需要對panic處理,轉化為err返回,否則框架丟擲的panic會導致整個Go的程式結束。

總結

在我看來,並沒有絕對,Java中對異常和錯誤有一個比較清晰的邊界,透過類繼承體系進行隔離,錯誤並不在程式設計師的考慮範圍之內,透過異常體系和控制流程來實現業務邏輯,往往也容易被濫用;而Go中並沒有,且弱化了異常的概念,並提供了將異常轉化為錯誤的方法。一切皆錯誤,擁有更好的可程式設計性,但同時也帶來諸如 if err != nil的這樣的程式碼到處都是,不同的程式語言對異常錯誤體系設計不一樣,也代表不同開發者的思想,沒有對與錯,個人認為都能解決特定的問題,同時也會帶來一定的困擾,一定要理解這種異常錯誤體系設計在當前程式語言中的設計思想,才能更好的使用,寫出更優雅的程式碼。

Go認為

  1. 讓程式設計師更直接的接觸錯誤,從而處理
  2. 錯誤是一種可程式設計的值
  3. 強調的是,無論何時,檢查錯誤都是至關重要的,而不是如何避免檢查錯誤

事實上關於Go的錯誤處理也有一些最佳實踐,有自己的思想,後續我也會整理,就不再這邊文章中釋出了,大家可以看看下面連結的文章

https://blog.golang.org/errors-are-values
https://blog.golang.org/error-handling-and...
https://blog.golang.org/defer-panic-and-re...

【GoLang那點事】你眼中的異常和錯誤有什麼區別?如何理解Go的異常和錯誤?

本作品採用《CC 協議》,轉載必須註明作者和本文連結
那小子阿偉

相關文章