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") }