golang優雅的錯誤處理

golang的錯誤處理一直深受你們詬病,項目裏面一半的代碼在作錯誤處理。git

本身在作golang開發一段時間後,也深有同感,以爲頗有必要優化一下,一方面讓代碼更優雅一些,另外一方面也爲了造成系統的錯誤處理方式,而不是爲所欲爲的來個errors.new(),或者一直return err。github

在查閱一些資料以後,發現本身對golang錯誤處理的認識,還停留在一個低階的層面上。這裏想和你們探討一下,也爲鞏固本身所學golang

錯誤的返回處理

在函數多層調用時,我經常使用的處理方式是:bash

func Write(w io.Writer, buf []byte) error {
  _, err := w.Write(buf)
  if err != nil {
    // annotated error goes to log file
    log.Println("unable to write:", err)
  
    // unannotated error returned to caller
    return err
  }
  return nil
}

層層都加日誌很是方便故障定位,但這樣作,日誌文件中會有不少重複的錯誤描述,而且上層調用函數拿到的錯誤,仍是底層函數返回的 error,沒有上下文信息函數

優化一:

func Write(w io.Writer, buf []byte) error {
  _, err := w.Write(buf)
  if err != nil {
    // annotated error returned to caller
    fmt.Errorf("authenticate failed: %v", err)
  }
  return nil
}

這裏去除了重複的錯誤日誌,而且在返回給上層調用函數的error信息中加上了上下文信息。可是這樣作破壞了相等性檢測,即咱們沒法判斷錯誤是不是一種預先定義好的錯誤。優化

例如:debug

func main() {
    err := readfile(「.bashrc」)
    if strings.Contains(error.Error(), "not found") {
        // handle error
    }
}

func readfile(path string) error {
    err := openfile(path)
    if err != nil {
        return fmt.Errorf(「cannot open file: %v", err)
    }
    // ……
}

形成的後果時,調用者不得不用字符串匹配的方式判斷底層函數 readfile 是否是出現了某種錯誤。指針

優化二:

使用第三方庫: github.com/pkg/errors,wrap能夠將一個錯誤加上一段字符串,包裝成新的字符串。cause進行相反的操做。日誌

// Wrap annotates cause with a message.
func Wrap(cause error, message string) error
// Cause unwraps an annotated error.
func Cause(err error) error

例如:code

func ReadFile(path string) ([]byte, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, errors.Wrap(err, "open failed")
    }
    defer f.Close()
    
    buf, err := ioutil.ReadAll(f)
    if err != nil {
        return nil, errors.Wrap(err, "read failed")
    }
    return buf, nil
}

經過wrap便可以包含底層被調用函數的上下文信息,又能夠經過cause還原錯誤,對原錯誤類型進行判斷,以下:

func main() {
    _, err := ReadFile()
    if errors.Cause(err) != io.EOF {
        fmt.Println(err)
        os.Exit(1)
    }
}

今年剛發佈的go1.13新增了相似的錯誤處理函數

//go1.13 沒有提供wrap函數,但經過fmt.Errof提供了相似的功能
fmt.Errorf("context info: %w",err)

//將嵌套的 error 解析出來,多層嵌套須要調用 Unwrap 函數屢次,才能獲取最裏層的 error
func Unwrap(err error) error

異常

部分開發者寫代碼中,沒有區分異常和錯誤,都統一按錯誤來處理,這種方式是不優雅的。要靈活使用Golang的內置函數panic和recover來觸發和終止異常處理流程。

錯誤指的是可能出現問題的地方出現了問題,好比打開一個文件時失敗,這種狀況在人們的意料之中 ;而異常指的是不該該出現問題的地方出現了問題,好比引用了空指針,這種狀況在人們的意料以外。

這裏給出一些應拋出異常的場景:

  1. 空指針引用
  2. 下標越界
  3. 除數爲0
  4. 不該該出現的分支,好比default
  5. 輸入不該該引發函數錯誤

在應用開發過程當中,經過拋出panic異常,程序退出,及時發現問題。在部署之後,要保證程序的持續穩定運行,須要及時經過recover捕獲異常。在recover中,要用合理的方式處理異常,如:

  1. 打印堆棧的調用信息和業務信息,方便記錄和排查問題
  2. 將異常轉換爲錯誤,返回給上層調用者處理

例如:

func funcA() (err error) {
    defer func() {
        if p := recover(); p != nil {
            fmt.Println("panic recover! p:", p)
            str, ok := p.(string)
            if ok {
                err = errors.New(str)
            } else {
                err = errors.New("panic")
            }
            debug.PrintStack()
        }
    }()
    return funcB()
}

func funcB() error {
    // simulation
    panic("foo")
    return errors.New("success")
}
相關文章
相關標籤/搜索