讓 Go 的錯誤處理更加強大
Go 所提供的默認的 errors
包有很多的不足。編寫多層架構應用程序並使用 API 公開功能的時候,相比於單純的 string
類型的值,更需要具有上下文信息的錯誤處理。意識到這個缺點後,我開始實現一個更強大,更優雅的 error 包。這是一個逐漸演化的過程,隨着時間推移,我需要在這個包中引入更多的功能。
在此,我們會探討我們如何使用一個 CustomError
數據類型爲應用中帶來更多的價值,並且使錯誤處理更強大。
首先需要明白的是,如果實現了 Error()
方法,Go 允許使用任何用戶定義的數據類型替代內置的 error
數據類型。
換言之,只要我們自定義的數據類型實現了返回一個 string
類型的 Error()
方法,我們就可以使用我們自己的數據類型代替 Go 默認提供的那個;任何應該返回 error
類型的函數都可以返回我們自定義的數據類型,並且運行如常。(譯者注:這裏所謂原生的 error “數據類型”,就是 error interface)
構建'CustomError' 類型
1、我們創建一個新的數據類型,該數據類型在應用程序中被解釋爲 error。我們將它命名爲 CustomError
,首先,它會包含一個默認的 error
類型。這個 error
字段能夠讓我們在 CustomError
初始化的時候使用堆棧的跟蹤信息對其進行註釋(更多相關細節請看這裏 [1])。記錄這些堆棧信息可以讓我們在諸如 NewRelic APM 之類的平臺上更容易地調試錯誤。
2、Go 內置的 error
類型將錯誤當做 string
類型的值。我一直認爲這種方式是不對的,至少這樣是不足的。一個錯誤應當有與之關聯的類型——比如錯誤是否由於 SQL DB 寫入 / 更新 / 獲取操作失敗而導致的?或者錯誤是否是由於請求中沒有提供足夠的數據導致的?
現在,更加深入地瞭解下 ErrorCode
類型的實際情況。
我們依據可能在應用中捕獲到的每個錯誤類型創建多個 ErrorCode
常量。這樣一個 error 的解釋就基於 ErrorCode
而不是字符串了。
獲知一個 error 的類型可以讓我們以不同的方式處理不同的錯誤,也可以基於 error 的不同類型採取業務層面的決策。不僅僅只是業務決策,快速瀏覽下 ErrorCode
甚至可以指出系統出故障的精確範圍。
舉例來說,我在系統中的主要 RPC 中使用了大量的 5xx 錯誤在不同時間段的計數 (5xx Count vs Time
)。通過瀏覽這個圖可以有助於定位系統錯誤的準確範圍。
3、出於提高可讀性的目標,我們同時也希望保留 error 的 string
解釋,這樣也能讓我們一眼就瞭解哪裏出了問題。
ErrorCode
告訴你一個錯誤的類型,但是不會告訴你它發生在代碼中的哪個位置。ErrorMsg
則服務於這個目的,並保持現有功能不變。
這裏可能有爭議,“爲什麼不使用在第 1 步中引入的 error
字段的錯誤信息?爲什麼要新加一個字段?”——這是因爲我們會使用 ErrorCode
和 ErrorMsg
的組合來作爲一個 error
(代碼請參考這裏 [2])。
但是僅有這兩個就足夠了嗎?如果我們想捕獲更多信息——比如導致一個 error 的上下文數據呢?
比方說,一個顧客打開一個查詢周圍餐廳的頁面,但是當他打開應用的時候,卻沒有給他展示任何東西。你也許需要捕獲更多上下文信息,比如 緯度
,經度
和其他一系列信息,方便你用來判斷是該區域真的沒有餐廳,還是一個維護性上的問題。
LoggingParams
正是解決辦法!
4、LoggingParams
允許你捕獲各種依賴於上下文的參數。
比如,我將這些在嚴重錯誤中產生的參數追加在告警信息中,看一下這些參數,有時就可以找出錯誤的根本原因。
如果沒有找到原因,它也有助於過濾出特定事件的錯誤日誌。
這是我們記錄 CustomError 時 Kibana 日誌看起來的樣子
5、最後,我們需要一些類似於 Go 中默認 error
類型申明 error != nil
之類的東西。
我們引入了一個名爲 exists
的 bool
字段,當 CustomError
被創建時,該字段會被初始化爲 true
。
6、對於小部分錯誤,我們希望向最終用戶展示特定信息。這些信息便於他們理解並做出反應。
我們同樣維護了一個 ErrorCode
到用戶界面友好(UI-friendly)的信息的映射。
使用:
看上面的例子,你會意識到每次 Acquire()
被調用,調用函數會期望返回 CustomError
的錯誤類型,與此同時,這會給我們帶來全面提升。
譯者注:站在 error 使用角度。上圖中的做法可能並不是很好。Acquire() 函數返回的不是 error 而是作者自定義的 CustomError 結構體,等同於要與這個 CustomError 耦合在一起。如果 Acquire() 函數仍返回 error,上層通過 errors.As 解析出 CustomError 可能會更好。另外,error 實際上是 interface。將一個 struct 類型或者 struct 的指針賦值給 interface 的變量,則必定不爲空。執行下面的語句,err 必定不等於 nil。
var err error err = Acquire() if err != nil { ...... }
結論
我們已經看到了 CustomError
可以被用來使得 error 在多層次應用中更有解釋力。您可以在這裏 [3] 查看具有正確接口定義和實現的完整代碼。
via: https://medium.com/codealchemist/error-handling-in-go-made-more-powerful-ce164c2384ee
作者:Abhinav Bhardwaj[4] 譯者:dust347[5] 校對:polaris1119[6]
本文由 GCTT[7] 原創編譯,Go 中文網 [8] 榮譽推出,發佈在 Go 語言中文網公衆號,轉載請聯繫我們授權。
參考資料
[1]
這裏: https://github.com/abhinav-codealchemist/custom-error-go/blob/ca21f0e42b4ed57b5390491fe25fcb16eec7cffa/CustomError.go#L23
[2]
這裏: https://github.com/abhinav-codealchemist/custom-error-go/blob/ca21f0e42b4ed57b5390491fe25fcb16eec7cffa/CustomError.go#L22
[3]
這裏: https://github.com/abhinav-codealchemist/custom-error-go
[4]
Abhinav Bhardwaj: https://codealchemist.medium.com/
[5]
dust347: https://github.com/dust347
[6]
polaris1119: https://github.com/polaris1119
[7]
GCTT: https://github.com/studygolang/GCTT
[8]
Go 中文網: https://studygolang.com/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/LTZXAPvTHRTUcNH_v7ZSIg