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?
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
}
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 error
s 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]+\]$`)