| Error handling and Go |
| 12 Jul 2011 |
| Tags: error, interface, type, technical |
| |
| Andrew Gerrand |
| |
| * Introduction |
| |
| If you have written any Go code you have probably encountered the built-in `error` type. |
| Go code uses `error` values to indicate an abnormal state. |
| For example, the `os.Open` function returns a non-nil `error` value when |
| it fails to open a file. |
| |
| func Open(name string) (file *File, err error) |
| |
| The following code uses `os.Open` to open a file. |
| If an error occurs it calls `log.Fatal` to print the error message and stop. |
| |
| f, err := os.Open("filename.ext") |
| if err != nil { |
| log.Fatal(err) |
| } |
| // do something with the open *File f |
| |
| You can get a lot done in Go knowing just this about the `error` type, |
| but in this article we'll take a closer look at `error` and discuss some |
| good practices for error handling in Go. |
| |
| * The error type |
| |
| The `error` type is an interface type. An `error` variable represents any |
| value that can describe itself as a string. |
| Here is the interface's declaration: |
| |
| type error interface { |
| Error() string |
| } |
| |
| The `error` type, as with all built in types, |
| is [[https://golang.org/doc/go_spec.html#Predeclared_identifiers][predeclared]] |
| in the [[https://golang.org/doc/go_spec.html#Blocks][universe block]]. |
| |
| The most commonly-used `error` implementation is the [[https://golang.org/pkg/errors/][errors]] |
| package's unexported `errorString` type. |
| |
| // errorString is a trivial implementation of error. |
| type errorString struct { |
| s string |
| } |
| |
| func (e *errorString) Error() string { |
| return e.s |
| } |
| |
| You can construct one of these values with the `errors.New` function. |
| It takes a string that it converts to an `errors.errorString` and returns |
| as an `error` value. |
| |
| // New returns an error that formats as the given text. |
| func New(text string) error { |
| return &errorString{text} |
| } |
| |
| Here's how you might use `errors.New`: |
| |
| func Sqrt(f float64) (float64, error) { |
| if f < 0 { |
| return 0, errors.New("math: square root of negative number") |
| } |
| // implementation |
| } |
| |
| A caller passing a negative argument to `Sqrt` receives a non-nil `error` |
| value (whose concrete representation is an `errors.errorString` value). |
| The caller can access the error string ("math: |
| square root of...") by calling the `error`'s `Error` method, |
| or by just printing it: |
| |
| f, err := Sqrt(-1) |
| if err != nil { |
| fmt.Println(err) |
| } |
| |
| The [[https://golang.org/pkg/fmt/][fmt]] package formats an `error` value by calling its `Error()`string` method. |
| |
| It is the error implementation's responsibility to summarize the context. |
| The error returned by `os.Open` formats as "open /etc/passwd: |
| permission denied," not just "permission denied." The error returned by |
| our `Sqrt` is missing information about the invalid argument. |
| |
| To add that information, a useful function is the `fmt` package's `Errorf`. |
| It formats a string according to `Printf`'s rules and returns it as an `error` |
| created by `errors.New`. |
| |
| if f < 0 { |
| return 0, fmt.Errorf("math: square root of negative number %g", f) |
| } |
| |
| In many cases `fmt.Errorf` is good enough, |
| but since `error` is an interface, you can use arbitrary data structures as error values, |
| to allow callers to inspect the details of the error. |
| |
| For instance, our hypothetical callers might want to recover the invalid |
| argument passed to `Sqrt`. |
| We can enable that by defining a new error implementation instead of using |
| `errors.errorString`: |
| |
| type NegativeSqrtError float64 |
| |
| func (f NegativeSqrtError) Error() string { |
| return fmt.Sprintf("math: square root of negative number %g", float64(f)) |
| } |
| |
| A sophisticated caller can then use a [[https://golang.org/doc/go_spec.html#Type_assertions][type assertion]] |
| to check for a `NegativeSqrtError` and handle it specially, |
| while callers that just pass the error to `fmt.Println` or `log.Fatal` will |
| see no change in behavior. |
| |
| As another example, the [[https://golang.org/pkg/encoding/json/][json]] |
| package specifies a `SyntaxError` type that the `json.Decode` function returns |
| when it encounters a syntax error parsing a JSON blob. |
| |
| type SyntaxError struct { |
| msg string // description of error |
| Offset int64 // error occurred after reading Offset bytes |
| } |
| |
| func (e *SyntaxError) Error() string { return e.msg } |
| |
| The `Offset` field isn't even shown in the default formatting of the error, |
| but callers can use it to add file and line information to their error messages: |
| |
| if err := dec.Decode(&val); err != nil { |
| if serr, ok := err.(*json.SyntaxError); ok { |
| line, col := findLine(f, serr.Offset) |
| return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err) |
| } |
| return err |
| } |
| |
| (This is a slightly simplified version of some [[https://github.com/camlistore/go4/blob/03efcb870d84809319ea509714dd6d19a1498483/jsonconfig/eval.go#L123-L135][actual code]] |
| from the [[http://camlistore.org][Camlistore]] project.) |
| |
| The `error` interface requires only a `Error` method; |
| specific error implementations might have additional methods. |
| For instance, the [[https://golang.org/pkg/net/][net]] package returns errors of type `error`, |
| following the usual convention, but some of the error implementations have |
| additional methods defined by the `net.Error` interface: |
| |
| package net |
| |
| type Error interface { |
| error |
| Timeout() bool // Is the error a timeout? |
| Temporary() bool // Is the error temporary? |
| } |
| |
| Client code can test for a `net.Error` with a type assertion and then distinguish |
| transient network errors from permanent ones. |
| For instance, a web crawler might sleep and retry when it encounters a temporary |
| error and give up otherwise. |
| |
| if nerr, ok := err.(net.Error); ok && nerr.Temporary() { |
| time.Sleep(1e9) |
| continue |
| } |
| if err != nil { |
| log.Fatal(err) |
| } |
| |
| * Simplifying repetitive error handling |
| |
| In Go, error handling is important. The language's design and conventions |
| encourage you to explicitly check for errors where they occur (as distinct |
| from the convention in other languages of throwing exceptions and sometimes catching them). |
| In some cases this makes Go code verbose, |
| but fortunately there are some techniques you can use to minimize repetitive error handling. |
| |
| Consider an [[https://cloud.google.com/appengine/docs/go/][App Engine]] |
| application with an HTTP handler that retrieves a record from the datastore |
| and formats it with a template. |
| |
| func init() { |
| http.HandleFunc("/view", viewRecord) |
| } |
| |
| func viewRecord(w http.ResponseWriter, r *http.Request) { |
| c := appengine.NewContext(r) |
| key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) |
| record := new(Record) |
| if err := datastore.Get(c, key, record); err != nil { |
| http.Error(w, err.Error(), 500) |
| return |
| } |
| if err := viewTemplate.Execute(w, record); err != nil { |
| http.Error(w, err.Error(), 500) |
| } |
| } |
| |
| This function handles errors returned by the `datastore.Get` function and |
| `viewTemplate`'s `Execute` method. |
| In both cases, it presents a simple error message to the user with the HTTP |
| status code 500 ("Internal Server Error"). |
| This looks like a manageable amount of code, |
| but add some more HTTP handlers and you quickly end up with many copies |
| of identical error handling code. |
| |
| To reduce the repetition we can define our own HTTP `appHandler` type that includes an `error` return value: |
| |
| type appHandler func(http.ResponseWriter, *http.Request) error |
| |
| Then we can change our `viewRecord` function to return errors: |
| |
| func viewRecord(w http.ResponseWriter, r *http.Request) error { |
| c := appengine.NewContext(r) |
| key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) |
| record := new(Record) |
| if err := datastore.Get(c, key, record); err != nil { |
| return err |
| } |
| return viewTemplate.Execute(w, record) |
| } |
| |
| This is simpler than the original version, |
| but the [[https://golang.org/pkg/net/http/][http]] package doesn't understand |
| functions that return `error`. |
| To fix this we can implement the `http.Handler` interface's `ServeHTTP` |
| method on `appHandler`: |
| |
| func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| if err := fn(w, r); err != nil { |
| http.Error(w, err.Error(), 500) |
| } |
| } |
| |
| The `ServeHTTP` method calls the `appHandler` function and displays the |
| returned error (if any) to the user. |
| Notice that the method's receiver, `fn`, is a function. |
| (Go can do that!) The method invokes the function by calling the receiver |
| in the expression `fn(w,`r)`. |
| |
| Now when registering `viewRecord` with the http package we use the `Handle` |
| function (instead of `HandleFunc`) as `appHandler` is an `http.Handler` |
| (not an `http.HandlerFunc`). |
| |
| func init() { |
| http.Handle("/view", appHandler(viewRecord)) |
| } |
| |
| With this basic error handling infrastructure in place, |
| we can make it more user friendly. |
| Rather than just displaying the error string, |
| it would be better to give the user a simple error message with an appropriate HTTP status code, |
| while logging the full error to the App Engine developer console for debugging purposes. |
| |
| To do this we create an `appError` struct containing an `error` and some other fields: |
| |
| type appError struct { |
| Error error |
| Message string |
| Code int |
| } |
| |
| Next we modify the appHandler type to return `*appError` values: |
| |
| type appHandler func(http.ResponseWriter, *http.Request) *appError |
| |
| (It's usually a mistake to pass back the concrete type of an error rather than `error`, |
| for reasons discussed in [[https://golang.org/doc/go_faq.html#nil_error][the Go FAQ]], |
| but it's the right thing to do here because `ServeHTTP` is the only place |
| that sees the value and uses its contents.) |
| |
| And make `appHandler`'s `ServeHTTP` method display the `appError`'s `Message` |
| to the user with the correct HTTP status `Code` and log the full `Error` |
| to the developer console: |
| |
| func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| if e := fn(w, r); e != nil { // e is *appError, not os.Error. |
| c := appengine.NewContext(r) |
| c.Errorf("%v", e.Error) |
| http.Error(w, e.Message, e.Code) |
| } |
| } |
| |
| Finally, we update `viewRecord` to the new function signature and have it |
| return more context when it encounters an error: |
| |
| func viewRecord(w http.ResponseWriter, r *http.Request) *appError { |
| c := appengine.NewContext(r) |
| key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) |
| record := new(Record) |
| if err := datastore.Get(c, key, record); err != nil { |
| return &appError{err, "Record not found", 404} |
| } |
| if err := viewTemplate.Execute(w, record); err != nil { |
| return &appError{err, "Can't display record", 500} |
| } |
| return nil |
| } |
| |
| This version of `viewRecord` is the same length as the original, |
| but now each of those lines has specific meaning and we are providing a |
| friendlier user experience. |
| |
| It doesn't end there; we can further improve the error handling in our application. Some ideas: |
| |
| - give the error handler a pretty HTML template, |
| |
| - make debugging easier by writing the stack trace to the HTTP response when the user is an administrator, |
| |
| - write a constructor function for `appError` that stores the stack trace for easier debugging, |
| |
| - recover from panics inside the `appHandler`, |
| logging the error to the console as "Critical," while telling the user "a |
| serious error has occurred." This is a nice touch to avoid exposing the |
| user to inscrutable error messages caused by programming errors. |
| See the [[https://golang.org/doc/articles/defer_panic_recover.html][Defer, Panic, and Recover]] |
| article for more details. |
| |
| * Conclusion |
| |
| Proper error handling is an essential requirement of good software. |
| By employing the techniques described in this post you should be able to |
| write more reliable and succinct Go code. |