More terse error handling in Go

2019-02-19 20:45发布

问题:

How do I handle a lot of errors in Go?

I look at my code and find that it is full of error handlers:

err = result.Scan(&bot.BID, &bot.LANGUAGE, &bot.SOURCE)
if err != nil {
    log.Fatalf("result.Scan: %v", err)
    return
}

fileName, err := copySourceToTemporaryFile(bot)
if err != nil {
    log.Fatalf("copySourceToTemporaryFile: %v", err)
    return
}
...

And a lot of lines look like:

// do something
// handle error
// handle error
// handle error

// do something 2
// handle error
// handle error
// handle error

Can I create a default handler that prints an error and stops processing, or at least move out this "error-handler-garbage" out of my code logic?

回答1:

That reminds me of the recent Errors are values by Rob Pike, as well as Mr. Rob Pike taught me about practice of error handling in Go at GoCon 2014

The key lesson, however, is that errors are values and the full power of the Go programming language is available for processing them.

It's worth stressing that whatever the design, it's critical that the program check the errors however they are exposed. The discussion here is not about how to avoid checking errors, it's about using the language to handle errors with grace.

One technique was to define an object called an errWriter:

type errWriter struct {
    w   io.Writer
    err error
}

The write method calls the Write method of the underlying Writer and records the first error for future reference:

func (ew *errWriter) write(buf []byte) {
    if ew.err != nil {
        return
    }
    _, ew.err = ew.w.Write(buf)
}

As soon as an error occurs, the write method becomes a no-op but the error value is saved.

Given the errWriter type and its write method, the code above can be refactored:

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
}


回答2:

If the error is "real", you should (have to) handle it if you don't want unexpected panics at runtime.

To supplement VonC's answer about the errWriter technique, there are more cases where you can reduce error handling code:

These are the cases when you know that even though a function or method may return an error, it will not (e.g. you're supplying the parameters from source code which you know will work). In these cases you (or the author of the library) can provide helper functions (or methods) which do not return the error but raise a runtime panic if it still occurs.

Great examples of these are the template and regexp packages: if you provide a valid template or regexp at compile time, you can be sure they can always be parsed without errors at runtime. For this reason the template package provides the Must(t *Template, err error) *Template function and the regexp package provides the MustCompile(str string) *Regexp function: they don't return errors because their intended use is where the input is guaranteed to be valid.

Examples:

// "text" is a valid template, parsing it will not fail
var t = template.Must(template.New("name").Parse("text"))

// `^[a-z]+\[[0-9]+\]$` is a valid regexp, always compiles
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)