Go項目分層下的最佳error處理方式分享
前言
在 Go 語言中,對于程序中可能出現(xiàn)的問題,比如數據庫連接失敗,文件讀取錯誤等,都是使用基于內置的 error 接口類型的值來表示和處理錯誤。而在分層的項目中,如何最佳處理 error成為眾多人關注的問題,本文將探討 Go 項目分層下的最佳 error 處理方式。準備好了嗎?準備一杯你最喜歡的飲料或茶,隨著本文一探究竟吧。
常見分層下的 error 處理
以典型的 MVC ( dao → service → controller/middleware) 分層結構舉例,常見的錯誤處理大致如下:
// controller / middleware
res, err := service.GetById(ctx, id)
if err != nil {
log.Errorf(ctx, "service.GetById failed, id=%s, error=%v", err)
······
}
······
// service
article, err := dao.GetById(ctx, id)
if err != nil {
log.Errorf(ctx, fmt.Errorf("dao.GetById failed, error=%v", err))
return fmt.Errorf("dao.GetById failed, error=%v", err)
}
······
// dao
······
if err != nil {
log.Errorf(ctx, fmt.Errorf("GetById failed, id=%s, error=%v", id, err))
return fmt.Errorf("GetById failed, id=%s, error=%v", id, err)
}
······以上錯誤處理的方式,在每一層都打印一條錯誤日志,然后對得到的 error 進行二次封裝。雖然以上處理方式可以使我們在查看日志時方便故障排查和問題定位,同時提供了錯誤的上下文信息,但也存在以下問題:
- 每層都打印日志,帶來了大量日志;
- 字符串拼接費時費力,缺乏統(tǒng)一規(guī)范,可能導致理解困難;
- 通過字符串拼接,獲得新
error,破壞了原始error,會導致等值判定失敗,難以獲取詳細的堆棧關聯(lián)。
分層下的最佳 error 處理方式
遵循以下建議,我們可以更好地處理 error :
- 1、一個
error,應該只被處理一次 - 2、讓
error包含更多的信息 - 3、原始
error,應保證完整性,不被破壞 - 4、
error需要被日志記錄
什么意思呢?為了確保 error 處理的有效性,對于某一層來說,應該保證每個錯誤只被處理一次,要么打印 error 信息,要么將其傳遞給上一層,而不是每一層都獨立打印 error 信息。
同時,在傳遞錯誤給上一層時,應該附帶有用的額外信息,并確保不破壞原始錯誤的完整性,以保證錯誤的可追溯性。最后,通過記錄錯誤日志可以幫助我們進行問題排查。

如圖所示:
在 Dao 層遇到原始錯誤 Original Error 后,我們可以將其與需要的額外信息封裝,組成一個新的 error ,然后傳遞給上一層,逐層附加信息,直至傳遞到 controller 層,最終得到一個全新的 error,其中包含 Original error 和每一層添加的額外信息。
通過最終得到的這個 error,我們可以像剝洋蔥一樣逐層解開,追溯到 Original error ,并獲取我們所需的信息。如果 controller 是最頂層,我們可以打印完整的錯誤信息,然后獲取 Original Error,并打印其所包含的 堆棧信息。
Wrap error
盡管前面已經探討了分層下的最佳 error 處理方式,但我們會發(fā)現(xiàn)官方標準庫errors 所提供的函數并不能滿足我們的需求,我們不能借助現(xiàn)有函數對原始錯誤附加額外信息且不破壞其完整性。在這種情況下,我們可以借助第三方庫 github.com/pkg/errors 來完成我們的需求。
github.com/pkg/errors 提供了很多實用的函數,例如:
Wrap(err error, message string) error:該函數基于原始錯誤err,返回一個帶有堆棧跟蹤信息和附加信息message的新errorWrapf(err error, format string, args ...interface{}) error: 和上面的函數功能是一樣的,只不過可以對附加信息進行格式化封裝WithMessage(err error, message string) error:該函數基于原始錯誤err,返回一個附加信息message的新errorWithMessagef(err error, format string, args ...interface{}) error: 和上面的函數功能是一樣的,只不過可以對附加信息進行格式化封裝Cause(err error) error:該函數用于提取err中的原始error,它會遞歸地檢查error,直到找到最底層的原始error,如果存在的話
了解了以上函數的功能,我們來看看項目分層下最佳 error 的具體實現(xiàn)。
// controller / middleware
res, err := service.GetById(ctx, id)
if err != nil {
log.Errorf(ctx, "service.GetById failed, original error: %T %v", errors.Cause(err), errors.Cause(err))
log.Errorf(ctx, "stack trace: \n%+v\n", err)
······
}
······
// service
article, err := dao.GetById(ctx, id)
if err != nil {
return errors.WithMessage(err, "dao.GetById failed")
}
······
// dao
······
if err != nil {
return errors.Wrapf(err, "GetById failed, id=%s, error=%v", id, err)
}
······當在 Dao 層遇到原始錯誤 Original Error 后,使用 errors.Wrap() 對錯誤進行封裝。這個封裝操作可以在保留根因(Origin error)的同時,提供堆棧信息,并添加額外的上下文信息,然后將封裝后的錯誤傳遞給上一層處理。
當 service 層接收到 error 之后,使用 errors.WithMessage() 函數,將額外的信息附加到錯誤上,并繼續(xù)將錯誤向上層傳遞,直至到達 controller 層。在 controller 層,我們可以打印出根因的類型、信息以及堆棧信息,以便更好地進行問題排查。

小結
本文對 Go 項目分層下的最佳 error 處理方式進行介紹,并通過使用 github.com/pkg/errors 庫中的一些實用函數來提供實現(xiàn)示例。
盡管本文基于 MVC 分層結構進行介紹,但實際上大多數項目的分層結構可能各不相同,因此在確定錯誤處理方式和策略時需要考慮具體情況。然而,我相信通過參考本文提出的四點建議和實現(xiàn)示例或其他更好的建議,一定能夠確定最佳的錯誤處理方式。
到此這篇關于Go項目分層下的最佳error處理方式分享的文章就介紹到這了,更多相關Go error處理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Go源碼字符串規(guī)范檢查lint工具strchecker使用詳解
這篇文章主要為大家介紹了Go源碼字符串規(guī)范檢查lint工具strchecker使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06

