<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>

          除了 fmt.Errorf() 之外—Go 中的日常錯(cuò)誤對(duì)象:CockroachDB errors 庫(kù)(第4篇)

          共 2501字,需瀏覽 6分鐘

           ·

          2020-12-26 15:58

          點(diǎn)擊上方藍(lán)色“Go語(yǔ)言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

          在 Go 中傳遞錯(cuò)誤的慣用方式是使用預(yù)定義的類型錯(cuò)誤。但是 Go 的標(biāo)準(zhǔn)庫(kù)僅提供了非常簡(jiǎn)單的錯(cuò)誤對(duì)象,errors.New() 和fmt.Errorf()。

          本文介紹了 CockroachDB 的 error 庫(kù)(它是 Go 標(biāo)準(zhǔn)庫(kù)庫(kù) errors 的直接替代品),Go 程序員如何用它來描述和傳播其代碼中的錯(cuò)誤和錯(cuò)誤代號(hào)(code)。

          Go 標(biāo)準(zhǔn)庫(kù) errors 太簡(jiǎn)單

          由 fmt.Errorf() 構(gòu)造的 Go 中最常見的“簡(jiǎn)單”錯(cuò)誤對(duì)象類似于帶有錯(cuò)誤接口的包含在結(jié)構(gòu)中的字符串:其 Error() 方法返回構(gòu)造錯(cuò)誤時(shí)設(shè)置的字符串。

          err?:=?fmt.Errorf("hello")
          fmt.Println(err)?//?prints?"hello"

          什么都沒有,僅此而已。打印錯(cuò)誤對(duì)象也會(huì)顯示該字符串。順便說一句,使用 Go 的錯(cuò)誤包 errors 的構(gòu)造函數(shù)構(gòu)建錯(cuò)誤 errors.New() 結(jié)果一樣。

          日常代碼的簡(jiǎn)單錯(cuò)誤

          如果使用 Dave Cheney 的錯(cuò)誤庫(kù)[1],或者甚至更好的 CockroachDB 錯(cuò)誤庫(kù)[2](通過導(dǎo)入 github.com/cockroachdb/errors),則簡(jiǎn)單錯(cuò)誤也會(huì)在構(gòu)造錯(cuò)誤時(shí)自動(dòng)捕獲堆棧跟蹤。

          僅當(dāng)詳細(xì)打印錯(cuò)誤時(shí)才顯示堆棧跟蹤。這樣可以更輕松地排除錯(cuò)誤的來源:

          import?(
          ???"fmt"
          ???"github.com/cockroachdb/errors"
          )

          func?main()?{
          ??err?:=?errors.New("hello")
          ??fmt.Println(err)?//?still?prints?just?"hello"

          ??fmt.Printf("%+v\n",?err)?//?verbose?mode
          }

          這會(huì)打印:

          hello
          (1)?attached?stack?trace
          ??--?stack?trace:
          ??|?main.main
          ??|?????/home/kena/src/errors-tests/test.go:10
          ??|?runtime.main
          ??|?????/usr/lib/go-1.14/src/runtime/proc.go:203
          ??|?runtime.goexit
          ??|?????/usr/lib/go-1.14/src/runtime/asm_amd64.s:1373
          Wraps:?(2)?hello
          Error?types:?(1)?*withstack.withStack?(2)?*errutil.leafError

          此詳細(xì)輸出包括第一行的 .Error() 結(jié)果,后跟堆棧跟蹤內(nèi)容。

          經(jīng)驗(yàn)一次又一次地表明,在程序中出現(xiàn)意外情況的確切點(diǎn)提取堆棧跟蹤的能力對(duì)于查明確切原因并成功解決問題至關(guān)重要。沒有這種能力,程序員會(huì)毫無線索,麻木排查,浪費(fèi)大量時(shí)間。

          僅出于這個(gè)原因,我不鼓勵(lì)任何人使用 Go 自己的 fmt.Errorf() 或 errors.New()。相反,請(qǐng)導(dǎo)入 github.com/cockroachdb/errors 并仔細(xì)閱讀以下內(nèi)容:

          • errors.New():直接替換 Go 標(biāo)準(zhǔn)庫(kù)的 errors.New(),但它會(huì)帶有堆棧跟蹤;
          • errors.Errorf() 或 errors.Newf():用堆棧跟蹤的方式替換 Go 標(biāo)準(zhǔn)庫(kù)的 fmt.Errorf();
          package?github.com/cockroachdb/errors

          //?New?constructs?a?simple?error?and?attaches?a?stack?trace.
          func?New(msg?string)?error

          //?Newf?constructs?a?simple?error?whose?message?is?composed?using?printf-like?formatting.
          //?It?also?attaches?a?stack?trace.
          func?Newf(format?string,?args?...interface{})?error

          //?Errorf?is?an?alias?for?Newf?for?convenience
          //?and?drop-in?compatibility?with?github.com/pkg/errors.
          func?Errorf(format?string,?args?...interface{})?error

          在錯(cuò)誤中添加消息前綴以識(shí)別上下文

          當(dāng)從多個(gè)位置調(diào)用相同的邏輯,并且可能因錯(cuò)誤而失敗時(shí),則希望將消息前綴添加到任何返回的錯(cuò)誤對(duì)象。

          這有助于提供有關(guān)“錯(cuò)誤發(fā)生的位置”的更多上下文,以便在運(yùn)行時(shí)出現(xiàn)錯(cuò)誤時(shí)(何時(shí)出現(xiàn)錯(cuò)誤),可以清楚地了解哪個(gè)代碼路徑產(chǎn)生了錯(cuò)誤。

          例如:

          package?main

          import?(
          ???"fmt"
          ???"github.com/cockroachdb/errors"
          )

          func?foo()?error?{
          ?????return?errors.New("boo")
          }

          func?bar()?error?{
          ?????if?err?:=?foo();?err?!=?nil?{
          ????????return?errors.Wrap(err,?"bar")
          ?????}
          ?????return?nil
          }

          func?baz()?error?{
          ?????if?err?:=?foo();?err?!=?nil?{
          ????????return?errors.Wrap(err,?"baz")
          ?????}
          ?????return?nil
          }

          func?main()?{
          ?????r?:=?rollDice()
          ?????var?err?error
          ?????if?(r?4)?{
          ????????err?=?bar()
          ?????}?else?{
          ????????err?=?baz()
          ?????}
          ?????fmt.Println(err)
          }

          多虧了 errors.Wrap(),它為消息添加了前綴,main 函數(shù)可能報(bào)告 bar:boo 或 baz:boo,并且人可以很方便的知曉是調(diào)用了哪個(gè)函數(shù)導(dǎo)致的錯(cuò)誤。如果沒有 errors.Wrap(),則導(dǎo)致錯(cuò)誤的調(diào)用路徑將是無法發(fā)現(xiàn)的。

          為方便起見,當(dāng)提供 nil 錯(cuò)誤作為輸入時(shí),errors.Wrap() 返回nil。在許多情況下,這使我們可以消除 if err != nil 條件。例如:

          func?bar()?error?{
          ?????return?errors.Wrap(foo(),?"bar")
          }

          func?baz()?error?{
          ?????return?errors.Wrap(foo(),?"baz")
          }

          最后,errors.Wrap() 還將輔助堆棧跟蹤附加到錯(cuò)誤對(duì)象,從而在對(duì)錯(cuò)誤的來源進(jìn)行故障排除時(shí)提供了額外的上下文。在 channel 的場(chǎng)景出現(xiàn)錯(cuò)誤是特別有用。

          對(duì)于 errors.New(),此堆棧跟蹤僅在顯示詳細(xì)錯(cuò)誤時(shí)可見。例如:

          import?(
          ???"fmt"
          ???"github.com/cockroachdb/errors"
          )

          func?foo()?error?{?return?errors.New("world")?}
          func?bar(err?error)?error?{?return?errors.Wrap(err,?"hello")?}
          func?baz()?error?{?return?bar(foo())?}

          func?main()?{
          ??err?:=?baz()
          ??fmt.Println(err)?//?still?prints?just?"hello:?world"

          ??fmt.Printf("%+v\n",?err)?//?verbose?mode
          }

          將打?。?/p>

          hello:?world
          (1)?attached?stack?trace
          ??--?stack?trace:
          ??|?main.bar
          ??|?????/home/kena/src/errors-tests/test.go:10
          ??|?[...repeated?from?below...]
          Wraps:?(2)?hello
          Wraps:?(3)?attached?stack?trace
          ??--?stack?trace:
          ??|?main.foo
          ??|?????/home/kena/src/errors-tests/test.go:9
          ??|?main.baz
          ??|?????/home/kena/src/errors-tests/test.go:11
          ??|?main.main
          ??|?????/home/kena/src/errors-tests/test.go:14
          ??|?runtime.main
          ??|?????/usr/lib/go-1.14/src/runtime/proc.go:203
          ??|?runtime.goexit
          ??|?????/usr/lib/go-1.14/src/runtime/asm_amd64.s:1373
          Wraps:?(4)?world
          Error?types:?(1)?*withstack.withStack?(2)?*errutil.withPrefix?(3)?*withstack.withStack?(4)?*errutil.leafError

          和以前一樣,.Error() 的結(jié)果顯示在第一行。然后,打印出最外層的堆棧跟蹤(errors.Wrap() 的結(jié)果)。這表明錯(cuò)誤被包裹在第 10 行,但調(diào)用跟蹤與下面顯示的一樣。

          然后,詳細(xì)顯示將顯示內(nèi)部錯(cuò)誤,并顯示消息 world 及其自身的堆棧跟蹤。此內(nèi)部堆棧跟蹤顯示內(nèi)部錯(cuò)誤是在第 9 行生成的。

          錯(cuò)誤包裝工具用途廣泛:可以使用類似于 printf 的格式來編寫消息前綴。這是完整的 API:

          package?github.com/cockroachdb/errors

          //?Wrap?adds?a?message?prefix?and?also?attaches?an?additional?stack?trace.
          //?If?the?first?argument?is?nil,?it?returns?nil.
          func?Wrap(err?error,?msg?string)?error

          //?Wrap?adds?a?message?prefix?composed?using?printf-like?formatting,
          //?and?also?attaches?an?additional?stack?trace.
          //?If?the?first?argument?is?nil,?it?returns?nil.
          func?Wrapf(err?error,?format?string,?args?...interface{})?error

          此外,為了兼容 Go 1.13 的 fmt.Errorf(),上面看到的 errors.Newf() 和 errors.Errorf() 函數(shù),它們還能識(shí)別出格式化動(dòng)詞 %w,從而觸發(fā) wrap 邏輯。

          例如:

          //?The?following?is?similar?to?errors.Wrapf(err,?"hello").
          //?However,?it?does?not?return?nil?if?err?is?nil!
          err?=?errors.Newf("hello:?%w",?err)

          請(qǐng)注意,只有 Newf()/Errorf() 可以識(shí)別 %w:errors.Wrap() 無法識(shí)別。

          提示:應(yīng)該優(yōu)先使用 errors.Wrap() 代替特殊動(dòng)詞 %w:因?yàn)樗鼤?huì)正確忽略作為輸入給出的 nil 錯(cuò)誤。

          次要錯(cuò)誤注解

          每個(gè)中級(jí) Go 程序員都會(huì)迅速陷入這一痛苦的境地:如果在處理錯(cuò)誤時(shí)遇到錯(cuò)誤,該怎么辦?

          一個(gè)常見的示例是在處理文件時(shí)遇到錯(cuò)誤后清理文件系統(tǒng):

          func?writeConfig(out?string,?cfgA,?cfgB?Config)?(resErr?error)?{
          ????//?Create?the?destination?directory.
          ????if?err?:=?os.MkDir(out);?err?!=?nil?{
          ???????return?err
          ????}
          ????defer?func()?{
          ???????//?If?an?error?is?encountered?below,?remove
          ???????//?the?destination?directory?upon?exit.
          ???????if?resErr?!=?nil?{
          ??????????if?dirErr?:=?os.RemoveAll(out);?dirErr?!=?nil?{
          ?????????????//?now...?what?
          ?????????????...
          ?????????}
          ???????}
          ????}()

          ????if?err?:=?writeCfg(out,?cfgA,?"a.json",?"config?A");?err?!=?nil?{
          ??????return?err
          ????}
          ????return?writeCfg(out,?cfgB,?"b.json",?"config?B")
          }

          func?writeCfg(outDir?path,?cfg?Config,?filename,?desc?string)?error?{
          ????j,?err?:=?json.Marshal(cfg)
          ????if?err?!=?nil?{
          ???????return?errors.Wrapf(err,?"marshaling?%s",?desc)
          ????}
          ????return?ioutil.WriteFile(filepath.Join(out,?filename),?j,?0777)
          }

          本示例中的函數(shù)創(chuàng)建一個(gè)輸出目錄,以將兩個(gè)配置對(duì)象寫入其中。但是,在寫入某些配置對(duì)象時(shí)可能會(huì)發(fā)生錯(cuò)誤。在這種情況下,該函數(shù)希望通過刪除剛剛創(chuàng)建的目錄來對(duì)其進(jìn)行清理。

          如果在目錄刪除過程中發(fā)生錯(cuò)誤,該怎么辦?應(yīng)該返回哪個(gè)錯(cuò)誤?

          • 如果返回原始錯(cuò)誤,我們將看不到目錄刪除錯(cuò)誤。
          • 如果返回目錄刪除錯(cuò)誤,我們將看不到文件生成錯(cuò)誤。

          我們希望以某種方式返回有關(guān)這兩個(gè)錯(cuò)誤的詳細(xì)信息,以幫助進(jìn)行故障排除。同時(shí),出于原因分析的目的,我們要謹(jǐn)慎地將遇到的第一個(gè)錯(cuò)誤保留為“主要”錯(cuò)誤。

          我們可以通過如下調(diào)整代碼來實(shí)現(xiàn):

          defer?func()?{
          ???//?If?an?error?is?encountered?below,?remove
          ???//?the?destination?directory?upon?exit.
          ???if?resErr?!=?nil?{
          ??????if?dirErr?:=?os.RemoveAll(out);?dirErr?!=?nil?{
          ?????????//?This?attaches?dirErr?as?an?ancillary?error
          ?????????//?to?the?error?object?that?was?already?stored?in?resErr.
          ?????????resErr?=?errors.WithSecondaryError(resErr,?dirErr)
          ?????}
          ???}
          }()

          通過這種編程模式,我們可以確信,在處理另一個(gè)錯(cuò)誤時(shí),我們可以保留遇到錯(cuò)誤時(shí)所發(fā)生事件的全部情況。

          次要錯(cuò)誤注解不會(huì)影響主要錯(cuò)誤上 .Error() 返回的文本。從相關(guān)代碼以及標(biāo)準(zhǔn) API error.Is() 的角度來看,代碼的行為就像僅發(fā)生了主要錯(cuò)誤一樣。

          但是,在詳細(xì)打印過程中會(huì)發(fā)現(xiàn)第二個(gè)錯(cuò)誤。例如:

          package?main

          import?(
          ???"fmt"
          ???"github.com/cockroachdb/errors"
          )

          func?main()?{
          ??err?:=?errors.New("hello")
          ??err?=?errors.WithSecondaryError(err,?errors.New("friend"))
          ??fmt.Println(err)?//?prints?just?"hello"

          ??fmt.Printf("%+v\n",?err)?//?verbose?mode
          }

          打?。?/p>

          hello
          (1)?secondary?error?attachment
          ??|?friend
          ??|?(1)?attached?stack?trace
          ??|???--?stack?trace:
          ??|???|?main.main
          ??|???|?????????/home/kena/src/errors-tests/test.go:11
          ??|???|?runtime.main
          ??|???|?????????/usr/lib/go-1.14/src/runtime/proc.go:203
          ??|???|?runtime.goexit
          ??|???|?????????/usr/lib/go-1.14/src/runtime/asm_amd64.s:1373
          ??|?Wraps:?(2)?friend
          ??|?Error?types:?(1)?*withstack.withStack?(2)?*errutil.leafError
          Wraps:?(2)?attached?stack?trace
          ??--?stack?trace:
          ??|?main.main
          ??|?????/home/kena/src/errors-tests/test.go:10
          ??|?runtime.main
          ??|?????/usr/lib/go-1.14/src/runtime/proc.go:203
          ??|?runtime.goexit
          ??|?????/usr/lib/go-1.14/src/runtime/asm_amd64.s:1373
          Wraps:?(3)?hello
          Error?types:?(1)?*secondary.withSecondaryError?(2)?*withstack.withStack?(3)?*errutil.leafError

          像以前一樣,我們?cè)诘谝恍锌吹?.Error() 的文本。然后,我們看到附加的次要錯(cuò)誤的詳細(xì)打印輸出,相對(duì)于主要錯(cuò)誤向右縮進(jìn)。次要錯(cuò)誤自己的 .Error()friend,首先打印它,然后打印次要錯(cuò)誤的嵌入式堆棧跟蹤。

          然后,打印輸出繼續(xù),不縮進(jìn)地顯示主要錯(cuò)誤的堆棧跟蹤。

          API 概覽:

          package?github.com/cockroachdb/errors

          //?WithSecondaryError?attaches?secondary?as?an?annotation
          //?to?the?primary?error.?If?primary?is?nil,?nil?is?returned.
          func?WithSecondaryError(primary?error,?secondary?error)?error

          //?CombineErrors?attaches?err2?to?err1?as?secondary?error
          //?if?both?err1?and?err2?are?not?nil.?If?err1?is?nil,?err2
          //?is?returned?instead.
          func?CombineErrors(err1,?err2?error)?errors

          子任務(wù)更智能的錯(cuò)誤處理

          擴(kuò)展包 errgroup[3] 提供了一個(gè)可重復(fù)使用的庫(kù),用于“為處理共同任務(wù)的子任務(wù)的 goroutine 組進(jìn)行同步,錯(cuò)誤傳播和上下文取消”。

          它的實(shí)現(xiàn)可以在這里找到:https://github.com/golang/sync/blob/master/errgroup/errgroup.go。

          在較高的級(jí)別上,它使用 sync.WaitGroup 運(yùn)行多個(gè) goroutine,并在末尾添加一個(gè)屏障。此外,一旦它們中的任何一個(gè)因錯(cuò)誤終止,它將取消該組中的所有其他 goroutine。

          邏輯問題在于,如果兩個(gè)或多個(gè) goroutine 因錯(cuò)誤而失敗,則僅報(bào)告第一個(gè)錯(cuò)誤。其他錯(cuò)誤是“被遺忘的”:

          func?(g?*Group)?Go(f?func()?error)?{
          ?????g.wg.Add(1)

          ?????go?func()?{
          ?????????????defer?g.wg.Done()

          ?????????????if?err?:=?f();?err?!=?nil?{
          ?????????????????????//?errOnce.Do?executes?its?argument?just?once.?The?second?time?an
          ?????????????????????//?error?is?encountered,?it?is?simply?forgotten?altogether!?Not?nice.
          ?????????????????????g.errOnce.Do(func()?{
          ?????????????????????????????g.err?=?err
          ?????????????????????????????if?g.cancel?!=?nil?{
          ?????????????????????????????????????g.cancel()
          ?????????????????????????????}
          ?????????????????????})
          ?????????????}
          ?????}()
          }

          我們可以按以下方式解決此問題:

          type?Group?struct?{
          ?????...
          ?????errOnce?sync.Once
          ?????mu?{
          ????????sync.Mutex?//?makes?.err?race-free.
          ????????err?????error
          ?????}
          }

          func?(g?*Group)?Wait()?error?{
          ???...
          ???return?g.mu.err
          }

          func?(g?*Group)?Go(f?func()?error)?{
          ?????...
          ?????go?func()?{
          ?????????????...
          ?????????????if?err?:=?f();?!errors.Is(err,?context.Canceled)?{
          ???????????????????g.mu.Lock()
          ???????????????????defer?g.mu.Unlock()
          ???????????????????g.mu.err?=?errors.CombineErrors(g.mu.err,?err)
          ?????????????}
          ?????}()
          }

          使用 errgroup.Group 的此備用版本,如果子任務(wù)中有兩個(gè)或多個(gè)錯(cuò)誤,則第一個(gè)將成為“主要”錯(cuò)誤,而第一個(gè)之后的所有其他錯(cuò)誤將作為輔助錯(cuò)誤注解附加。

          該代碼還使用 errors.Is(err, context.Canceled) 來排除由組調(diào)用共享上下文的 cancel() 函數(shù)而產(chǎn)生的錯(cuò)誤對(duì)象,這些對(duì)象只是噪音,可能在故障排除期間沒有用。

          檢查錯(cuò)誤的身份

          在最常見的情況下,錯(cuò)誤會(huì)傳播,最終通過網(wǎng)絡(luò)連接返回,或打印到日志文件。

          但是,有時(shí)代碼需要檢查錯(cuò)誤對(duì)象以決定其他行為。

          為此,庫(kù)可以定義一些特定的函數(shù)來處理這種情況。例如:

          package?os

          //?IsExist?returns?a?boolean?indicating?whether?the?error?is?known?to?report
          //?that?a?file?or?directory?already?exists.?It?is?satisfied?by?ErrExist?as
          //?well?as?some?syscall?errors.
          func?IsExist(err?error)?bool

          可以這樣使用:

          func?ensureDirectoryExists(path?string)?error?{
          ????if?err?:=?os.Mkdir(path);?err?!=?nil?{
          ???????if?os.IsExist(err)?{
          ?????????//?The?directory?already?exists.?This?is?OK,
          ?????????//?no?need?to?report?an?error.
          ?????????err?=?nil
          ???????}
          ???????return?err
          ????}
          ????fmt.Println("directory?created")
          }

          此函數(shù)嘗試創(chuàng)建目錄。如果已經(jīng)存在,它將不執(zhí)行任何操作。如果遇到另一個(gè)錯(cuò)誤(例如磁盤損壞等),則會(huì)報(bào)告該錯(cuò)誤。

          另一種技術(shù)是使用“前哨”錯(cuò)誤,并將返回的錯(cuò)誤對(duì)象與那些標(biāo)記進(jìn)行比較以檢測(cè)特定情況。

          我們看到了上面帶有 error.Is(err, context.Canceled) 的示例。這是來自 SQL 客戶端程序的另一個(gè)示例:

          func?(c?*sqlConn)?Query(query?string,?args?[]driver.Value)?(*sqlRows,?error)?{
          ?????if?err?:=?c.ensureConn();?err?!=?nil?{
          ?????????????return?nil,?err
          ?????}
          ?????rows,?err?:=?c.conn.Query(query,?args)
          ?????if?errors.Is(err,?driver.ErrBadConn)?{
          ?????????????//?If?the?connection?has?been?closed?by?the?server?or
          ?????????????//?there?was?some?other?kind?of?network?error,?close
          ?????????????//?the?connection?on?our?side?so?that?the?call?to
          ?????????????//?ensureConn()?above?establishes?a?new?connection
          ?????????????//?during?the?next?query.
          ?????????????c.Close()
          ?????????????c.reconnecting?=?true
          ?????}
          ?????if?err?!=?nil?{
          ?????????????return?nil,?err
          ?????}
          ?????return?&sqlRows{rows:?rows.(sqlRowsI),?conn:?c},?nil
          }

          此代碼檢測(cè) SQL 驅(qū)動(dòng)程序何時(shí)返回 driver.ErrBadConn 并在這種情況下選擇特殊行為。任何其他錯(cuò)誤均按原樣返回,并導(dǎo)致程序在此函數(shù)的調(diào)用程序中的某處停止。

          errors.is() 可以通過重復(fù)調(diào)用 ?Unwrap() 方法來檢測(cè)整個(gè)錯(cuò)誤的直接因果鏈中的前哨錯(cuò)誤。因此,將忽略“在途中”發(fā)現(xiàn)的任何次要錯(cuò)誤注解。這種行為是有意設(shè)計(jì)的:類似樹的行為將使人們難以推理出錯(cuò)誤是另一個(gè)“原因”的含義。還會(huì)在其他 API errors.As() 錯(cuò)誤中引發(fā)有關(guān)遍歷順序的難題。就個(gè)人而言,經(jīng)驗(yàn)還沒有向我表明,除線性因果鏈之外,其他任何東西在實(shí)踐中有用。

          和 pkg/errors 的不同

          自 2016 年以來,事實(shí)上是 Go 的錯(cuò)誤包的標(biāo)準(zhǔn)替代品是 Dave Cheney 的 pkg/errors 庫(kù),該庫(kù)位于 https://github.com/pkg/errors。

          該包最初引入了鏈接列表錯(cuò)誤對(duì)象的概念,自動(dòng)包裝錯(cuò)誤并添加堆棧跟蹤以在故障排除期間提供更多上下文。

          不幸的是,Go 1.13 的發(fā)布使 pkg/errors 過時(shí)了:Dave Cheney 定義了自己的庫(kù),使用一種名為 Cause() 的方法來提取錯(cuò)誤鏈的線性原因。當(dāng) Go 1.13 采納用鏈接列表出錯(cuò)的想法時(shí),它定義了另一個(gè)方法 Unwrap() 來提取原因。因此,Go 的errors.Is()和其他 API 無法理解源自 pkg/errors 的錯(cuò)誤。

          此外,來自 pkg/errors 的對(duì)象將嚴(yán)重遭受 Go Error 打印災(zāi)難,因此該庫(kù)使自定義錯(cuò)誤類型的實(shí)現(xiàn)非常困難。

          CockroachDB 錯(cuò)誤庫(kù)接管了 pkg/errors:它采用 Go 1.13 約定,提供了 Go 1.13 標(biāo)準(zhǔn) API 的直接替代品,并避免了 Go 錯(cuò)誤打印災(zāi)難。它還實(shí)現(xiàn)了大多數(shù) pkg/errors 接口,因此可以用作以前使用 Dave Cheney 庫(kù)的程序的直接替代。

          總結(jié)

          Go 庫(kù)通過 Go 自己的錯(cuò)誤包中的 fmt.Errorf() 和 errors.New() 提供了錯(cuò)誤接口的簡(jiǎn)化實(shí)現(xiàn)。

          改用 CockroachDB 錯(cuò)誤庫(kù),代替 Go 的錯(cuò)誤包和 Dave Cheney的 pkg/errors,可以獲得更好的體驗(yàn)。

          它的錯(cuò)誤構(gòu)造函數(shù) errors.New()/errors.Newf()(別名為 errors.Errorf())自動(dòng)在錯(cuò)誤對(duì)象中包含堆棧跟蹤,可以使用 fmt.Printf("%+v" ,err) 打印堆棧追蹤。

          它還提供了錯(cuò)誤包裝器的詞匯表。最常見的是帶有 errors.Wrap()/errors.Wrapf() 的消息前綴注釋,用于注釋從多個(gè)位置調(diào)用的函數(shù)的調(diào)用路徑。這還包括幕后的堆棧跟蹤。

          另一個(gè)常見的包裝器解決了在處理另一個(gè)錯(cuò)誤時(shí)遇到錯(cuò)誤時(shí)如何在 Go 中執(zhí)行的令人困惑的問題:使用輔助原因注解,并使用 errors.WithSecondaryCause() 或 errors.CombineErrors() 附加,Go 代碼可以保留兩個(gè)錯(cuò)誤,因此程序員在故障排除期間可以同時(shí)看到兩者。

          CockroachDB 錯(cuò)誤庫(kù)中的錯(cuò)誤還提供了一致的行為,并且在詳細(xì)格式化錯(cuò)誤時(shí)提供了有用的顯示結(jié)構(gòu),從而避免了巨大的 Go 錯(cuò)誤打印災(zāi)難。我們將在本系列的后續(xù)文章中專門探討實(shí)現(xiàn)自定義錯(cuò)誤,以進(jìn)一步探討該主題。

          參考資料

          [1]

          Dave Cheney 的錯(cuò)誤庫(kù): https://github.com/pkg/errors

          [2]

          CockroachDB 錯(cuò)誤庫(kù): https://github.com/cockroachdb/errors

          [3]

          errgroup: https://godoc.org/golang.org/x/sync/errgroup



          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù) ebook 獲?。贿€可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 73
          點(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>
                  久久综合新金瓶梅一级黄大片 | 久草中文在线视频 | 五月天色婷婷丁香 | 日本精品在线视频 | 看看欧美黑人操逼免费的 |