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")
}
相关文章
相关标签/搜索