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來觸發和終止異常處理流程。
錯誤指的是可能出現問題的地方出現了問題,好比打開一個文件時失敗,這種狀況在人們的意料之中 ;而異常指的是不該該出現問題的地方出現了問題,好比引用了空指針,這種狀況在人們的意料以外。
這裏給出一些應拋出異常的場景:
在應用開發過程當中,經過拋出panic異常,程序退出,及時發現問題。在部署之後,要保證程序的持續穩定運行,須要及時經過recover捕獲異常。在recover中,要用合理的方式處理異常,如:
例如:
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") }