Golang中Error的設(shè)計與實踐詳解
如果你對于 Go 的 Error 設(shè)計不太熟悉也不習(xí)慣,為什么許多接口都需要返回 error 接口類型的值呢?什么時候該處理 error,什么時候該拋出 error,什么時候又該忽略 error ?Go 設(shè)計者又為什么要這樣設(shè)計 error 呢?想必剛接觸 Golang 的同學(xué)也會和我一樣有類似的疑惑,在讀了 TGPL 以及 Go Blog 相關(guān)的章節(jié)/內(nèi)容后,我嘗試回答一下這些問題。
在第 1 、2小節(jié)我將嘗試回答 error 是什么,它是如何設(shè)計的,以及為什么這樣設(shè)計。
在第 3 小節(jié)我將回答在 Coding 時,如何處理錯誤。
Error 是什么
在 Go built-in 包中,Error 被設(shè)計為一個接口。
// 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è)計理念是:失敗(failure)只是一種常見的行為。
因此對于那些失敗被視作理所當(dāng)然的函數(shù)可以返回一個額外的結(jié)果——error,通常是最后一個返回值。而如果失敗只有一種可能的原因,那么只需要返回一個 bool 值即可。
上述做法在 Go 的源碼或接口設(shè)計中很常見。舉兩個例子:
以常見的 Reader 接口為例, Read 方法讀取至多 len(p) 個字節(jié)到 p 中,返回讀取的字節(jié)數(shù) n 和讀取過程中可能發(fā)生的錯誤 err。
type Reader interface { Read(p []byte) (n int, err error) }
當(dāng)我們使用 map 時經(jīng)常遇到的一種情況是:確定某個鍵是否在 map 中。但是 map 在該鍵不存在時也會返回默認值,此時可以使用帶 bool 返回值的形式:
if val, ok := m["key"]; ok { // do something } else { // do other things }
Error 的設(shè)計
Go 的錯誤處理設(shè)計與其他語言的異常不同。Go 中的 error 就是一個普通的值對象,而其他語言如 Java 中的 Exception 將會造成程序控制流的終止和其他行為,Exception 與普通的值不同。雖然 Go 也有類似的異常機制 —— panic,但它僅用于報告完全無法預(yù)料的錯誤(可能有 Bug),而不應(yīng)該是一個健壯程序應(yīng)該返回的程序錯誤(這一點與 Java 等語言不同)。
關(guān)于 Go 為什么這樣設(shè)計的原因,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"。
即:因為異常會將錯誤的描述和處理錯誤的控制流糾纏在一起,通常會導(dǎo)致程序錯誤以一種難以理解的棧追蹤的方式被報告給終端用戶,這種方式充滿了程序結(jié)構(gòu)的信息,但是缺少關(guān)于哪里出錯的易于理解的上下文信息。
相反,Go 程序使用普通的程序控制流機制如 if 以及 return 來對 error 作出響應(yīng),這種設(shè)計雖然要求 Gophers 更加關(guān)注錯誤處理邏輯,但這正是它想做到的點。即“好的程序應(yīng)該考慮到所有可能的錯誤,并且對其進行處理”。
Go 將 error 設(shè)計為一個接口,只需要實現(xiàn) Error() string 方法,返回有意義、簡練的錯誤描述信息即可。這也使得我們可以以任何的方式來自定義錯誤。
Tips: 建議在底層只需要返回清晰地錯誤信息,每一層包裹一些重要并且簡潔的上下文信息,并且最終在程序的頂層或者某一個不得不處理的層級處理該錯誤。
正是這種方式,在 Go 中也將這種層層包裹的錯誤稱之為錯誤鏈。由此,在 Go 1.13 之后出現(xiàn)了一些新的設(shè)計以支持這種錯誤鏈的處理方式。其中最簡單的錯誤鏈就是如下所述的層層包裹的文本信息(或者程序調(diào)用棧信息)
genesis: crashed: no parachute: G-switch failed: bad relay orientation
Error 處理策略
最常見的做法是傳遞錯誤。即將被調(diào)用程序產(chǎn)生的錯誤傳遞給調(diào)用方,由上層決定如何處理,并且如果需要的話可以附加一些本程序所知的上下文信息。
obj, err := doSomething() if err != nil { return err } // do otherthings
第二種,對于那些表示短暫的、難以預(yù)測的錯誤,可以去重試該操作,當(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)用方打印錯誤信息并且優(yōu)雅地結(jié)束程序
if err := initT(); err != nil { panic("something wrong") // though this way is not elegant }
第四種,在某些情況錯誤并不致命,也可以只是記錄下錯誤并且繼續(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ā)生的錯誤或者即使發(fā)生了也不會有任何問題,可以忽略它。
// could not encode failed bytes, _ := json.Marshal(obj)
最后,Go 的錯誤處理比較特別,一般在檢查錯誤之后,先處理失敗情況然后再處理成功情形——"Happy Path"??梢员WC所有錯誤均被處理之后再開心的處理正常情形(提醒 Programmer 不要忘記處理異常情況),并且可以減少縮進層級(在其他語言也被稱為 "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è)計與實踐詳解的文章就介紹到這了,更多相關(guān)golang error內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Windows10系統(tǒng)下安裝Go環(huán)境詳細步驟
Go語言是谷歌推出的一款全新的編程語言,可以在不損失應(yīng)用程序性能的情況下極大的降低代碼的復(fù)雜性,這篇文章主要給大家介紹了關(guān)于Windows10系統(tǒng)下安裝Go環(huán)境的詳細步驟,需要的朋友可以參考下2023-11-11Go?for-range?的?value值地址每次都一樣的原因解析
循環(huán)語句是一種常用的控制結(jié)構(gòu),在?Go?語言中,除了?for?關(guān)鍵字以外,還有一個?range?關(guān)鍵字,可以使用?for-range?循環(huán)迭代數(shù)組、切片、字符串、map?和?channel?這些數(shù)據(jù)類型,這篇文章主要介紹了Go?for-range?的?value值地址每次都一樣的原因解析,需要的朋友可以參考下2023-05-05利用Go語言快速實現(xiàn)一個極簡任務(wù)調(diào)度系統(tǒng)
任務(wù)調(diào)度(Task Scheduling)是很多軟件系統(tǒng)中的重要組成部分,字面上的意思是按照一定要求分配運行一些通常時間較長的腳本或程序。本文將利用Go語言快速實現(xiàn)一個極簡任務(wù)調(diào)度系統(tǒng),感興趣的可以了解一下2022-10-10