Go項(xiàng)目分層下的最佳error處理方式分享
前言
在 Go
語言中,對(duì)于程序中可能出現(xiàn)的問題,比如數(shù)據(jù)庫連接失敗,文件讀取錯(cuò)誤等,都是使用基于內(nèi)置的 error
接口類型的值來表示和處理錯(cuò)誤。而在分層的項(xiàng)目中,如何最佳處理 error
成為眾多人關(guān)注的問題,本文將探討 Go
項(xiàng)目分層下的最佳 error
處理方式。準(zhǔn)備好了嗎?準(zhǔn)備一杯你最喜歡的飲料或茶,隨著本文一探究竟吧。
常見分層下的 error 處理
以典型的 MVC
( dao
→ service
→ controller/middleware
) 分層結(jié)構(gòu)舉例,常見的錯(cuò)誤處理大致如下:
// 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) } ······
以上錯(cuò)誤處理的方式,在每一層都打印一條錯(cuò)誤日志,然后對(duì)得到的 error
進(jìn)行二次封裝。雖然以上處理方式可以使我們?cè)诓榭慈罩緯r(shí)方便故障排查和問題定位,同時(shí)提供了錯(cuò)誤的上下文信息,但也存在以下問題:
- 每層都打印日志,帶來了大量日志;
- 字符串拼接費(fèi)時(shí)費(fèi)力,缺乏統(tǒng)一規(guī)范,可能導(dǎo)致理解困難;
- 通過字符串拼接,獲得新
error
,破壞了原始error
,會(huì)導(dǎo)致等值判定失敗,難以獲取詳細(xì)的堆棧關(guān)聯(lián)。
分層下的最佳 error 處理方式
遵循以下建議,我們可以更好地處理 error
:
- 1、一個(gè)
error
,應(yīng)該只被處理一次 - 2、讓
error
包含更多的信息 - 3、原始
error
,應(yīng)保證完整性,不被破壞 - 4、
error
需要被日志記錄
什么意思呢?為了確保 error
處理的有效性,對(duì)于某一層來說,應(yīng)該保證每個(gè)錯(cuò)誤只被處理一次,要么打印 error
信息,要么將其傳遞給上一層,而不是每一層都獨(dú)立打印 error
信息。
同時(shí),在傳遞錯(cuò)誤給上一層時(shí),應(yīng)該附帶有用的額外信息,并確保不破壞原始錯(cuò)誤的完整性,以保證錯(cuò)誤的可追溯性。最后,通過記錄錯(cuò)誤日志可以幫助我們進(jìn)行問題排查。
如圖所示:
在 Dao
層遇到原始錯(cuò)誤 Original Error
后,我們可以將其與需要的額外信息封裝,組成一個(gè)新的 error
,然后傳遞給上一層,逐層附加信息,直至傳遞到 controller
層,最終得到一個(gè)全新的 error
,其中包含 Original error
和每一層添加的額外信息。
通過最終得到的這個(gè) error
,我們可以像剝洋蔥一樣逐層解開,追溯到 Original error
,并獲取我們所需的信息。如果 controller
是最頂層,我們可以打印完整的錯(cuò)誤信息,然后獲取 Original Error
,并打印其所包含的 堆棧信息。
Wrap error
盡管前面已經(jīng)探討了分層下的最佳 error
處理方式,但我們會(huì)發(fā)現(xiàn)官方標(biāo)準(zhǔn)庫errors
所提供的函數(shù)并不能滿足我們的需求,我們不能借助現(xiàn)有函數(shù)對(duì)原始錯(cuò)誤附加額外信息且不破壞其完整性。在這種情況下,我們可以借助第三方庫 github.com/pkg/errors
來完成我們的需求。
github.com/pkg/errors
提供了很多實(shí)用的函數(shù),例如:
Wrap(err error, message string) error
:該函數(shù)基于原始錯(cuò)誤err
,返回一個(gè)帶有堆棧跟蹤信息和附加信息message
的新error
Wrapf(err error, format string, args ...interface{}) error
: 和上面的函數(shù)功能是一樣的,只不過可以對(duì)附加信息進(jìn)行格式化封裝WithMessage(err error, message string) error
:該函數(shù)基于原始錯(cuò)誤err
,返回一個(gè)附加信息message
的新error
WithMessagef(err error, format string, args ...interface{}) error
: 和上面的函數(shù)功能是一樣的,只不過可以對(duì)附加信息進(jìn)行格式化封裝Cause(err error) error
:該函數(shù)用于提取err
中的原始error
,它會(huì)遞歸地檢查error
,直到找到最底層的原始error
,如果存在的話
了解了以上函數(shù)的功能,我們來看看項(xiàng)目分層下最佳 error
的具體實(shí)現(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) } ······
當(dāng)在 Dao
層遇到原始錯(cuò)誤 Original Error
后,使用 errors.Wrap()
對(duì)錯(cuò)誤進(jìn)行封裝。這個(gè)封裝操作可以在保留根因(Origin error
)的同時(shí),提供堆棧信息,并添加額外的上下文信息,然后將封裝后的錯(cuò)誤傳遞給上一層處理。
當(dāng) service
層接收到 error
之后,使用 errors.WithMessage()
函數(shù),將額外的信息附加到錯(cuò)誤上,并繼續(xù)將錯(cuò)誤向上層傳遞,直至到達(dá) controller 層。在 controller
層,我們可以打印出根因的類型、信息以及堆棧信息,以便更好地進(jìn)行問題排查。
小結(jié)
本文對(duì) Go
項(xiàng)目分層下的最佳 error
處理方式進(jìn)行介紹,并通過使用 github.com/pkg/errors
庫中的一些實(shí)用函數(shù)來提供實(shí)現(xiàn)示例。
盡管本文基于 MVC
分層結(jié)構(gòu)進(jìn)行介紹,但實(shí)際上大多數(shù)項(xiàng)目的分層結(jié)構(gòu)可能各不相同,因此在確定錯(cuò)誤處理方式和策略時(shí)需要考慮具體情況。然而,我相信通過參考本文提出的四點(diǎn)建議和實(shí)現(xiàn)示例或其他更好的建議,一定能夠確定最佳的錯(cuò)誤處理方式。
到此這篇關(guān)于Go項(xiàng)目分層下的最佳error處理方式分享的文章就介紹到這了,更多相關(guān)Go error處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Lumberjack+zap進(jìn)行日志切割歸檔操作
這篇文章主要介紹了使用Lumberjack+zap進(jìn)行日志切割歸檔操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12Go源碼字符串規(guī)范檢查lint工具strchecker使用詳解
這篇文章主要為大家介紹了Go源碼字符串規(guī)范檢查lint工具strchecker使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06golang開發(fā)微框架Gin的安裝測(cè)試及簡(jiǎn)介
這篇文章主要為大家介紹了golang微框架Gin的安裝測(cè)試及簡(jiǎn)介,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2021-11-11