Golang中Error的設(shè)計(jì)與實(shí)踐詳解
如果你對(duì)于 Go 的 Error 設(shè)計(jì)不太熟悉也不習(xí)慣,為什么許多接口都需要返回 error 接口類型的值呢?什么時(shí)候該處理 error,什么時(shí)候該拋出 error,什么時(shí)候又該忽略 error ?Go 設(shè)計(jì)者又為什么要這樣設(shè)計(jì) error 呢?想必剛接觸 Golang 的同學(xué)也會(huì)和我一樣有類似的疑惑,在讀了 TGPL 以及 Go Blog 相關(guān)的章節(jié)/內(nèi)容后,我嘗試回答一下這些問題。
在第 1 、2小節(jié)我將嘗試回答 error 是什么,它是如何設(shè)計(jì)的,以及為什么這樣設(shè)計(jì)。
在第 3 小節(jié)我將回答在 Coding 時(shí),如何處理錯(cuò)誤。
Error 是什么
在 Go built-in 包中,Error 被設(shè)計(jì)為一個(gè)接口。
// The error built-in interface type is the conventional interface for // representing an error condition, with the nil value representing no error. type error interface { Error() string }
Go 的設(shè)計(jì)理念是:失?。╢ailure)只是一種常見的行為。
因此對(duì)于那些失敗被視作理所當(dāng)然的函數(shù)可以返回一個(gè)額外的結(jié)果——error,通常是最后一個(gè)返回值。而如果失敗只有一種可能的原因,那么只需要返回一個(gè) bool 值即可。
上述做法在 Go 的源碼或接口設(shè)計(jì)中很常見。舉兩個(gè)例子:
以常見的 Reader 接口為例, Read 方法讀取至多 len(p) 個(gè)字節(jié)到 p 中,返回讀取的字節(jié)數(shù) n 和讀取過程中可能發(fā)生的錯(cuò)誤 err。
type Reader interface { Read(p []byte) (n int, err error) }
當(dāng)我們使用 map 時(shí)經(jīng)常遇到的一種情況是:確定某個(gè)鍵是否在 map 中。但是 map 在該鍵不存在時(shí)也會(huì)返回默認(rèn)值,此時(shí)可以使用帶 bool 返回值的形式:
if val, ok := m["key"]; ok { // do something } else { // do other things }
Error 的設(shè)計(jì)
Go 的錯(cuò)誤處理設(shè)計(jì)與其他語言的異常不同。Go 中的 error 就是一個(gè)普通的值對(duì)象,而其他語言如 Java 中的 Exception 將會(huì)造成程序控制流的終止和其他行為,Exception 與普通的值不同。雖然 Go 也有類似的異常機(jī)制 —— panic,但它僅用于報(bào)告完全無法預(yù)料的錯(cuò)誤(可能有 Bug),而不應(yīng)該是一個(gè)健壯程序應(yīng)該返回的程序錯(cuò)誤(這一點(diǎn)與 Java 等語言不同)。
關(guān)于 Go 為什么這樣設(shè)計(jì)的原因,Kernighan 在 《The Go Programming Language》中給出解釋:"The reason for this design is that exceptions tend to entangle the description of an error with the control flow required to handle it, often leading to an undesirable outcome: routine errors are reported to the end user in the form of an incomprehensible stack trace, full of information about the structure of the program but lacking intelligible context about what went wrong"。
即:因?yàn)楫惓?huì)將錯(cuò)誤的描述和處理錯(cuò)誤的控制流糾纏在一起,通常會(huì)導(dǎo)致程序錯(cuò)誤以一種難以理解的棧追蹤的方式被報(bào)告給終端用戶,這種方式充滿了程序結(jié)構(gòu)的信息,但是缺少關(guān)于哪里出錯(cuò)的易于理解的上下文信息。
相反,Go 程序使用普通的程序控制流機(jī)制如 if 以及 return 來對(duì) error 作出響應(yīng),這種設(shè)計(jì)雖然要求 Gophers 更加關(guān)注錯(cuò)誤處理邏輯,但這正是它想做到的點(diǎn)。即“好的程序應(yīng)該考慮到所有可能的錯(cuò)誤,并且對(duì)其進(jìn)行處理”。
Go 將 error 設(shè)計(jì)為一個(gè)接口,只需要實(shí)現(xiàn) Error() string 方法,返回有意義、簡練的錯(cuò)誤描述信息即可。這也使得我們可以以任何的方式來自定義錯(cuò)誤。
Tips: 建議在底層只需要返回清晰地錯(cuò)誤信息,每一層包裹一些重要并且簡潔的上下文信息,并且最終在程序的頂層或者某一個(gè)不得不處理的層級(jí)處理該錯(cuò)誤。
正是這種方式,在 Go 中也將這種層層包裹的錯(cuò)誤稱之為錯(cuò)誤鏈。由此,在 Go 1.13 之后出現(xiàn)了一些新的設(shè)計(jì)以支持這種錯(cuò)誤鏈的處理方式。其中最簡單的錯(cuò)誤鏈就是如下所述的層層包裹的文本信息(或者程序調(diào)用棧信息)
genesis: crashed: no parachute: G-switch failed: bad relay orientation
Error 處理策略
最常見的做法是傳遞錯(cuò)誤。即將被調(diào)用程序產(chǎn)生的錯(cuò)誤傳遞給調(diào)用方,由上層決定如何處理,并且如果需要的話可以附加一些本程序所知的上下文信息。
obj, err := doSomething() if err != nil { return err } // do otherthings
第二種,對(duì)于那些表示短暫的、難以預(yù)測(cè)的錯(cuò)誤,可以去重試該操作,當(dāng)然要施加一定的重試次數(shù)限制
for i := 0; i < times; i++ { res, err := run() if err == nil { return res } // do something like log or metrics }
第三種,如果無法繼續(xù)往下執(zhí)行,調(diào)用方打印錯(cuò)誤信息并且優(yōu)雅地結(jié)束程序
if err := initT(); err != nil { panic("something wrong") // though this way is not elegant }
第四種,在某些情況錯(cuò)誤并不致命,也可以只是記錄下錯(cuò)誤并且繼續(xù)往下執(zhí)行。這種情況,最多導(dǎo)致程序缺少部分功能,但總比什么都不做要好。
obj, err := doSomething() if err != nil { logs.CtxInfo(ctx, "something wrong but it doesnot cause serious consequences") } // and continue to do something
最后一種,調(diào)用方確信不可能發(fā)生的錯(cuò)誤或者即使發(fā)生了也不會(huì)有任何問題,可以忽略它。
// could not encode failed bytes, _ := json.Marshal(obj)
最后,Go 的錯(cuò)誤處理比較特別,一般在檢查錯(cuò)誤之后,先處理失敗情況然后再處理成功情形——"Happy Path"。可以保證所有錯(cuò)誤均被處理之后再開心的處理正常情形(提醒 Programmer 不要忘記處理異常情況),并且可以減少縮進(jìn)層級(jí)(在其他語言也被稱為 "Guard"模式)。
obj, err := getObj() if err != nil { // do some err handling policy return fmt.Errorf("could not get obj, err = %v", err) } // happy path
到此這篇關(guān)于Golang中Error的設(shè)計(jì)與實(shí)踐詳解的文章就介紹到這了,更多相關(guān)golang error內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Windows10系統(tǒng)下安裝Go環(huán)境詳細(xì)步驟
Go語言是谷歌推出的一款全新的編程語言,可以在不損失應(yīng)用程序性能的情況下極大的降低代碼的復(fù)雜性,這篇文章主要給大家介紹了關(guān)于Windows10系統(tǒng)下安裝Go環(huán)境的詳細(xì)步驟,需要的朋友可以參考下2023-11-11Go?for-range?的?value值地址每次都一樣的原因解析
循環(huán)語句是一種常用的控制結(jié)構(gòu),在?Go?語言中,除了?for?關(guān)鍵字以外,還有一個(gè)?range?關(guān)鍵字,可以使用?for-range?循環(huán)迭代數(shù)組、切片、字符串、map?和?channel?這些數(shù)據(jù)類型,這篇文章主要介紹了Go?for-range?的?value值地址每次都一樣的原因解析,需要的朋友可以參考下2023-05-05Go語言跨平臺(tái)時(shí)字符串中的換行符如何統(tǒng)一?
本文介紹了Go語言中統(tǒng)一換行符的方法,包括使用`strings.ReplaceAll`函數(shù)將Windows風(fēng)格的換行符`\r\n`替換為Unix風(fēng)格的換行符`\n`,或?qū)\n`替換為`\r\n`,統(tǒng)一換行符可以避免不同平臺(tái)間顯示不一致、傳輸時(shí)出現(xiàn)多余的換行符或丟失換行符,以及解析錯(cuò)誤等問題2024-11-11利用Go語言快速實(shí)現(xiàn)一個(gè)極簡任務(wù)調(diào)度系統(tǒng)
任務(wù)調(diào)度(Task Scheduling)是很多軟件系統(tǒng)中的重要組成部分,字面上的意思是按照一定要求分配運(yùn)行一些通常時(shí)間較長的腳本或程序。本文將利用Go語言快速實(shí)現(xiàn)一個(gè)極簡任務(wù)調(diào)度系統(tǒng),感興趣的可以了解一下2022-10-10一篇文章帶你搞懂Go語言標(biāo)準(zhǔn)庫Time
在我們開發(fā)的過程中,每個(gè)項(xiàng)目都需要時(shí)間這一類的函數(shù),此時(shí)對(duì)time這個(gè)包的研究深度就顯得尤為重要,這篇文章主要給大家介紹了關(guān)于如何通過一篇文章帶你搞懂Go語言標(biāo)準(zhǔn)庫Time的相關(guān)資料,需要的朋友可以參考下2022-10-10