<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          社區(qū)精選 | if err != nil 太煩?Go 創(chuàng)始人教你如何對(duì)錯(cuò)誤進(jìn)行編程!

          共 5699字,需瀏覽 12分鐘

           ·

          2022-08-09 07:11

          今天小編為大家?guī)?lái)的是社區(qū)作者 煎魚(yú) 的文章,在這篇文章中他將和大家一起學(xué)習(xí)如何對(duì)錯(cuò)誤進(jìn)行編程!




          大家好,我是煎魚(yú)。


          前段時(shí)間我分享了一篇文章《10+ 條 Go 官方諺語(yǔ),你知道幾條?》,引發(fā)了許多小伙伴的討論。其中有一條 “Errors are values”,大家在是 “錯(cuò)誤是值” 還是 “錯(cuò)誤就是價(jià)值” 中反復(fù)橫跳,糾結(jié)不易。


          其實(shí)說(shuō)這句話(huà)的 Rob Pike,他用一篇文章《Errors are values》詮釋了這句諺語(yǔ)的意思,到底是什么?


          文章鏈接:https://go.dev/blog/errors-are-values


          今天煎魚(yú)和大家一起學(xué)習(xí),以下的 “我” 均代表 Rob Pike。


          背景



          Go 程序員,尤其是那些剛接觸該語(yǔ)言的程序員,經(jīng)常討論的一個(gè)問(wèn)題是如何處理錯(cuò)誤。對(duì)于以下代碼片段出現(xiàn)的次數(shù),談話(huà)經(jīng)常變成哀嘆(各大平臺(tái)吐槽、批判非常多,認(rèn)為設(shè)計(jì)的不好)。


          如下代碼:


          if err != nil {
              return err
          }

          掃描代碼片段


          我們最近掃描了我們能找到的所有 Go 開(kāi)源項(xiàng)目,發(fā)現(xiàn)這個(gè)代碼片段只在每一兩頁(yè)出現(xiàn)一次,比一些人認(rèn)為的要少。


          盡管如此,如果人們?nèi)匀徽J(rèn)為必須經(jīng)常輸入如下代碼:


          if err != nil


          那么一定有什么地方出了問(wèn)題,而明顯的目標(biāo)就是 Go 語(yǔ)言本身(說(shuō)設(shè)計(jì)的不好?)。


          錯(cuò)誤的理解


          顯然這是不幸的,誤導(dǎo)的,而且很容易糾正。也許現(xiàn)在的情況是,剛接觸 Go 的程序員會(huì)問(wèn):"如何處理錯(cuò)誤?",學(xué)習(xí)這種模式,然后就此打住。


          在其他語(yǔ)言中,人們可能會(huì)使用 try-catch 塊或其他類(lèi)似機(jī)制來(lái)處理錯(cuò)誤。因此,程序員認(rèn)為,當(dāng)我在以前的語(yǔ)言中會(huì)使用 try-catch 時(shí),我在 Go 中只需輸入 if err != nil。


          隨著時(shí)間的推移,Go 代碼中收集了許多這樣的片段,結(jié)果感覺(jué)很笨拙。


          錯(cuò)誤是值



          不管這種解釋是否合適,很明顯,這些 Go 程序員錯(cuò)過(guò)了關(guān)于錯(cuò)誤的一個(gè)基本點(diǎn):錯(cuò)誤是值(Errors are values)。


          值可以被編程,既然錯(cuò)誤是值,那么錯(cuò)誤也可以被編程。


          當(dāng)然,涉及錯(cuò)誤值的常見(jiàn)語(yǔ)句是測(cè)試它是否為 nil,但是還有無(wú)數(shù)其他事情可以用錯(cuò)誤值做,并且應(yīng)用其中一些其他事情可以使您的程序更好,消除很多樣板。

          如果使用死記硬背的 if 語(yǔ)句檢查每個(gè)錯(cuò)誤,就會(huì)出現(xiàn)這種情況(也就是 if err != nil 到處都是的情況)。


          bufio 例子


          下面是一個(gè)來(lái)自 bufio 包的 Scanner 類(lèi)型的簡(jiǎn)單例子。它的 Scan 方法執(zhí)行了底層的 I/O,這當(dāng)然會(huì)導(dǎo)致一個(gè)錯(cuò)誤。然而,Scan 方法根本沒(méi)有暴露出錯(cuò)誤。


          相反,它返回一個(gè)布爾值,并在掃描結(jié)束時(shí)運(yùn)行一個(gè)單獨(dú)的方法,報(bào)告是否發(fā)生錯(cuò)誤。


          客戶(hù)端代碼看起來(lái)像這樣:


          scanner := bufio.NewScanner(input)
          for scanner.Scan() {
              token := scanner.Text()
              // process token
          }
          if err := scanner.Err(); err != nil {
              // process the error
          }

          當(dāng)然,有一個(gè) nil 檢查錯(cuò)誤,但它只出現(xiàn)并執(zhí)行一次。Scan 方法可以改為定義為:

          func (s *Scanner) Scan() (token []byte, error)

          然后,用戶(hù)代碼的例子可能是(取決于如何檢索令牌):


          scanner := bufio.NewScanner(input)
          for {
              token, err := scanner.Scan()
              if err != nil {
                  return err // or maybe break
              }
              // process token
          }


          這并沒(méi)有太大的不同,但有一個(gè)重要的區(qū)別。在這段代碼中,客戶(hù)端必須在每次迭代時(shí)檢查錯(cuò)誤,但在真正的 Scanner API 中,錯(cuò)誤處理是從關(guān)鍵 API 元素中抽象出來(lái)的,它正在迭代令牌。


          使用真正的 API,客戶(hù)端的代碼因此感覺(jué)更自然:循環(huán)直到完成,然后擔(dān)心錯(cuò)誤。


          錯(cuò)誤處理不會(huì)掩蓋控制流程。


          當(dāng)然,在幕后發(fā)生的事情是,一旦 Scan 遇到 I/O 錯(cuò)誤,它就會(huì)記錄它并返回 false。當(dāng)客戶(hù)端詢(xún)問(wèn)時(shí),一個(gè)單獨(dú)的方法 Err 會(huì)報(bào)告錯(cuò)誤值。


          雖然這很微不足道,但它與在每個(gè) if err != nil 后到處放或要求客戶(hù)端檢查錯(cuò)誤是不一樣的。這是用錯(cuò)誤值編程。簡(jiǎn)單的編程,是的,但仍然是編程。


          值得強(qiáng)調(diào)的是,無(wú)論設(shè)計(jì)如何,程序檢查錯(cuò)誤是至關(guān)重要的,無(wú)論它們暴露在哪里。這里的討論不是關(guān)于如何避免檢查錯(cuò)誤,而是關(guān)于使用語(yǔ)言?xún)?yōu)雅地處理錯(cuò)誤。


          實(shí)戰(zhàn)探討



          當(dāng)我參加在東京舉行的 2014 年秋季 GoCon 時(shí),出現(xiàn)了重復(fù)錯(cuò)誤檢查代碼的話(huà)題。一位熱心的 Gopher,在 Twitter 上的名字是 @jxck\_,回應(yīng)了我們熟悉的關(guān)于錯(cuò)誤檢查的哀嘆。


          他有一些代碼,從結(jié)構(gòu)上看是這樣的:


          _, 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ù)的。在真正的代碼中,這段代碼比較長(zhǎng),有更多的事情要做,所以不容易只是用一個(gè)輔助函數(shù)來(lái)重構(gòu)這段代碼,但在這種理想化的形式中,一個(gè)函數(shù)字面的關(guān)閉對(duì)錯(cuò)誤變量會(huì)有幫助:


          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è)執(zhí)行寫(xiě)入的函數(shù)中關(guān)閉;單獨(dú)的輔助函數(shù)使用起來(lái)比較笨拙,因?yàn)樾枰谡{(diào)用之間維護(hù) err 變量(嘗試一下)。


          我們可以通過(guò)借用上面的掃描方法的思路,使之更簡(jiǎn)潔、更通用、更可重復(fù)使用。我在我們的討論中提到了這個(gè)技術(shù),但是 @jxck\_ 沒(méi)有看到如何應(yīng)用它。經(jīng)過(guò)長(zhǎng)時(shí)間的交流,在語(yǔ)言不通的情況下,我問(wèn)能不能借他的筆記本,打一些代碼給他看。


          我定義了一個(gè)名為 errWriter 的對(duì)象,如下所示:


          type errWriter struct {
              w   io.Writer
              err error
          }


          并給了它一種方法,Write。它不需要具有標(biāo)準(zhǔn)的 Write 簽名,并且部分小寫(xiě)以突出區(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ò)誤,Write 方法就會(huì)變成無(wú)用功,但錯(cuò)誤值會(huì)被保存。


          鑒于 errWriter 類(lèi)型和它的 Write 方法,上面的代碼可以被重構(gòu)為如下代碼:


          ew := &errWriter{w: fd}
          ew.write(p0[a:b])
          ew.write(p1[c:d])
          ew.write(p2[e:f])
          // and so on
          if ew.err != nil {
              return ew.err
          }


          這更干凈,甚至與使用閉包相比,也使實(shí)際的寫(xiě)入順序更容易在頁(yè)面上看到。不再有混亂。使用錯(cuò)誤值(和接口)進(jìn)行編程使代碼更好。


          很可能同一個(gè)包中的其他一些代碼可以基于這個(gè)想法,甚至直接使用 errWriter。


          另外,一旦 errWriter 存在,它可以做更多的事情來(lái)幫助,特別是在不太人性化的例子中。它可以積累字節(jié)數(shù)。它可以將寫(xiě)內(nèi)容凝聚成一個(gè)緩沖區(qū),然后以原子方式傳輸。還有更多。


          事實(shí)上,這種模式經(jīng)常出現(xiàn)在標(biāo)準(zhǔn)庫(kù)中。archive/zip 和 net/http 包使用它。在這個(gè)討論中更突出的是,bufio 包的 Writer 實(shí)際上是 errWriter 思想的一個(gè)實(shí)現(xiàn)。盡管 bufio.Writer.Write 返回錯(cuò)誤,但這主要是為了尊重 io.Writer 接口。


          bufio.Writer 的 Write 方法的行為就像我們上面的 errWriter.write 方法一樣,F(xiàn)lush 會(huì)報(bào)錯(cuò),所以我們的例子可以這樣寫(xiě):


          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)用程序而言:沒(méi)有辦法知道在錯(cuò)誤發(fā)生之前完成了多少處理。如果該信息很重要,則需要更細(xì)粒度的方法。不過(guò),通常情況下,最后進(jìn)行全有或全無(wú)檢查就足夠了。


          總結(jié)



          在本文中我們只研究了一種避免重復(fù)錯(cuò)誤處理代碼的技術(shù)。


          請(qǐng)記住,使用 errWriter 或 bufio.Writer 并不是簡(jiǎn)化錯(cuò)誤處理的唯一方法,而且這種方法并不適用于所有情況。


          然而,關(guān)鍵的教訓(xùn)是錯(cuò)誤是值,Go 編程語(yǔ)言的全部功能可用于處理它們。

          使用該語(yǔ)言來(lái)簡(jiǎn)化您的錯(cuò)誤處理。


          但請(qǐng)記住:無(wú)論您做什么,都要檢查您的錯(cuò)誤!


          Go 圖書(shū)系列


          • Go 語(yǔ)言入門(mén)系列:初探 Go 項(xiàng)目實(shí)戰(zhàn)
            https://eddycjy.com/go-categories/
          • Go 語(yǔ)言編程之旅:深入用 Go 做項(xiàng)目
            https://golang2.eddycjy.com/
          • Go 語(yǔ)言設(shè)計(jì)哲學(xué):了解 Go 的為什么和設(shè)計(jì)思考
            https://golang3.eddycjy.com/
          • Go 語(yǔ)言進(jìn)階之旅:進(jìn)一步深入 Go 源碼
            https://golang1.eddycjy.com/


          更多閱讀


          • Go 想要加個(gè)箭頭語(yǔ)法,這下更像 PHP 了!
            https://mp.weixin.qq.com/s/uo23gKC_Lbm0JNe5_YbVfA
          • Go 錯(cuò)誤處理新思路?用左側(cè)函數(shù)和表達(dá)式
            https://mp.weixin.qq.com/s/nzoFI8ANBVDP9VPWfDgoHw



          點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開(kāi)更多互動(dòng)和交流,公眾號(hào)后臺(tái)回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 20
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  偷拍亚洲天堂 | 操动漫美女一区 | 开心久久五月天 | 日韩在线黄色 | 九九成人在线 |