亚洲视频二区_亚洲欧洲日本天天堂在线观看_日韩一区二区在线观看_中文字幕不卡一区

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.430618.com 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

這篇文章摘取至我在日本東京舉辦的 GoCon spring conference 上的演講稿。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

錯(cuò)誤只是一些值

我花了很多時(shí)間來(lái)思考如何在 Go 中處理錯(cuò)誤是最好的。我真希望能有一種簡(jiǎn)單直接的方式來(lái)處理錯(cuò)誤,一些我們只要讓 Go 程序員記住就能使用的規(guī)則,就像教數(shù)學(xué)或字母表一樣。

然而,我得到的結(jié)論是:處理錯(cuò)誤不止有一種方式。我認(rèn)為 Go 處理錯(cuò)誤的方式可以劃分為 3 種主要的策略。

標(biāo)記錯(cuò)誤策略

第一種錯(cuò)誤處理策略,我稱之為標(biāo)記錯(cuò)誤

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

這個(gè)名字來(lái)源于在實(shí)際編程中,使用一個(gè)指定的值來(lái)表示程序已經(jīng)無(wú)法繼續(xù)執(zhí)行。所以在 Go 中我們使用一個(gè)指定的值來(lái)表示錯(cuò)誤。

例如:系統(tǒng)包里面的 io.EOF 或是在 syscall 包中更底層些的常量錯(cuò)誤例如

syscall.ENOENT。

甚至還有標(biāo)記表示沒(méi)有錯(cuò)誤發(fā)生例如:path/filepath.Walk 中的 go/build.NoGoError 和 path/filepath.SkipDir。

使用標(biāo)記值是靈活性最差的一種錯(cuò)誤處理策略,調(diào)用者必須使用相等操作符來(lái)比較返回值和預(yù)先定義的值。當(dāng)你想要提供更多的相關(guān)信息時(shí),返回不同的錯(cuò)誤值會(huì)破壞等式檢查操作。

即使通過(guò) fmt.Errorf 來(lái)提供更多的信息也會(huì)干擾調(diào)用者的等式測(cè)試,調(diào)用者必須去看 Error 方法輸出的結(jié)果是否匹配某個(gè)指定的字符串。

永遠(yuǎn)不要檢查 error.Error 的輸出

順便說(shuō)一下,我相信你永遠(yuǎn)都不需要檢查 error.Error 方法的返回值。 error 接口中的 Error 方法是提供給使用者查看的信息,而不是用來(lái)給代碼做判斷的。

這些信息應(yīng)該在日志文件中或者是顯示屏上出現(xiàn),你不需要通過(guò)檢查這些信息來(lái)改變程序行為。

我知道有時(shí)候這樣很難,就像有些人在 twitter 上提到的那樣,這條建議在寫(xiě)測(cè)試的時(shí)候不適用。盡管如此,在我看來(lái),作為一種編碼風(fēng)格,你應(yīng)該避免比較字符型的錯(cuò)誤信息。

標(biāo)記錯(cuò)誤成為公開(kāi) API 的一部分

如果你的公開(kāi)函數(shù)或方法返回了一些指定的錯(cuò)誤值,那么這些值必須是公開(kāi)的,當(dāng)然也需要在文檔中有所描述。這些加入到你的 API 中了。

如果你的 API 定義了一個(gè)返回指定錯(cuò)誤的接口,那么所有該接口的實(shí)現(xiàn)都必須只返回這個(gè)錯(cuò)誤,就算能提供更多的其他信息也不應(yīng)該返回除了指定錯(cuò)誤之外的信息。

我們可以在 io.Reader 中看到這樣的處理方式。 io.Copy 要求 reader 實(shí)現(xiàn)返回 io.EOF 通知調(diào)用者沒(méi)有更多的數(shù)據(jù)了,但是這并不是一個(gè)錯(cuò)誤。

標(biāo)記錯(cuò)誤在兩個(gè)包之間制造了依賴關(guān)系

最大的問(wèn)題是標(biāo)記錯(cuò)誤在兩個(gè)包之間制造了源碼層面的依賴關(guān)系。例如:檢查一個(gè)錯(cuò)誤是否是 io.EOF 你的代碼必須引入 io 包。

