aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Pike <r@golang.org>2015-01-09 14:45:38 +1100
committerRob Pike <r@golang.org>2015-01-13 00:44:13 +0000
commite0bdb5e11933d836cf5563c81eb03a24b3c72e2b (patch)
treeb65c042a348d8722f6e52efdae14e37d5f9a7b29
parenta58ce1c1bec32fe10d98c52164a5175f857eb4d9 (diff)
blog: new post "Errors are values"
I won't submit it this time. Sorry. Change-Id: I3a63985bf6b12a22d0371f8fdf5c0fcd2a01ff8b Reviewed-on: https://go-review.googlesource.com/2539 Reviewed-by: Andrew Gerrand <adg@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
-rw-r--r--content/errors-are-values.article233
1 files changed, 233 insertions, 0 deletions
diff --git a/content/errors-are-values.article b/content/errors-are-values.article
new file mode 100644
index 0000000..fa5fedf
--- /dev/null
+++ b/content/errors-are-values.article
@@ -0,0 +1,233 @@
+Errors are values
+12 Jan 2015
+
+* Introduction
+
+A common point of discussion among Go programmers,
+especially those new to the language, is how to handle errors.
+The conversation often turns into a lament at the number of times the sequence
+
+ if err != nil {
+ return err
+ }
+
+shows up.
+We recently scanned all the open source projects we could find and
+discovered that this snippet occurs only once per page or two,
+less often than some would have you believe.
+Still, if the perception persists that one must type
+
+ if err != nil
+
+all the time, something must be wrong, and the obvious target is Go itself.
+
+This is unfortunate, misleading, and easily corrected.
+Perhaps what is happening is that programmers new to Go ask,
+"How does one handle errors?", learn this pattern, and stop there.
+In other languages, one might use a try-catch block or other such mechanism to handle errors.
+Therefore, the programmer thinks, when I would have used a try-catch
+in my old language, I will just type `if` `err` `!=` `nil` in Go.
+Over time the Go code collects many such snippets, and the result feels clumsy.
+
+Regardless of whether this explanation fits,
+it is clear that these Go programmers miss a fundamental point about errors:
+_Errors_are_values._
+
+Values can be programmed, and since errors are values, errors can be programmed.
+
+Of course a common statement involving an error value is to test whether it is nil,
+but there are countless other things one can do with an error value,
+and application of some of those other things can make your program better,
+eliminating much of the boilerplate that arises if every error is checked with a rote if statement.
+
+Here's a simple example from the `bufio` package's
+[[http://golang.org/pkg/bufio/#Scanner][`Scanner`]] type.
+Its [[http://golang.org/pkg/bufio/#Scanner.Scan][`Scan`]] method performs the underlying I/O,
+which can of course lead to an error.
+Yet the `Scan` method does not expose an error at all.
+Instead, it returns a boolean, and a separate method, to be run at the end of the scan,
+reports whether an error occurred.
+Client code looks like this:
+
+ scanner := bufio.NewScanner(input)
+ for scanner.Scan() {
+ token := scanner.Text()
+ // process token
+ }
+ if err := scanner.Err(); err != nil {
+ // process the error
+ }
+
+Sure, there is a nil check for an error, but it appears and executes only once.
+The `Scan` method could instead have been defined as
+
+ func (s *Scanner) Scan() (token []byte, error)
+
+and then the example user code might be (depending on how the token is retrieved),
+
+ scanner := bufio.NewScanner(input)
+ for {
+ token, err := scanner.Scan()
+ if err != nil {
+ return err // or maybe break
+ }
+ // process token
+ }
+
+This isn't very different, but there is one important distinction.
+In this code, the client must check for an error on every iteration,
+but in the real `Scanner` API, the error handling is abstracted away from the key API element,
+which is iterating over tokens.
+With the real API, the client's code therefore feels more natural:
+loop until done, then worry about errors.
+Error handling does not obscure the flow of control.
+
+Under the covers what's happening, of course,
+is that as soon as `Scan` encounters an I/O error, it records it and returns `false`.
+A separate method, [[http://golang.org/pkg/bufio/#Scanner.Err][`Err`]],
+reports the error value when the client asks.
+Trivial though this is, it's not the same as putting
+
+ if err != nil
+
+everywhere or asking the client to check for an error after every token.
+It's programming with error values.
+Simple programming, yes, but programming nonetheless.
+
+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.
+
+The topic of repetitive error-checking code arose when I attended the autumn 2014 GoCon in Tokyo.
+An enthusiastic gopher, who goes by [[https://twitter.com/jxck_][`@jxck_`]] on Twitter,
+echoed the familiar lament about error checking.
+He had some code that looked schematically like this:
+
+ _, 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
+
+It is very repetitive.
+In the real code, which was longer,
+there is more going on so it's not easy to just refactor this using a helper function,
+but in this idealized form, a function literal closing over the error variable would help:
+
+ 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
+ }
+
+This pattern works well, but requires a closure in each function doing the writes;
+a separate helper function is clumsier to use because the `err` variable
+needs to be maintained across calls (try it).
+
+We can make this cleaner, more general, and reusable by borrowing the idea from the
+`Scan` method above.
+I mentioned this technique in our discussion but `@jxck_` didn't see how to apply it.
+After a long exchange, hampered somewhat by a language barrier,
+I asked if I could just borrow his laptop and show him by typing some code.
+
+I defined an object called an `errWriter`, something like this:
+
+ type errWriter struct {
+ w io.Writer
+ err error
+ }
+
+and gave it one method, `write.`
+It doesn't need to have the standard `Write` signature,
+and it's lower-cased in part to highlight the distinction.
+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
+ }
+
+This is cleaner, even compared to the use of a closure,
+and also makes the actual sequence of writes being done easier to see on the page.
+There is no clutter any more.
+Programming with error values (and interfaces) has made the code nicer.
+
+It's likely that some other piece of code in the same package can build on this idea,
+or even use `errWriter` directly.
+
+Also, once `errWriter` exists, there's more it could do to help,
+especially in less artificial examples.
+It could accumulate the byte count.
+It could coalesce writes into a single buffer that can then be transmitted atomically.
+And much more.
+
+In fact, this pattern appears often in the standard library.
+The [[http://golang.org/pkg/archive/zip/][`archive/zip`]] and
+[[http://golang.org/pkg/net/http/][`net/http`]] packages use it.
+More salient to this discussion, the [[http://golang.org/pkg/bufio/][`bufio` package's `Writer`]]
+is actually an implementation of the `errWriter` idea.
+Although `bufio.Writer.Write` returns an error,
+that is mostly about honoring the [[http://golang.org/pkg/io/#Writer][`io.Writer`]] interface.
+The `Write` method of `bufio.Writer` behaves just like our `errWriter.write`
+method above, with `Flush` reporting the error, so our example could be written like this:
+
+ 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()
+ }
+
+There is one significant drawback to this approach, at least for some applications:
+there is no way to know how much of the processing completed before the error occurred.
+If that information is important, a more fine-grained approach is necessary.
+Often, though, an all-or-nothing check at the end is sufficient.
+
+We've looked at just one technique for avoiding repetitive error handling code.
+Keep in mind that the use of `errWriter` or `bufio.Writer` isn't the only way to simplify error handling,
+and this approach is not suitable for all situations.
+The key lesson, however, is that errors are values and the full power of
+the Go programming language is available for processing them.
+
+Use the language to simplify your error handling.
+
+But remember: Whatever you do, always check your errors!
+
+Finally, for the full story of my interaction with @jxck_, including a little video he recorded,
+visit [[http://jxck.hatenablog.com/entry/golang-error-handling-lesson-by-rob-pike][his blog]].