有很多種方法來聲明 errors:
errors.New
聲明簡單的靜態字符串錯誤信息fmt.Errorf
聲明格式化的字符串錯誤信息- 為自定義類型實現
Error()
方法 - 通過
"pkg/errors".Wrap
包裝錯誤類型
1.如何自定義錯誤類型?
客戶需要檢測並處理此錯誤嗎?如果是,那應該自定義類型,並實現 Error()
方法。
type errNotFound struct { } func (fe *errNotFound) Error() string { return "找不到文件" } //模擬錯誤 func openFile() ([]byte, error) { return nil, &errNotFound{} } func main() { _, err := openFile() if err != nil { fmt.Println(err) } } # 找不到文件
當你需要捕獲一些錯誤的具體信息,往往也需要通過自定義的方式,比如下面這個例子捕獲哪個文件打開錯誤了
type errNotFound struct { file string } func (e errNotFound) Error() string { return fmt.Sprintf("file %q not found", e.file) } func open(file string) error { return errNotFound{file: file} } func use() { if err := open("test.txt"); err != nil { if err, ok := err.(errNotFound); ok { fmt.Println(err) } else { panic("unknown error") } } } func main() { use() } # file "test.txt" not found
直接將自定義的錯誤類型設為導出需要特別小心,因為這意味着他們已經成為包的公開 API 的一部分了。更好的方式是暴露一個匹配函數來檢測錯誤。
type errNotFound struct { file string } func (e errNotFound) Error() string { return fmt.Sprintf("file %q not found", e.file) } func IsNotFoundError(err error) bool { _, ok := err.(errNotFound) return ok } func Open(file string) error { return errNotFound{file: file} } // package bar if err := foo.Open("foo"); err != nil { if foo.IsNotFoundError(err) { // handle } else { panic("unknown error") } }
2.當你需要定義一個簡單的錯誤信息,可以用errors.New也可以用fmt.Errorf
如果項目中僅僅是為了打日志,就可以直接用了,如果項目中需要捕獲具體的錯誤並進行對應的處理,規范的做法是先聲明這個錯誤類型
比較優秀的做法是這樣做,切記不要通過Error()判斷字符串這種方式去識別錯誤
var ErrCouldNotOpen = errors.New("could not open") func Open() error { return ErrCouldNotOpen } if err := foo.Open(); err != nil { if err == ErrCouldNotOpen { // handle } else { panic("unknown error") } }
errors.New本質上也可以通過自定義模擬實現
func New(text string) error { return &errorString{text} } type errorString struct { s string } func (e *errorString) Error() string { return e.s } func test() error { return New("測試NewError") } func main() { err := test() fmt.Printf("%v", err) } # 測試NewError
3. 實際項目中,大量的if err != nil { log.Error(...)....}充斥在代碼中
一、可讀性較差
二、大量重復日志,給運維帶來壓力
三、很難一次定位錯誤位置
可以通過 "
p
kg/e
rrors
"包中提供的 Wrap、WithMessage、Cause等方法解決這個問題
Wrap 可以包裝信息加堆棧信息、WithMessage只包裝信息、Cause可以找到原始錯誤
func inner() error { return errors.Wrap(sql.ErrNoRows, "inner failed") } func out() error { return errors.WithMessage(inner(), "out failed") } func main() { err := out() if err != nil { fmt.Printf("%+v\n", err) } # sql: no rows in result set # inner failed # ...test.go:11 # ...test.go:14 # ...test.go:17 # ... # out failed if errors.Cause(err) == sql.ErrNoRows { # true } }
參考:
https://qcrao.com/2019/09/18/golang-error-break-through/
https://zhuanlan.zhihu.com/p/98152645