Errors are values
原文地址?https://blog.golang.org/errors-are-values
Go程序員之間(特別是這些剛接觸Go語言的新人)一個(gè)常見的討論點(diǎn)是如何處理錯(cuò)誤。談話經(jīng)常變成為對(duì)如下代碼序列出現(xiàn)次數(shù)的感嘆。
If err!=nil{
??return err
}
我們最近掃描了我們可以找到的所有開源項(xiàng)目,并發(fā)現(xiàn)這個(gè)代碼段每頁或每?jī)身撝怀霈F(xiàn)一次,比你相信的要少。然而,如果這種看法仍然存在:所有時(shí)候都必須輸入
If err!=nil
一定有些地方是錯(cuò)誤的,明顯的目標(biāo)就是Go本身。
?
這是不幸的,誤導(dǎo)的,很容易糾正的。也許正在發(fā)生的事情是新的程序員問道“如何處理錯(cuò)誤?”,學(xué)習(xí)這種模式,并停止在那里。在其他語言中,可能會(huì)使用try-catch塊或其他這樣的機(jī)制來處理錯(cuò)誤。因此,程序員認(rèn)為,當(dāng)我用舊的語言使用try-catch時(shí),我將在Go中輸入err!=nil。隨著時(shí)間的推移,Go代碼收集了很多這樣的片段,結(jié)果感覺很笨拙。
無論這個(gè)解釋是否合適,很明顯,這些Go程序員錯(cuò)過了關(guān)于errors的基本觀點(diǎn):錯(cuò)誤是值(Errors are values)
值可編程,并且由于錯(cuò)誤是值,所以可以對(duì)錯(cuò)誤進(jìn)行編程。
當(dāng)然,一個(gè)涉及錯(cuò)誤值的常見語句是測(cè)試它是否為nil,但是還有無數(shù)的其他東西可以使用錯(cuò)誤值,并且應(yīng)用某些其他的東西可以使你的程序更好,消除了大量樣板(如果每個(gè)error通過一個(gè)死記硬背的if語句來判斷)。
這是一個(gè)簡(jiǎn)單的例子,來自bufio包的Scanner類型。它的Scan方法執(zhí)行底層I/O,這當(dāng)然可能導(dǎo)致錯(cuò)誤。然而Scan方法根本不會(huì)顯示錯(cuò)誤。相反,它返回一個(gè)布爾值,并且在掃描結(jié)束時(shí)用一個(gè)單獨(dú)的方法報(bào)告是否發(fā)生錯(cuò)誤??蛻舳舜a如下所示:
scanner := bufio.NewScanner(input)
for scanner.Scan() {
????token := scanner.Text()
????// process token
}
if err := scanner.Err(); err != nil {
????// process the error
}
?
當(dāng)然,這里有個(gè)對(duì)err的nil檢查,但它似乎只執(zhí)行一次。Scan方法可以被定義為
func (s *Scanner) Scan() (token []byte, error)
?
然后示例用戶代碼可能如下:(取決于如何取得token)
scanner := bufio.NewScanner(input)
for {
????token, err := scanner.Scan()
????if err != nil {
????????return err // or maybe break
????}
????// process token
}
?
這不是很大的不同,但有一個(gè)重要的區(qū)別。在此代碼中,客戶端必須在每次迭代時(shí)檢查錯(cuò)誤,但在實(shí)際的Scan API中,將錯(cuò)誤處理從關(guān)鍵的API元素中抽象出來,在token上迭代。使用真實(shí)的API,客戶端代碼感覺更自然:循環(huán)直到完成,然后判斷錯(cuò)誤。錯(cuò)誤處理不會(huì)干擾控制流。
?看內(nèi)部發(fā)生了什么,當(dāng)然是,一旦Scan遇到I/O錯(cuò)誤,它會(huì)記錄它并返回false。一個(gè)單獨(dú)的方法Err會(huì)在客戶端詢問時(shí)報(bào)告錯(cuò)誤值。簡(jiǎn)而言之,這與把 if err != nil放到任何地方或要求客戶端在每個(gè)token后檢查錯(cuò)誤的做法是不一樣的。它是用錯(cuò)誤值進(jìn)行編程。簡(jiǎn)單的編程,是的,但仍然編程。
值得強(qiáng)調(diào)的是,無論設(shè)計(jì)如何,程序檢查errors是非常重要的,然而它們是暴露出來的。這里的討論不是關(guān)于如何避免檢查錯(cuò)誤,它是關(guān)于使用語言來優(yōu)雅地處理錯(cuò)誤。
?
當(dāng)我在東京參加2014年秋天的GoCon時(shí),出現(xiàn)了重復(fù)性錯(cuò)誤檢查代碼的主題。一個(gè)熱心的gopher,在Twitter上的@jxck_,回應(yīng)了熟悉的關(guān)于錯(cuò)誤檢查的感嘆。他有一些代碼看起來像這樣:
_, err = fd.Write(p0[a:b])
if err != nil {
????return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
????return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
????return err
}
// and so on
這是非常重復(fù)的。在真正的代碼中(更長的),有更多的,所以使用一個(gè)helper函數(shù)重構(gòu)是不容易的,但是在這種理想的形式中,一個(gè)關(guān)于錯(cuò)誤變量的函數(shù)將有幫助
?
var err error
write := func(buf []byte) {
????if err != nil {
????????return
????}
????_, err = w.Write(buf)
}
write(p0[a:b])
write(p1[c:d])
write(p2[e:f])
// and so on
if err != nil {
????return err
}
這個(gè)模式運(yùn)作良好,但是需要在每個(gè)執(zhí)行寫入操作的函數(shù)中有一個(gè)閉包;一個(gè)單獨(dú)的helper函數(shù)使用起來是笨拙的,因?yàn)?/span>err變量需要在調(diào)用之間保持。
?
我們可以通過借用Scan方法的想法來使這個(gè)更整潔,更通用和可重用。我在討論中提到了這種技術(shù),但是@jxck_沒有看懂如何應(yīng)用它。經(jīng)過長時(shí)間的交流,收到語言障礙的阻礙,我問道我是否可以借用他的筆記本電腦,并通過鍵入一些代碼來給他看。
我定義了一個(gè)名為errWrite的對(duì)象,如下所示:
type errWriter struct {
????w ??io.Writer
????err error
}
?
并給它一個(gè)方法,write。它不需要具有標(biāo)準(zhǔn)的Write簽名,小寫部分來突出顯示區(qū)別。write方法調(diào)用底層Writer的Write方法,并記錄第一個(gè)錯(cuò)誤以供將來參考:
?
func (ew *errWriter) write(buf []byte) {
????if ew.err != nil {
????????return
????}
????_, ew.err = ew.w.Write(buf)
}
?
一旦發(fā)生錯(cuò)誤,寫入方法將變?yōu)闊o效,但是會(huì)保存錯(cuò)誤值。
給出errWrite類型及其寫入方法,可以重構(gòu)以上代碼:
?
ew:=&errWriter {w:fd}
ew.write(p0 [a:b])
ew.write(p1 [c:d])
ew.write(p2 [e:f])
// 等等
如果ew.err!= nil {
???? 返回ew.err
}
?
這更干凈,甚至與使用閉包相比,也使得實(shí)際的寫入序列在頁面上更容易看到。沒有任何雜亂。使用錯(cuò)誤值(和接口)編程使代碼更好。
?
同一個(gè)軟件包中的其他一些代碼很可能會(huì)建立在這個(gè)想法上,甚至直接使用errWriter。
?
另外,一旦errWriter存在,還有更多的可以幫助,特別是在較少人為的例子中。它可以累加字節(jié)數(shù)。它可以將寫入合并到單個(gè)緩沖區(qū),然后可以原子傳輸。以及更多。
?
實(shí)際上,這種模式經(jīng)常出現(xiàn)在標(biāo)準(zhǔn)庫中。Archive/zip和net/http包使用它。這個(gè)討論更為突出,bufio包的Writer實(shí)際上是一個(gè)errWriter想法的實(shí)現(xiàn)。雖然bufio.Writer.Write返回一個(gè)error,這主要是關(guān)于遵守io.Writer接口的形式。bufio.Writer的Write方法與上面的errWrite.write方法是一樣,Flush報(bào)告錯(cuò)誤,所以我們的例子可以這樣寫:
b := bufio.NewWriter(fd)
b.Write(p0[a:b])
b.Write(p1[c:d])
b.Write(p2[e:f])
// and so on
if b.Flush() != nil {
????return b.Flush()
}
?
這種方法有一個(gè)明顯的缺點(diǎn),至少對(duì)于某些應(yīng)用程序:無法知道錯(cuò)誤發(fā)生之前完成了多少處理。如果這些信息很重要,則需要采用更細(xì)粒度的方法。通常,盡管如此,一個(gè)在末尾的完全或沒有檢查就足夠了。
?
我們只看了一種避免重復(fù)錯(cuò)誤處理代碼的技術(shù)。請(qǐng)記住,使用errWrite或者bufio.Writer不是簡(jiǎn)化錯(cuò)誤處理的唯一方法,而且這種方法不適用于所有情況。然而,重要的教訓(xùn)是errors是值,Go編程語言的全部功能可用于處理它們。
使用語言來簡(jiǎn)化錯(cuò)誤處理。
?
但記住:無論你做什么,總是檢查你的errors。
?
最后,關(guān)于我與@jxck_互動(dòng)的完整故事,包括他錄制的一點(diǎn)視頻,請(qǐng)?jiān)L問他的博客。?
?
轉(zhuǎn)載于:https://www.cnblogs.com/majianguo/p/6793152.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的Errors are values的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: likelihood(似然) and l
- 下一篇: VMware虚拟机 CentOS 6.5