本文是GopherCon 2016的演讲Dont Just Check Errors Handle Them Gracefully的笔记,详情可点击链接观看(请带梯子上油管)
if err == ErrSomething { ... }
这是最常见的处理方法,通过判断错误类型来做相应的处理。常见的有
io.EOF
syscall.ENOENT
go/build.NoGoError
path/filepath.SkipDir
但这种处理方法是最不灵活的一种方法,因为我们必须知道确定的错误类型。如果我们需要使用自定义的一些错误类型,当我们的代码作为一个package导出时,错误变成了api的一部分,这样就有了强耦合,以后要是修改错误会很麻烦。
另外,当我们使用fmt.Errorf
的时候,我们需要通过判断错误string的内容来区分错误,如:
func readfile(path string) error {
err := openfile(path)
if err != nil {
return fmt.Errorf("cannot open file: %v", err)
}
...
}
func main() {
err := readfile(".bash")
if strings.Contains(err.Error(), "not found") {
// handle error
}
}
这是种非常糟糕的做法,当错误信息改变时,对应的错误处理也要相应改变, 另外还要性能的问题。
除非是标准库里面的函数,否则避免使用Sentiel errors
if err, ok := err.(SomeType); ok { ... }
配合Type assertion使用
err := somethint()
switch err := err.(type) {
case nil:
// call succeed
case *os.PathError:
// handle PathError
default:
// handle Unknow error
}
这个其实没什么好讲的,同样的,Error Types也会造成包之间的强耦合,除非是在包内部使用,否则不推荐使用
Opaque是不透明的意思,这个是讲者自己的叫法,具体的思想就是:
Assert errors for behaviour, not type
通过行为来判断错误而不是类型
说起行为,很自然的就联想到了interface,对的,不使用type,使用interface来处理。
type temporary interface {
Tepporary() bool
}
func IsTemporary(err error) bool {
te, ok := err.(temporary)
return ok && te.Temporary()
}
这种方法的耦合度更低一些,不过无法完全避免依赖度,毕竟interface也是api的一部分。
if err != nil {
return err
}
这应该最常见的错误处理,不过想想当一个错误经过层层的return,当到达最外面的log记录了下来。但当你debug查找日志时只看到一句
No such file or directory
心里会不会万匹草泥马奔腾(╯‵□′)╯︵┴─┴
是的,这种方法坏处就是破坏了错误发生的上线文环境,debug起来就很困难。
当然我们使用fmt.Errorf
为错误加上一些信息
func AuthenticatRequest(r *Request) error {
err := authenticate(r.User)
if err != nil {
return fmt.Errorf("authenticat failed: %v", err)
}
return nil
}
为了更优雅一点,讲者提供了一个包github.com/pkg/errors
做这个事情。
这个包的api也很简单
err := errors.New("kerboom")
fmt.Printf("%v\n", err)
使用errors.New
新建的err打印时会展示出调用堆栈
err := errors.Warp(
syscall.EBADF, "couldn't write to stream")
fmt.Printf("%v\n", err)
// 输出
// couldn't write to stream: bad file descriptor
有Wrap方法那也会有UnWrap的方法,这就是errors.Cause
errors.Cause(err) // 返回Wrap之前的error
前面的例子需要改成这样
type temporary interface {
Tepporary() bool
}
func IsTemporary(err error) bool {
te, ok := err.Cause(err).(temporary)
return ok && te.Temporary()
}
github.com/pkg/errors
代码也就几百行,还是很不错的,值得推荐。
以上