Home

Golang错误处理

本文是GopherCon 2016的演讲Dont Just Check Errors Handle Them Gracefully的笔记,详情可点击链接观看(请带梯子上油管)

常见错误处理策略

Sentiel errors

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

Error Types

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 errors

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的一部分。

Don’t just check errors, handle then greacefully

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也很简单

errors.New

err := errors.New("kerboom")
fmt.Printf("%v\n", err)

使用errors.New新建的err打印时会展示出调用堆栈

errors.Wrap

err := errors.Warp(
			syscall.EBADF, "couldn't write to stream")
fmt.Printf("%v\n", err)

// 输出			
// couldn't write to stream: bad file descriptor

errors.Cause

有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代码也就几百行,还是很不错的,值得推荐。

以上