這個(gè)例子看起來(lái)沒(méi)那么糟糕,因?yàn)檫@是很普通的操作。但是想象一下,項(xiàng)目中很多包導(dǎo)出錯(cuò)誤值,而其他包必須導(dǎo)入對(duì)應(yīng)的包才能檢查錯(cuò)誤條件,這樣就違背了低耦合的設(shè)計(jì)原則。

我參與過(guò)的一個(gè)大型項(xiàng)目,使用的就是這種錯(cuò)誤處理模式,我可以告訴你不好的設(shè)計(jì)所帶來(lái)的循環(huán)引入問(wèn)題近在咫尺。

結(jié)論:避免使用標(biāo)記錯(cuò)誤策略

所以,我的建議是避免在代碼中使用標(biāo)記錯(cuò)誤處理策略。在標(biāo)準(zhǔn)庫(kù)中有些情況使用了這種處理方式,但是這并不是你應(yīng)該效仿的一種處理模式。

如果有人要求你從你的包里面暴露一個(gè)錯(cuò)誤值,你應(yīng)該禮貌的拒絕他,并提供一個(gè)替代方案,也就是下面將要提到的方法。

錯(cuò)誤類型

錯(cuò)誤類型是我想討論的第二種 Go 錯(cuò)誤處理模式。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

錯(cuò)誤類型是你創(chuàng)建的實(shí)現(xiàn) error 接口的類型。在下面的例子中, MyError 類型記錄了文件,行號(hào),相關(guān)的錯(cuò)誤信息。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

因?yàn)?MyError 是一個(gè)類型,所以調(diào)用者可以使用 type assertion 從 error 中獲取相關(guān)信息。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

錯(cuò)誤類型比標(biāo)記錯(cuò)誤最大的改進(jìn)就是通過(guò)封裝底層的錯(cuò)誤來(lái)提供更多的相關(guān)信息。

一個(gè)絕佳的例子就是 os.PathError 除了底層錯(cuò)誤外還提供了使用哪個(gè)文件,執(zhí)行哪個(gè)操作等相關(guān)信息。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

錯(cuò)誤類型存在的問(wèn)題

調(diào)用者可以使用 type assertion 或者 type switch,error 類型必須是公開(kāi)的。

如果你的代碼實(shí)現(xiàn)了一個(gè)約定指定錯(cuò)誤類型的接口,那么所有這個(gè)接口的實(shí)現(xiàn)者,都要依賴于定義這個(gè)錯(cuò)誤類型的包。

對(duì)包的錯(cuò)誤類型的過(guò)度暴露,使調(diào)用者和包之間產(chǎn)生了很強(qiáng)的耦合性,導(dǎo)致了 API 的脆弱性。

結(jié)論:避免錯(cuò)誤類型

雖然錯(cuò)誤類型在發(fā)生錯(cuò)誤時(shí)能夠捕捉到更多的環(huán)境信息,比標(biāo)記錯(cuò)誤要好一些,但是錯(cuò)誤類型也存在很多和標(biāo)記錯(cuò)誤一樣的問(wèn)題。

所以,在這里我的建議是避免使用錯(cuò)誤類型,至少避免使他們成為你 API 接口的一部分。

封裝錯(cuò)誤

現(xiàn)在我們到了第三個(gè)錯(cuò)誤處理分類。在我看來(lái)這個(gè)是靈活性最好的處理策略,在調(diào)用者和你的代碼之間產(chǎn)生的耦合度最低。

我管這種處理方式叫做封裝錯(cuò)誤 (Opaque errors),因?yàn)楫?dāng)你發(fā)現(xiàn)有錯(cuò)誤發(fā)生時(shí),你無(wú)法知道內(nèi)部的錯(cuò)誤情況。作為調(diào)用者,你只知道調(diào)用的結(jié)果成功或者失敗。

封裝錯(cuò)誤處理方式只返回錯(cuò)誤不去猜測(cè)他的內(nèi)容。如果你采用了這種處理方式,那么錯(cuò)誤處理在調(diào)試方面會(huì)變得非常有價(jià)值。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

