新聞中心
姍姍來(lái)遲的 Go 1.13 修改了 errors 包,增加了幾個(gè)函數(shù),用于增強(qiáng) error 的功能,這篇文章介紹 error 相關(guān)的用法。

創(chuàng)新互聯(lián)建站長(zhǎng)期為上千客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開(kāi)放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為安義企業(yè)提供專業(yè)的成都網(wǎng)站設(shè)計(jì)、成都網(wǎng)站建設(shè),安義網(wǎng)站改版等技術(shù)服務(wù)。擁有十年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開(kāi)發(fā)。
由于上上周發(fā)表的調(diào)度器系列文章的標(biāo)題比較文藝,導(dǎo)致這篇文章的標(biāo)題采用了相似的命名方法。我嘗試想寫(xiě)一個(gè)大的主題,奈何水平有限,如果沒(méi)有寫(xiě)出大家理想的水平,見(jiàn)諒~
按照慣例,手動(dòng)貼上文章的目錄:
寫(xiě)過(guò) C 的同學(xué)知道,C 語(yǔ)言中常常返回整數(shù)錯(cuò)誤碼(errno)來(lái)表示函數(shù)處理出錯(cuò),通常用 -1 來(lái)表示錯(cuò)誤,用 0 表示正確。
而在 Go 中,我們使用 error 類型來(lái)表示錯(cuò)誤,不過(guò)它不再是一個(gè)整數(shù)類型,是一個(gè)接口類型:
- type error interface {
- Error() string
- }
它表示那些能用一個(gè)字符串就能說(shuō)清的錯(cuò)誤。
我們最常用的就是 errors.New() 函數(shù),非常簡(jiǎn)單:
- // src/errors/errors.go
- func New(text string) error {
- return &errorString{text}
- }
- type errorString struct {
- s string
- }
- func (e *errorString) Error() string {
- return e.s
- }
使用 New 函數(shù)創(chuàng)建出來(lái)的 error 類型實(shí)際上是 errors 包里未導(dǎo)出的 errorString 類型,它包含唯一的一個(gè)字段 s,并且實(shí)現(xiàn)了唯一的方法:Error() string。
通常這就夠了,它能反映當(dāng)時(shí)“出錯(cuò)了”,但是有些時(shí)候我們需要更加具體的信息,例如:
- func Sqrt(f float64) (float64, error) {
- if f < 0 {
- return 0, errors.New("math: square root of negative number")
- }
- // implementation
- }
當(dāng)調(diào)用者發(fā)現(xiàn)出錯(cuò)的時(shí)候,只知道傳入了一個(gè)負(fù)數(shù)進(jìn)來(lái),并不清楚到底傳的是什么值。在 Go 里:
- It is the error implementation’s responsibility to summarize the context.
它要求返回這個(gè)錯(cuò)誤的函數(shù)要給出具體的“上下文”信息,也就是說(shuō),在 Sqrt 函數(shù)里,要給出這個(gè)負(fù)數(shù)到底是什么。
所以,如果發(fā)現(xiàn) f 小于 0,應(yīng)該這樣返回錯(cuò)誤:
- if f < 0 {
- return 0, fmt.Errorf("math: square root of negative number %g", f)
- }
這就用到了 fmt.Errorf 函數(shù),它先將字符串格式化,再調(diào)用 errors.New 函數(shù)來(lái)創(chuàng)建錯(cuò)誤。
當(dāng)我們想知道錯(cuò)誤類型,并且打印錯(cuò)誤的時(shí)候,直接打印 error:
- fmt.Println(err)
或者:
- fmt.Println(err.Error)
fmt 包會(huì)自動(dòng)調(diào)用 err.Error() 函數(shù)來(lái)打印字符串。
通常,我們將 error 放到函數(shù)返回值的最后一個(gè),沒(méi)什么好說(shuō)的,大家都這樣做,約定俗成。
參考資料【Tony Bai】這篇文章提到,構(gòu)造 error 的時(shí)候,要求傳入的字符串首字母小寫(xiě),結(jié)尾不帶標(biāo)點(diǎn)符號(hào),這是因?yàn)槲覀兘?jīng)常會(huì)這樣使用返回的 error:
- ... err := errors.New("error example")
- fmt.Printf("The returned error is %s.\n", err)
error 的困局
- In Go, error handling is important. The language’s design and conventions encourage you to explicitly check for errors where they occur (as distinct from the convention in other languages of throwing exceptions and sometimes catching them).
在 Go 語(yǔ)言中,錯(cuò)誤處理是非常重要的。它從語(yǔ)言層面要求我們需要明確地處理遇到的錯(cuò)誤。而不是像其他語(yǔ)言,類如 Java,使用 try-catch- finally 這種“把戲”。
這就造成代碼里 “error” 滿天飛,顯得非常冗長(zhǎng)拖沓。
而為了代碼健壯性考慮,對(duì)于函數(shù)返回的每一個(gè)錯(cuò)誤,我們都不能忽略它。因?yàn)槌鲥e(cuò)的同時(shí),很可能會(huì)返回一個(gè) nil 類型的對(duì)象。如果不對(duì)錯(cuò)誤進(jìn)行判斷,那下一行對(duì) nil 對(duì)象的操作百分之百會(huì)引發(fā)一個(gè) panic。
這樣,Go 語(yǔ)言中詬病最多的就是它的錯(cuò)誤處理方式似乎回到了上古 C 語(yǔ)言時(shí)代。
- rr := doStuff1()
- if err != nil {
- //handle error...
- }
- err = doStuff2()
- if err != nil {
- //handle error...
- }
- err = doStuff3()
- if err != nil {
- //handle error...
- }
Go authors 之一的 Russ Cox 對(duì)于這種觀點(diǎn)進(jìn)行過(guò)駁斥:當(dāng)初選擇返回值這種錯(cuò)誤處理機(jī)制而不是 try-catch,主要是考慮前者適用于大型軟件,后者更適合小程序。
在參考資料【Go FAQ】里也提到,try-catch 會(huì)讓代碼變得非常混亂,程序員會(huì)傾向?qū)⒁恍┏R?jiàn)的錯(cuò)誤,例如,failing to open a file,也拋到異常里,這會(huì)讓錯(cuò)誤處理更加冗長(zhǎng)繁瑣且易出錯(cuò)。
而 Go 語(yǔ)言的多返回值使得返回錯(cuò)誤異常簡(jiǎn)單。對(duì)于真正的異常,Go 提供 panic-recover 機(jī)制,也使得代碼看起來(lái)非常簡(jiǎn)潔。
當(dāng)然 Russ Cox 也承認(rèn) Go 的錯(cuò)誤處理機(jī)制對(duì)于開(kāi)發(fā)人員的確有一定的心智負(fù)擔(dān)。
參考資料【Go 語(yǔ)言的錯(cuò)誤處理機(jī)制是一個(gè)優(yōu)秀的設(shè)計(jì)嗎?】是知乎上的一個(gè)回答,闡述了 Go 對(duì)待錯(cuò)誤和異常的不同處理方式,前者使用 error,后者使用 panic,這樣的處理比較 Java 那種錯(cuò)誤異常一鍋端的做法更有優(yōu)勢(shì)。
【如何優(yōu)雅的在Golang中進(jìn)行錯(cuò)誤處理】對(duì)于在業(yè)務(wù)上如何處理 error,給出了一些很好的示例。
嘗試破局
這部分的內(nèi)容主要來(lái)自 Dave cheney GoCon 2016 的演講,參考資料可以直達(dá)原文。
經(jīng)常聽(tīng)到 Go 有很多“箴言”,說(shuō)得很順口,但理解起來(lái)并不是太容易,因?yàn)樗鼈兇蟛糠侄际怯泄适碌摹@?,我們常說(shuō):
Don’t communicating by sharing memory, share memory by communicating.
文中還列舉了很多,都很有意思:
下面我們講三條關(guān)于 error 的“箴言”。
Errors are just values
Errors are just values 的實(shí)際意思是只要實(shí)現(xiàn)了 Error 接口的類型都可以認(rèn)為是 Error,重要的是要理解這些“箴言”背后的道理。
作者把處理 error 的方式分為三種:
- Sentinel errors
- Error Types
- Opaque errors
我們來(lái)挨個(gè)說(shuō)。首先 Sentinel errors,Sentinel 來(lái)自計(jì)算機(jī)中常用的詞匯,中文意思是“哨兵”。以前在學(xué)習(xí)快排的時(shí)候,會(huì)有一個(gè)“哨兵”,其他元素都要和“哨兵”進(jìn)行比較,它劃出了一條界限。
這里 Sentinel errors 實(shí)際想說(shuō)的是這里有一個(gè)錯(cuò)誤,暗示處理流程不能再進(jìn)行下去了,必須要在這里停下,這也是一條界限。而這些錯(cuò)誤,往往是提前約定好的。
例如,io 包里的 io.EOF,表示“文件結(jié)束”錯(cuò)誤。但是這種方式處理起來(lái),不太靈活:
- func main() {
- r := bytes.NewReader([]byte("0123456789"))
- _, err := r.Read(make([]byte, 10))
- if err == io.EOF {
- log.Fatal("read failed:", err)
- }
- }
必須要判斷 err 是否和約定好的錯(cuò)誤 io.EOF 相等。
再來(lái)一個(gè)例子,當(dāng)我想返回 err 并且加上一些上下文信息時(shí),就麻煩了:
在 readfile 函數(shù)里判斷 err 不為空,則用 fmt.Errorf 在 err 前加上具體的 file 信息,返回給調(diào)用者。返回的 err 其實(shí)還是一個(gè)字符串。
造成的后果時(shí),調(diào)用者不得不用字符串匹配的方式判斷底層函數(shù) readfile 是不是出現(xiàn)了某種錯(cuò)誤。當(dāng)你必須要這樣才能判斷某種錯(cuò)誤時(shí),代碼的“壞味道”就出現(xiàn)了。
順帶說(shuō)一句,err.Error() 方法是給程序員而非代碼設(shè)計(jì)的,也就是說(shuō),當(dāng)我們調(diào)用 Error 方法時(shí),結(jié)果要寫(xiě)到文件或是打印出來(lái),是給程序員看的。在代碼里,我們不能根據(jù) err.Error() 來(lái)做一些判斷,就像上面的 main 函數(shù)里做的那樣,不好。
Sentinel errors 最大的問(wèn)題在于它在定義 error 和使用 error 的包之間建立了依賴關(guān)系。比如要想判斷 err == io.EOF 就得引入 io 包,當(dāng)然這是標(biāo)準(zhǔn)庫(kù)的包,還 Ok。如果很多用戶自定義的包都定義了錯(cuò)誤,那我就要引入很多包,來(lái)判斷各種錯(cuò)誤。麻煩來(lái)了,這容易引起循環(huán)引用的問(wèn)題。
因此,我們應(yīng)該盡量避免 Sentinel errors,僅管標(biāo)準(zhǔn)庫(kù)中有一些包這樣用,但建議還是別模仿。
第二種就是 Error Types,它指的是實(shí)現(xiàn)了 error 接口的那些類型。它的一個(gè)重要的好處是,類型中除了 error 外,還可以附帶其他字段,從而提供額外的信息,例如出錯(cuò)的行數(shù)等。
標(biāo)準(zhǔn)庫(kù)有一個(gè)非常好的例子:
- // PathError records an error and the operation and file path that caused it.
- type PathError struct {
- Op string
- Path string
- Err error
- }
PathError 額外記錄了出錯(cuò)時(shí)的文件路徑和操作類型。
通常,使用這樣的 error 類型,外層調(diào)用者需要使用類型斷言來(lái)判斷錯(cuò)誤:
- // underlyingError returns the underlying error for known os error types.
- func underlyingError(err error) error {
- switch err := err.(type) {
- case *PathError:
- return err.Err
- case *LinkError:
- return err.Err
- case *SyscallError:
- return err.Err
- }
- return err
- }
但是這又不可避免地在定義錯(cuò)誤和使用錯(cuò)誤的包之間形成依賴關(guān)系,又回到了前面的問(wèn)題。
即使 Error types 比 Sentinel errors 好一些,因?yàn)樗艹休d更多的上下文信息,但是它仍然存在引入包依賴的問(wèn)題。因此,也是不推薦的。至少,不要把 Error types 作為一個(gè)導(dǎo)出類型。
最后一種,Opaque errors。翻譯一下,就是“黑盒 errors”,因?yàn)槟隳苤厘e(cuò)誤發(fā)生了,但是不能看到它內(nèi)部到底是什么。
譬如下面這段偽代碼:
- func fn() error {
- x, err := bar.Foo()
- if err != nil {
- return err
- }
- // use x
- return nil
- }
作為調(diào)用者,調(diào)用完 Foo 函數(shù)后,只用知道 Foo 是正常工作還是出了問(wèn)題。也就是說(shuō)你只需要判斷 err 是否為空,如果不為空,就直接返回錯(cuò)誤。否則,繼續(xù)后面的正常流程,不需要知道 err 到底是什么。
這就是處理 Opaque errors 這種類型錯(cuò)誤的策略。
當(dāng)然,在某些情況下,這樣做并不夠用。例如,在一個(gè)網(wǎng)絡(luò)請(qǐng)求中,需要調(diào)用者判斷返回的錯(cuò)誤類型,以此來(lái)決定是否重試。這種情況下,作者給出了一種方法:
- type temporary interface {
- Temporary() bool
- }
- func IsTemporary(err error) bool {
- te, ok := err.(temporary)
- return ok && te.Temporary()
- }
就是說(shuō),不去判斷錯(cuò)誤的類型到底是什么,而是去判斷錯(cuò)誤是否具有某種行為,或者說(shuō)實(shí)現(xiàn)了某個(gè)接口。
來(lái)個(gè)例子:
type temporary interface { Temporary() bool}func IsTemporary(err error) bool { te, ok := err.(temporary) return ok && te.Temporary()}
拿到網(wǎng)絡(luò)請(qǐng)求返回的 error 后,調(diào)用 IsTemporary 函數(shù),如果返回 true,那就重試。
這么做的好處是在進(jìn)行網(wǎng)絡(luò)請(qǐng)求的包里,不需要 import 引用定義錯(cuò)誤的包。
handle not just check errors
這一節(jié)要說(shuō)第二句箴言:“Don’t just check errors, handle them gracefully”。
- func AuthenticateRequest(r *Request) error {
- err := authenticate(r.User)
- if err != nil {
- return err
- }
- return nil
- }
上面這個(gè)例子中的代碼是有問(wèn)題的,直接優(yōu)化成一句就可以了:
- func AuthenticateRequest(r *Request) error {
- return authenticate(r.User)
- }
還有其他的問(wèn)題,在函數(shù)調(diào)用鏈的最頂層,我們得到的錯(cuò)誤可能是:No such file or directory。
這個(gè)錯(cuò)誤反饋的信息太少了,不知道文件名、路徑、行號(hào)等等。
嘗試改進(jìn)一下,增加一點(diǎn)上下文:
- func AuthenticateRequest(r *Request) error {
- err := authenticate(r.User)
- if err != nil {
- return fmt.Errorf("authenticate failed: %v", err)
- }
- return nil
- }
這種做法實(shí)際上是先錯(cuò)誤轉(zhuǎn)換成字符串,再拼接另一個(gè)字符串,最后,再通過(guò) fmt.Errorf 轉(zhuǎn)換成錯(cuò)誤。這樣做破壞了相等性檢測(cè),即我們無(wú)法判斷錯(cuò)誤是否是一種預(yù)先定義好的錯(cuò)誤了。
應(yīng)對(duì)方案是使用第三方庫(kù):github.com/pkg/errors。提供了友好的界面:
- // Wrap annotates cause with a message.
- func Wrap(cause error, message string) error
- // Cause unwraps an annotated error.
- func Cause(err error) error
通過(guò) Wrap 可以將一個(gè)錯(cuò)誤,加上一個(gè)字符串,“包裝”成一個(gè)新的錯(cuò)誤;通過(guò) Cause 則可以進(jìn)行相反的操作,將里層的錯(cuò)誤還原。
有了這兩個(gè)函數(shù),就方便很多:
- func ReadFile(path string) ([]byte, error) {
- f, err := os.Open(path)
- if err != nil {
- return nil, errors.Wrap(err, "open failed")
- }
- defer f.Close()
- buf, err := ioutil.ReadAll(f)
- if err != nil {
- return nil, errors.Wrap(err, "read failed")
- }
- return buf, nil
- }
這是一個(gè)讀文件的函數(shù),先嘗試打開(kāi)文件,如果出錯(cuò),則返回一個(gè)附加上了 “open failed” 的錯(cuò)誤信息;之后,嘗試讀文件,如果出錯(cuò),則返回一個(gè)附加上了 “read failed” 的錯(cuò)誤。
當(dāng)在外層調(diào)用 ReadFile 函數(shù)時(shí):
- func main() {
- _, err := ReadConfig()
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
- }
- func ReadConfig() ([]byte, error) {
- home := os.Getenv("HOME")
- config, err := ReadFile(filepath.Join(home, ".settings.xml"))
- return config, errors.Wrap(err, "could not read config")
- }
這樣我們?cè)?main 函數(shù)里就能打印出這樣一個(gè)錯(cuò)誤信息:
- could not read config: open failed: open /Users/dfc/.settings.xml: no such file or directory
它是有層次的,非常清晰。而如果我們用 pkg/errors 庫(kù)提供的打印函數(shù):
- func main() {
- _, err := ReadConfig()
- if err != nil {
- errors.Print(err)
- os.Exit(1)
- }
- }
能得到更有層次、更詳細(xì)的錯(cuò)誤:
- readfile.go:27: could not read config
- readfile.go:14: open failed
- open /Users/dfc/.settings.xml: no such file or directory
上面講的是 Wrap 函數(shù),接下來(lái)看一下 “Cause” 函數(shù),以前面提到的 temporary 接口為例:
- type temporary interface {
- Temporary() bool
- }
- // IsTemporary returns true if err is temporary.
- func IsTemporary(err error) bool {
- te, ok := errors.Cause(err).(temporary)
- return ok && te.Temporary()
- }
判斷之前先使用 Cause 取出錯(cuò)誤,做斷言,最后,遞歸地調(diào)用 Temporary 函數(shù)。如果錯(cuò)誤沒(méi)實(shí)現(xiàn) temporary 接口,就會(huì)斷言失敗,返回 false。
Only handle errors once
什么叫“處理”錯(cuò)誤:
- Handling an error means inspecting the error value, and making a decision.
意思是查看了一下錯(cuò)誤,并且做出一個(gè)決定。
例如,如果不做任何決定,相當(dāng)于忽略了錯(cuò)誤:
- func Write(w io.Writer, buf []byte) { w.Write(buf)
- w.Write(buf)
- }
w.Write(buf) 會(huì)返回兩個(gè)結(jié)果,一個(gè)表示寫(xiě)成功的字節(jié)數(shù),一個(gè)是 error,上面的例子中沒(méi)有對(duì)這兩個(gè)返回值做任何處理。
下面這個(gè)例子卻又處理了兩次錯(cuò)誤:
- func Write(w io.Writer, buf []byte) error {
- _, err := w.Write(buf)
- if err != nil {
- // annotated error goes to log file
- log.Println("unable to write:", err)
- // unannotated error returned to caller return err
- return err
- }
- return nil
- }
第一次處理是將錯(cuò)誤寫(xiě)進(jìn)了日志,第二次處理則是將錯(cuò)誤返回給上層調(diào)用者。而調(diào)用者也可能將錯(cuò)誤寫(xiě)進(jìn)日志或是繼續(xù)返回給上層。
這樣一來(lái),日志文件中會(huì)有很多重復(fù)的錯(cuò)誤描述,并且在最上層調(diào)用者(如 main 函數(shù))看來(lái),它拿到的錯(cuò)誤卻還是最底層函數(shù)返回的 error,沒(méi)有任何上下文信息。
使用第三方的 error 包就可以比較完美的解決問(wèn)題:
- func Write(w io.Write, buf []byte) error {
- _, err := w.Write(buf)
- return errors.Wrap(err, "write failed")
- }
返回的錯(cuò)誤,對(duì)于人和機(jī)器而言,都是友好的。
小結(jié)
這一部分主要講了處理 error 的一些原則,引入了第三方的 errors 包,使得錯(cuò)誤處理變得更加優(yōu)雅。
作者最后給出了一些結(jié)論:
- errors 就像對(duì)外提供的 API 一樣,需要認(rèn)真對(duì)待。
- 將 errors 看成黑盒,判斷它的行為,而不是類型。
- 盡量不要使用 sentinel errors。
- 使用第三方的錯(cuò)誤包來(lái)包裹 error(errors.Wrap),使得它更好用。
- 使用 errors.Cause 來(lái)獲取底層的錯(cuò)誤。
胎死腹中的 try 提案
之前已經(jīng)出現(xiàn)用 “check & handle” 關(guān)鍵字和 “try 內(nèi)置函數(shù)”改進(jìn)錯(cuò)誤處理流程的提案,目前 try 內(nèi)置函數(shù)的提案已經(jīng)被官方提前拒絕,原因是社區(qū)里一邊倒地反對(duì)聲音。
關(guān)于這兩個(gè)提案的具體內(nèi)容見(jiàn)參考資料【check & handle】和【try 提案】。
go 1.13 的改進(jìn)
有一些 Go 語(yǔ)言失敗的嘗試,比如 Go 1.5 引入的 vendor 和 internal 來(lái)管理包,最后被濫用而引發(fā)了很多問(wèn)題。因此 Go 1.13 直接拋棄了 GOPATH 和 vendor 特性,改用 module 來(lái)管理包。
柴大在《Go 語(yǔ)言十年而立,Go2 蓄勢(shì)待發(fā)》一文中表示:
比如最近 Go 語(yǔ)言之父之一 Robert Griesemer 提交的通過(guò) try 內(nèi)置函數(shù)來(lái)簡(jiǎn)化錯(cuò)誤處理就被否決了。失敗的嘗試是一個(gè)好的現(xiàn)象,它表示 Go 語(yǔ)言依然在一些新興領(lǐng)域的嘗試 —— Go 語(yǔ)言依然處于活躍期。
今年 9 月 3 號(hào),Go 發(fā)布 1.13 版本,除了 module 特性轉(zhuǎn)正之外,還改進(jìn)了數(shù)字字面量。比較重要的還有 defer 性能提升 30%,將更多的對(duì)象從堆上移動(dòng)到棧上以提升性能,等等。
還有一個(gè)重大的改進(jìn)發(fā)生在 errors 標(biāo)準(zhǔn)庫(kù)中。errors 庫(kù)增加了 Is/As/Unwrap三個(gè)函數(shù),這將用于支持錯(cuò)誤的再次包裝和識(shí)別處理,為 Go 2 中新的錯(cuò)誤處理改進(jìn)提前做準(zhǔn)備。
1.13 支持了 error 包裹(wrapping):
An error e can wrap another error w by providing an Unwrap method that returns w. Both e and w are available to programs, allowing e to provide additional context to w or to reinterpret it while still allowing programs to make decisions based on w.
為了支持 wrapping,fmt.Errorf 增加了 %w 的格式,并且在 error 包增加了三個(gè)函數(shù):errors.Unwrap,errors.Is,errors.As。
fmt.Errorf
使用 fmt.Errorf 加上 %w 格式符來(lái)生成一個(gè)嵌套的 error,它并沒(méi)有像 pkg/errors 那樣使用一個(gè) Wrap 函數(shù)來(lái)嵌套 error,非常簡(jiǎn)潔。
Unwrap
- func Unwrap(err error) error
將嵌套的 error 解析出來(lái),多層嵌套需要調(diào)用 Unwrap 函數(shù)多次,才能獲取最里層的 error。
源碼如下:
- func Unwrap(err error) error {
- // 判斷是否實(shí)現(xiàn)了 Unwrap 方法
- u, ok := err.(interface {
- Unwrap() error
- })
- // 如果不是,返回 nil
- if !ok {
- return nil
- }
- // 調(diào)用 Unwrap 方法返回被嵌套的 error
- return u.Unwrap()
- }
對(duì) err 進(jìn)行斷言,看它是否實(shí)現(xiàn)了 Unwrap 方法,如果是,調(diào)用它的 Unwrap 方法。否則,返回 nil。
Is
- func Is(err, target error) bool
判斷 err 是否和 target 是同一類型,或者 err 嵌套的 error 有沒(méi)有和 target 是同一類型的,如果是,則返回 true。
源碼如下:
通過(guò)一個(gè)無(wú)限循環(huán),使用 Unwrap 不斷地將 err 里層嵌套的 error 解開(kāi),再看被解開(kāi)的 error 是否實(shí)現(xiàn)了 Is 方法,并且調(diào)用它的 Is 方法,當(dāng)兩者都返回 true 的時(shí)候,整個(gè)函數(shù)返回 true。
As
- func As(err error, target interface{}) bool
從 err 錯(cuò)誤鏈里找到和 target 相等的并且設(shè)置 target 所指向的變量。
源碼如下:
返回 true 的條件是錯(cuò)誤鏈里的 err 能被賦值到 target 所指向的變量;或者 err 實(shí)現(xiàn)的 As(interface{}) bool 方法返回 true。
前者,會(huì)將 err 賦給 target 所指向的變量;后者,由 As 函數(shù)提供這個(gè)功能。
如果 target 不是一個(gè)指向“實(shí)現(xiàn)了 error 接口的類型或者其它接口類型”的非空的指針的時(shí)候,函數(shù)會(huì) panic。
這一部分的內(nèi)容,飛雪無(wú)情大佬的文章【飛雪無(wú)情 分析 1.13 錯(cuò)誤】寫(xiě)得比較好,推薦閱讀。
總結(jié)
Go 語(yǔ)言使用 error 和 panic 處理錯(cuò)誤和異常是一個(gè)非常好的做法,比較清晰。至于是使用 error 還是 panic,看具體的業(yè)務(wù)場(chǎng)景。
當(dāng)然,Go 中的 error 過(guò)于簡(jiǎn)單,以至于無(wú)法記錄太多的上下文信息,對(duì)于錯(cuò)誤包裹也沒(méi)有比較好的辦法。當(dāng)然,這些可以通過(guò)第三方庫(kù)來(lái)解決。官方也在新發(fā)布的 go 1.13 中對(duì)這一塊作出了改進(jìn),相信在 Go 2 里會(huì)有更進(jìn)一步的優(yōu)化。
本文還列舉了一些處理 error 的示例,例如不要兩次處理一個(gè)錯(cuò)誤,判斷錯(cuò)誤的行為而不是類型等等。
參考資料里列舉了很多錯(cuò)誤處理相關(guān)的示例,這篇文章作為一個(gè)引子。
參考資料
【Go 2 錯(cuò)誤提案】https://go.googlesource.com/proposal/+/master/design/29934-error-values.md
【check & handle】https://go.googlesource.com/proposal/+/master/design/go2draft-error-handling-overview.md
【錯(cuò)誤討論的 issue】https://github.com/golang/go/issues/29934
【error value 的 FAQ】https://github.com/golang/go/wiki/ErrorValueFAQ
【error 包】https://golang.org/pkg/errors/
【飛雪無(wú)情的博客 錯(cuò)誤處理】https://www.flysnow.org/2019/01/01/golang-error-handle-suggestion.html
【飛雪無(wú)情 分析 1.13 錯(cuò)誤】https://www.flysnow.org/2019/09/06/go1.13-error-wrapping.html
【Tony Bai Go語(yǔ)言錯(cuò)誤處理】https://tonybai.com/2015/10/30/error-handling-in-go/
【Go 官方 error 使用教程】https://blog.golang.org/error-handling-and-go
【Go FAQ】https://golang.org/doc/faq#exceptions
【ethancai 錯(cuò)誤處理】https://ethancai.github.io/2017/12/29/Error-Handling-in-Go/
【Dave cheney GoCon 2016 演講】https://dave.cheney.net/paste/gocon-spring-2016.pdf
【Morsing's Blog Effective error handling in Go】http://morsmachine.dk/error-handling
【如何優(yōu)雅的在Golang中進(jìn)行錯(cuò)誤處理】https://www.ituring.com.cn/article/508191
【Go 2 錯(cuò)誤處理提案:try 還是 check?】https://toutiao.io/posts/uh9qo7/preview
【try 提案】https://github.com/golang/go/issues/32437
【否決 try 提案】https://github.com/golang/go/issues/32437#issuecomment-512035919
【Go 語(yǔ)言的錯(cuò)誤處理機(jī)制是一個(gè)優(yōu)秀的設(shè)計(jì)嗎?】https://www.zhihu.com/question/27158146/answer/44676012
文章名稱:Golang error 的突圍
瀏覽地址:http://fisionsoft.com.cn/article/cojgeop.html


咨詢
建站咨詢
