除了 fmt.Errorf() 之外—Go 中的日常錯(cuò)誤對(duì)象:CockroachDB errors 庫(kù)(第4篇)
在 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)一步探討該主題。
參考資料
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
推薦閱讀