例如:Foo 的調(diào)用約定沒(méi)有指定在發(fā)生錯(cuò)誤時(shí)會(huì)返回哪些相關(guān)信息,這樣 Foo 函數(shù)的開(kāi)發(fā)者就可以自由的提供相關(guān)錯(cuò)誤信息,并且不會(huì)影響到和調(diào)用者之間的約定。

斷言行為,而不是類型

在少數(shù)情況下,這種二元錯(cuò)誤處理方案是不夠的。

例如:在和進(jìn)程外交互的時(shí)候,比如網(wǎng)絡(luò)活動(dòng),需要調(diào)用者評(píng)估錯(cuò)誤情況來(lái)決定是否需要重試操作。

在這種情況下我們斷言錯(cuò)誤實(shí)現(xiàn)指定的行為要比斷言指定類型或值好些??纯聪旅娴睦樱?/p>Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

我們可以傳任何錯(cuò)誤給 IsTemporary 來(lái)判斷錯(cuò)誤是否需要重試。

如果錯(cuò)誤沒(méi)有實(shí)現(xiàn) temporary 接口;那么就沒(méi)有 Temporary 方法,那么錯(cuò)誤就不是 temporary。

如果錯(cuò)誤實(shí)現(xiàn)了 Temporary,如果 Temporary 返回 true 那么調(diào)用者就可以考慮重試該操作。

這里的關(guān)鍵點(diǎn)是這個(gè)實(shí)現(xiàn)邏輯不需要導(dǎo)入定義錯(cuò)誤的包或者了解任何關(guān)于錯(cuò)誤的底層類型,我們只要簡(jiǎn)單的關(guān)注它的行為即可。

優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

這引出了我想談的第二個(gè) Go 語(yǔ)言的格言:優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤。你能在下面的代碼中找出錯(cuò)誤么?

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

一個(gè)很明顯的建議是上面的代碼可以簡(jiǎn)化為

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

但是這只是個(gè)簡(jiǎn)單的問(wèn)題,任何人在代碼審查的時(shí)候都應(yīng)該看到。更根本的問(wèn)題是這段代碼看不出來(lái)原始錯(cuò)誤是在哪里發(fā)生的。

如果 authenticate 返回錯(cuò)誤, 那么 AuthenticateRequest 將會(huì)返回錯(cuò)誤給調(diào)用者,調(diào)用者也一樣返回。 在程序的最上一層主函數(shù)塊內(nèi)打印錯(cuò)誤信息到屏幕或者日志文件,然而所有信息就是 No such file or directory

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

沒(méi)有錯(cuò)誤發(fā)生的文件,行號(hào)等信息,也沒(méi)有調(diào)用棧信息。代碼的編寫(xiě)者必須在一堆函數(shù)中查找哪個(gè)調(diào)用路徑會(huì)返回 file not found 錯(cuò)誤。

Donovan 和 Kernighan 寫(xiě)的 The Go Programming Language 建議你使用 fmt.Errorf 在錯(cuò)誤路徑中增加相關(guān)信息

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

就像我們?cè)谇懊嫣岬降?,這個(gè)模式不兼容標(biāo)記錯(cuò)誤或者類型斷言,因?yàn)檗D(zhuǎn)換錯(cuò)誤值到字符串,再和其他的字符串合并,再使用 fmt.Errorf 轉(zhuǎn)換為error 打破了對(duì)等關(guān)系,破壞了原始錯(cuò)誤的相關(guān)信息。

注解錯(cuò)誤

我在這里建議一個(gè)給錯(cuò)誤添加相關(guān)信息的方法,要用到一個(gè)簡(jiǎn)單的包。代碼在 github.com/pkg/errors。這個(gè)包有兩個(gè)主要的函數(shù):

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

第一個(gè)函數(shù)是封裝函數(shù) Wrap ,輸入一個(gè)錯(cuò)誤和一個(gè)信息,生成一個(gè)新的錯(cuò)誤返回。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

第二個(gè)函數(shù)是 Cause ,輸入一個(gè)封裝過(guò)的錯(cuò)誤,解包之后得到原始的錯(cuò)誤信息。

