本文内容纲要:

- 错误是值 Errors are values
错误是值 Errors are values

Rob Pike
12 January 2015

在程序员中,尤其是go新手,经常听到的一个讨论话题是:如何处理错误。当下面这段代码出现次数过多时,这个话题大多数时候都会变成对go的悲叹。

if err != nil {
    return err
}
if err !=nil
if err !=niltry-catchtry-catchif err != nil

无论这个解释再怎么好,很明显的一点就是go程序员忽略了一个关于错误的概念:错误是值(Errors are value)。

我们可以对值进行编程,并且因为错误是值,所以我们也可以对错误进行编程。

原文:Values can be programmed, and since errors are values, errors can be programmed.

if
bufioScannerScanscanscan
scanner := bufio.NewScanner(input)
for scanner.Scan() {
    token := scanner.Text()
    // process token
}
if err := scanner.Err(); err != nil {
    // process the error
}
Scan
func (s *Scanner) Scan() (token []byte, error)

用户的代码样例如下:

scanner := bufio.NewScanner(input)
for {
    token, err := scanner.Scan()
    if err != nil {
        return err // or maybe break
    }
    // process token
}
Scannertoken
scanfalseErrif err != nil

值得强调的是,无论使用哪种设计,最至关重要的是,无论错误如何暴露出来,都要去检查处理它。我们在这的讨论,不是讨论如何避免检查错误,而是如何使用go更优雅地处理错误。

我在东京参加2014年秋季GoCon大会时,反复出现了错误检查代码的话题。一个热情的 gopher,@jxck,反映了他对错误处理的抱怨,他有一些像这样的代码:

_, err = fd.Write(p0[a:b])
if err != nil {
    return err
}
_, err = fd.Write(p1[c:d])
if err != nil {
    return err
}
_, err = fd.Write(p2[e:f])
if err != nil {
    return err
}
// and so on

这出现了过多重复的代码。在真实的编码过程中,它更长些,并且有更多情况要处理,因此用一个辅助函数去重构它并不容易,但是在理想环境中,定义一个函数来处理错误变量或许能帮助我们

var err error
write := func(buf []byte) {
    if err != nil {
        return
    }
    _, err = w.Write(buf)
}
write(p0[a:b])
write(p1[c:d])
write(p2[e:f])
// and so on
if err != nil {
    return err
}

这个方式看起来不错,但是在执行写操作的时候,每个函数都需要闭包操作,每次单独的辅助函数调用的时候都需要维护一个共同变量。

Scan
errWriter
type errWriter struct {
    w   io.Writer
    err error
}
writeWrite方法Write
func (ew *errWriter) write(buf []byte) {
    if ew.err != nil {
        return
    }
    _, ew.err = ew.w.Write(buf)
}
write
errWriterwrite
ew := &errWriter{w: fd}
ew.write(p0[a:b])
ew.write(p1[c:d])
ew.write(p2[e:f])
// and so on
if ew.err != nil {
    return ew.err
}

相比比起闭包的方法,这种写法更为清晰且透明。再也不杂乱了,使用错误值编程,可以使得代码更漂亮。

errWriter
errWriter
archive/zipnet/httpbufioWritererrWriterbufio.Writer.Writeio.WriterWritererrWriter.writeFlush
b := bufio.NewWriter(fd)
b.Write(p0[a:b])
b.Write(p1[c:d])
b.Write(p2[e:f])
// and so on
if b.Flush() != nil {
    return b.Flush()
}

至少对于某些应用程序,此方法存在一个明显的缺点:无法知道发生错误之前已完成多少处理。如果该信息很重要,则必须采用更细粒度的方法。但是,通常,最后进行全有或全无的检查就足够了。

我们只研究了一种避免重复错误处理代码的技术。请记住,使用 errWriter 或 bufio.Writer 并不是简化错误处理的唯一方法,并且这种方法并不适合所有情况。但是,关键的教训是,错误是值,而 Go 编程语言的全部功能可用于处理它们。

使用该语言简化错误处理。

但是请记住:无论做什么,都要检查错误!

本文内容总结:错误是值 Errors are values,