Go語(yǔ)言中錯(cuò)誤處理的方式總結(jié)
Go 的 error 有兩個(gè)很重要的特性:
- error 就是一個(gè)普通的值,處理起來(lái)沒(méi)有額外的開(kāi)銷(xiāo)
- error 的擴(kuò)展性很不錯(cuò),可以按照不同的場(chǎng)景來(lái)自定義錯(cuò)誤
在 Go1.13 之后,在 errors 包中提供了一些函數(shù),讓錯(cuò)誤的處理和追蹤更加方便一些。
這篇文章會(huì)結(jié)合 errors 中的函數(shù),來(lái)討論一下 Go 中常見(jiàn)的 error 使用方式。
這里說(shuō)的 errors 包是指 Go 中的原生 errors 包。
1、原生 error
在 Go 的錯(cuò)誤處理中,下面的代碼占絕大多數(shù):
if err != nil { return err }
在滿(mǎn)足業(yè)務(wù)需求的情況下,這種錯(cuò)誤處理其實(shí)是最推薦的方式,這種直接透?jìng)鞯姆绞阶尨a之間的耦合度更低。在很多情況下,如果不關(guān)心錯(cuò)誤中的具體信息,使用這種方式就可以了。
2、提前定義好 error
原生的 error 在有些情況下使用起來(lái)就不是很方便,比如我需要獲得具體的錯(cuò)誤信息,如果還用上面的方式來(lái)使用error,可能會(huì)出現(xiàn)下面的代碼:
if err != nil && err.Error() == "invalid param" { }
寫(xiě)過(guò)代碼的都知道上面的代碼很不優(yōu)雅,另外如果錯(cuò)誤的信息變化之后,這里的代碼邏輯就會(huì)出錯(cuò),可以通過(guò)把錯(cuò)誤定義成一個(gè)變量:
var ( ErrInvalidParam = errors.New("invalid param") )
那么上面的代碼就可以變成這樣:
if err != nil && err == ErrInvalidParam { }
如果一次性需要處理的錯(cuò)誤比較多,還可以使用 switch 進(jìn)行處理:
if err != nil { switch err { case ErrInvalidParam: return case ErrNetWork: return case ErrFileNotExist: return default: return } }
但是這種方式還不完美,因?yàn)?error 在傳遞的過(guò)程中,有可能會(huì)被包裝,以攜帶更多的堆棧信息,比如下面這樣:
if err != nil { // 在包裝錯(cuò)誤的時(shí)候,這里格式化錯(cuò)誤要使用%w return fmt.Errorf("add error info: %+v, origin error: %w", "other info", err) }
假設(shè)上面被包裝的錯(cuò)誤是 ErrInvalidParam,那么在調(diào)用的地方判斷錯(cuò)誤,就不能使用下面的代碼:
if err != nil && err == ErrInvalidParam { }
為了解決這個(gè)問(wèn)題, errors.Is 函數(shù)可以判斷被包裝的 error 中是否有預(yù)期的 error:
if errors.Is(err, ErrInvalidParam) { }
盡量使用 errors.Is 來(lái)替代對(duì) error 的比較。
3、使用自定義的錯(cuò)誤類(lèi)型
上面的 error 使用方式在某些情況下還是不能滿(mǎn)足要求。假如對(duì)于上面的無(wú)效參數(shù) error,業(yè)務(wù)方想要知道具體是哪個(gè)參數(shù)無(wú)效,直接定義的錯(cuò)誤就無(wú)法滿(mǎn)足要求。error 本質(zhì)是一個(gè)接口,也就是是說(shuō),只要實(shí)現(xiàn)了 Error 方法,就是一個(gè) error 類(lèi)型:
type error interface { Error() string }
那么就可以自定義一種錯(cuò)誤類(lèi)型:
type ErrInvalidParam struct { ParamName string ParamValue string } func (e *ErrInvalidParam) Error() string { return fmt.Sprintf("invalid param: %+v, value: %+v", e.ParamName, e.ParamValue) }
然后就可以使用類(lèi)型斷言機(jī)制或者類(lèi)型選擇機(jī)制,來(lái)對(duì)不同類(lèi)型的錯(cuò)誤進(jìn)行處理:
e, ok := err.(*ErrInvalidParam) if ok && e != nil { }
同樣可以在 switch 中使用:
if err != nil { switch err.(type) { case *ErrInvalidParam: return default: return } }
在這里 error 同樣會(huì)存在被包裝的問(wèn)題,而 errors.As 剛好可以用來(lái)解決這個(gè)問(wèn)題,可以判斷出被包裝的錯(cuò)誤中是否存在某個(gè) error 類(lèi)型:
var e *ErrInvalidParam if errors.As(err, &e) { }
4、更靈活的 error 類(lèi)型
上面的方式已經(jīng)可以解決大部分場(chǎng)景的 error 處理了,但是在一些復(fù)雜的情況下,可能需要從錯(cuò)誤中獲取更多的信息,還包含一定的邏輯處理。
在 Go 的 net 包中,有這樣的一個(gè)接口:
type Error interface { error Timeout() bool Temporary() bool }
在這個(gè)接口中,有兩個(gè)方法,這兩個(gè)方法會(huì)對(duì)這個(gè)錯(cuò)誤類(lèi)型進(jìn)行處理,判斷是超時(shí)錯(cuò)誤還是臨時(shí)錯(cuò)誤,實(shí)現(xiàn)了這個(gè)接口的 error 要實(shí)現(xiàn)這兩個(gè) 方法,實(shí)現(xiàn)具體的判斷邏輯。
在處理具體 error 時(shí),會(huì)調(diào)用相應(yīng)的方法來(lái)判斷:
if ne, ok := e.(net.Error); ok && ne.Temporary() { // 對(duì)臨時(shí)錯(cuò)誤進(jìn)行處理 }
if ne, ok := e.(net.Error); ok && ne.Timeout() { // 對(duì)超時(shí)錯(cuò)誤進(jìn)行處理 }
這種類(lèi)型的 error 相對(duì)來(lái)說(shuō),使用的會(huì)比較少,一般情況下,盡量不要使用這么復(fù)雜的處理方式。
5、errors 中的其他能力
在 errors 包中,除了上面提到的 errors.Is 和 errors.As 兩個(gè)很有用的函數(shù)之外,還有一個(gè)比較實(shí)用的函數(shù)errors.Unwrap,這個(gè)函數(shù)可以從包裝的錯(cuò)誤中將原錯(cuò)誤解析出來(lái)。
可以使用 fmt.Errorf 來(lái)包裝 error,需要使用 %w 的格式化:
return fmt.Errorf("add error info: %+v, origin error: %w", "other info", err)
在后續(xù)的 error 處理時(shí),可以調(diào)用 errors.Unwrap 函數(shù)來(lái)獲得被包裝前的 error:
err = errors.Unwrap(err) fmt.Printf("origin error: %+v\n", err)
package main import ( "errors" "fmt" ) func main(){ err1 := errors.New("zero") fmt.Printf("origin error: %+v\n", err1) err2 := fmt.Errorf("add error info: %+v, origin error: %w", "other info", err1) fmt.Printf("origin error: %+v\n", err2) err3 := errors.Unwrap(err2) fmt.Printf("origin error: %+v\n", err3) }
程序輸出
origin error: zero
origin error: add error info: other info, origin error: zero
origin error: zero
6、注意
如果需要使用 goroutine 時(shí),應(yīng)該使用統(tǒng)一的 Go 函數(shù)進(jìn)行創(chuàng)建,這個(gè)函數(shù)中會(huì)進(jìn)行 recover ,避免因?yàn)橐吧鷊oroutine panic 導(dǎo)致主進(jìn)程退出。
func Go(f func()){ go func(){ defer func(){ if err := recover(); err != nil { log.Printf("panic: %+v", err) } }() f() }() }
在應(yīng)用程序中出現(xiàn)錯(cuò)誤時(shí),使用 errors.New 或者 errors.Errorf 返回錯(cuò)誤:
errors.Errorf("用戶(hù)余額不足, uid: %d, money: %d", uid, money)
如果是調(diào)用應(yīng)用程序的其他函數(shù)出現(xiàn)錯(cuò)誤,請(qǐng)直接返回,如果需要攜帶信息,請(qǐng)使用 errors.WithMessage。
// https://github.com/pkg/errors errors.WithMessage(err, "其他附加信息")
如果是調(diào)用其他庫(kù)(標(biāo)準(zhǔn)庫(kù)、企業(yè)公共庫(kù)、開(kāi)源第三方庫(kù)等)獲取到錯(cuò)誤時(shí),請(qǐng)使用 errors.Wrap 添加堆棧信息。
切記,不要每個(gè)地方都是用 errors.Wrap 只需要在錯(cuò)誤第一次出現(xiàn)時(shí)進(jìn)行 errors.Wrap 即可。
根據(jù)場(chǎng)景進(jìn)行判斷是否需要將其他庫(kù)的原始錯(cuò)誤吞掉,例如可以把 repository 層的數(shù)據(jù)庫(kù)相關(guān)錯(cuò)誤吞掉,返回業(yè)務(wù)錯(cuò)誤碼,避免后續(xù)我們分割微服務(wù)或者更換 ORM 庫(kù)時(shí)需要去修改上層代碼。
注意我們?cè)诨A(chǔ)庫(kù),在大量引入的第三方庫(kù)編寫(xiě)時(shí)一般不使用 errors.Wrap 避免堆棧信息重復(fù)。
func f() error { err := json.Unmashal(&a, data) if err != nil { return errors.Wrap(err, "其他附加信息") } // 其他邏輯 return nil }
禁止每個(gè)出錯(cuò)的地方都打日志,只需要在進(jìn)程的最開(kāi)始的地方使用 %+v 進(jìn)行統(tǒng)一打印,例如 http/rpc 服務(wù)的中間件。
錯(cuò)誤判斷使用 errors.Is 進(jìn)行比較:
func f() error { err := A() if errors.Is(err, io.EOF){ return nil } // 其他邏輯 return nil }
錯(cuò)誤類(lèi)型判斷,使用 errors.As 進(jìn)行賦值:
func f() error { err := A() var errA errorA if errors.As(err, &errA){ } // 其他邏輯 return nil }
以上就是Go語(yǔ)言中錯(cuò)誤處理的方式總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Go錯(cuò)誤處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 詳解golang函數(shù)多返回值錯(cuò)誤處理與error類(lèi)型
- Go語(yǔ)言中的錯(cuò)誤處理最佳實(shí)踐詳解
- 重學(xué)Go語(yǔ)言之錯(cuò)誤處理與異常機(jī)制詳解
- Go語(yǔ)言中實(shí)現(xiàn)完美錯(cuò)誤處理實(shí)踐分享
- Golang錯(cuò)誤處理方式異常與error
- Golang中的錯(cuò)誤處理深入分析
- Golang中的錯(cuò)誤處理的示例詳解
- Golang中的錯(cuò)誤處理的示例詳解
- Go 代碼規(guī)范錯(cuò)誤處理示例經(jīng)驗(yàn)總結(jié)
- Go?錯(cuò)誤處理實(shí)踐總結(jié)示例
- Go錯(cuò)誤處理的幾種方式
相關(guān)文章
GO語(yǔ)言支付寶沙箱對(duì)接的實(shí)現(xiàn)
本文介紹了如何使用GO語(yǔ)言對(duì)接支付寶沙箱環(huán)境,包括秘鑰生成、SDK安裝和代碼實(shí)現(xiàn)等步驟,詳細(xì)內(nèi)容涵蓋了從秘鑰生成到前端代碼的每個(gè)階段,為開(kāi)發(fā)者提供了一條清晰的指引2024-09-093個(gè)Go語(yǔ)言中實(shí)用重構(gòu)技術(shù)分享
代碼重構(gòu)是在不改變外部功能的情況下對(duì)現(xiàn)有代碼進(jìn)行改進(jìn),是編程的核心部分之一,本文為大家介紹了Go語(yǔ)言中3個(gè)實(shí)用重構(gòu)技術(shù),需要的可以參考一下2023-06-06Go 標(biāo)準(zhǔn)庫(kù)增加metrics指標(biāo)探討分析
go中有一個(gè)神奇的標(biāo)準(zhǔn)庫(kù) runtime/metrics,提供了一系列預(yù)定義好的 Go 自身的相關(guān)指標(biāo),如果沒(méi)有編寫(xiě)過(guò)基礎(chǔ)監(jiān)控庫(kù)或者關(guān)注的比較少的朋友可能會(huì)沒(méi)接觸到這類(lèi)指標(biāo),本文展開(kāi)現(xiàn)有metrics 指標(biāo),并結(jié)合現(xiàn)有的社區(qū)討論一起看看還有沒(méi)有必要增加更多的標(biāo)準(zhǔn)庫(kù)指標(biāo)2023-10-10Gorm存在時(shí)更新,不存在時(shí)創(chuàng)建的問(wèn)題
這篇文章主要介紹了Gorm存在時(shí)更新,不存在時(shí)創(chuàng)建的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08詳解Golang?ProtoBuf的基本語(yǔ)法總結(jié)
最近項(xiàng)目是采用微服務(wù)架構(gòu)開(kāi)發(fā)的,各服務(wù)之間通過(guò)gPRC調(diào)用,基于ProtoBuf序列化協(xié)議進(jìn)行數(shù)據(jù)通信,因此接觸學(xué)習(xí)了Protobuf,本文會(huì)對(duì)Protobuf的語(yǔ)法做下總結(jié),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助2022-10-10