使用這兩個(gè)函數(shù),我們現(xiàn)在可以給任何錯(cuò)誤添加相關(guān)信息,并且在我們需要查看底層錯(cuò)誤類型的時(shí)候可以解包查看。下面的例子是讀取文件內(nèi)容到內(nèi)存的函數(shù)。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

我們使用這個(gè)函數(shù)寫(xiě)一個(gè)讀取配置文件的函數(shù),然后在 main 中調(diào)用。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

如果 ReadConfig 發(fā)生錯(cuò)誤,由于使用了 errors.Wrap,我們可以得到一個(gè) K&D 風(fēng)格的包含相關(guān)信息的錯(cuò)誤

could not read config: open failed: open /Users/dfc/.settings.xml: no such file or directory

因?yàn)?errors.Wrap 生成了發(fā)生錯(cuò)誤時(shí)的調(diào)用棧信息,所以我們可以查看額外的調(diào)用棧調(diào)試信息。這又是一個(gè)同樣的例子,但是這次我們用 errors.Print 替換 fmt.Println

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

我們會(huì)得到如下的信息:

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

第一行來(lái)至 ReadConfig, 第二行來(lái)至 os.Open 的 ReadFile, 剩下的來(lái)至 os 包,沒(méi)有攜帶位置信息。

現(xiàn)在我們介紹了關(guān)于打包錯(cuò)誤生成棧的概念,我們需要談?wù)勅绾谓獍?。下面?errors.Cause 函數(shù)的作用。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

操作中,當(dāng)你需要檢查一個(gè)錯(cuò)誤是否匹配一個(gè)指定值或類型時(shí),你需要先使用 errors.Cause 獲取原始錯(cuò)誤信息

只處理一次錯(cuò)誤

最后我想要說(shuō)的是,你只需要處理一次錯(cuò)誤。處理錯(cuò)誤意味著檢查錯(cuò)誤值,然后作出決定。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

如果你不需要做出決定,你可以忽略這個(gè)錯(cuò)誤。在上面的例子可以看到我們忽略了 w.Write 返回的錯(cuò)誤。

但是在返回一個(gè)錯(cuò)誤時(shí)做出多個(gè)決定也是有問(wèn)題的。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

在這個(gè)例子中,如果 Write 發(fā)生錯(cuò)誤, 一行信息會(huì)寫(xiě)入日志,記錄發(fā)送錯(cuò)誤的文件和行號(hào),同時(shí)把錯(cuò)誤返回給調(diào)用者,同樣的調(diào)用者也可能會(huì)寫(xiě)入日志,然后返回,直到程序的最頂層。

日志文件里就會(huì)出現(xiàn)一堆重復(fù)的信息,但是在程序最頂層獲得的原始錯(cuò)誤卻沒(méi)有任何相關(guān)信息。

Go中優(yōu)雅的處理錯(cuò)誤,而不僅僅只是檢查錯(cuò)誤

 

使用 errors 包可以讓你在 error 里面加入相關(guān)信息,并且內(nèi)容是可以被人和機(jī)器所識(shí)別的。

結(jié)論

最后,錯(cuò)誤是你提供的包中公開(kāi) API 的一部分,要像其他公開(kāi) API 一樣小心對(duì)待。

為了獲得最大的靈活性,我建議你嘗試把所有的錯(cuò)誤當(dāng)做封裝錯(cuò)誤來(lái)處理,在那些無(wú)法做到的情況下,斷言行為錯(cuò)誤,而不是類型或值。

盡可能少的在你程序中使用標(biāo)記錯(cuò)誤,在錯(cuò)誤發(fā)生時(shí)盡早的使用 errors.Wrap 打包封裝為封裝錯(cuò)誤。


via: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully

作者:Dave Cheney 譯者:tyler2018 校對(duì):polaris1119

本文由 GCTT 原創(chuàng)編譯,Go語(yǔ)言中文網(wǎng) 榮譽(yù)推出

分享到:
標(biāo)簽:錯(cuò)誤 語(yǔ)言
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定