Golang 如何使用正確使用error?


有很多種方法來聲明 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(...)....}充斥在代碼中

    一、可讀性較差

    二、大量重復日志,給運維帶來壓力

  三、很難一次定位錯誤位置

    可以通過 "pkg/errors"包中提供的 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM