github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/blog/content/error-handling-and-go.article (about)

     1  Error handling and Go
     2  12 Jul 2011
     3  Tags: error, interface, type, technical
     4  
     5  Andrew Gerrand
     6  
     7  * Introduction
     8  
     9  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.
    10  
    11  	func Open(name string) (file *File, err error)
    12  
    13  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.
    14  
    15  	    f, err := os.Open("filename.ext")
    16  	    if err != nil {
    17  	        log.Fatal(err)
    18  	    }
    19  	    // do something with the open *File f
    20  
    21  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.
    22  
    23  * The error type
    24  
    25  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:
    26  
    27  	type error interface {
    28  	    Error() string
    29  	}
    30  
    31  The `error` type, as with all built in types, is [[http://golang.org/doc/go_spec.html#Predeclared_identifiers][predeclared]] in the [[http://golang.org/doc/go_spec.html#Blocks][universe block]].
    32  
    33  The most commonly-used `error` implementation is the [[http://golang.org/pkg/errors/][errors]] package's unexported `errorString` type.
    34  
    35  	// errorString is a trivial implementation of error.
    36  	type errorString struct {
    37  	    s string
    38  	}
    39  
    40  	func (e *errorString) Error() string {
    41  	    return e.s
    42  	}
    43  
    44  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.
    45  
    46  	// New returns an error that formats as the given text.
    47  	func New(text string) error {
    48  	    return &errorString{text}
    49  	}
    50  
    51  Here's how you might use `errors.New`:
    52  
    53  	func Sqrt(f float64) (float64, error) {
    54  	    if f < 0 {
    55  	        return 0, errors.New("math: square root of negative number")
    56  	    }
    57  	    // implementation
    58  	}
    59  
    60  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:
    61  
    62  	    f, err := Sqrt(-1)
    63  	    if err != nil {
    64  	        fmt.Println(err)
    65  	    }
    66  
    67  The [[http://golang.org/pkg/fmt/][fmt]] package formats an `error` value by calling its `Error()`string` method.
    68  
    69  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.
    70  
    71  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`.
    72  
    73  	    if f < 0 {
    74  	        return 0, fmt.Errorf("math: square root of negative number %g", f)
    75  	    }
    76  
    77  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.
    78  
    79  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`:
    80  
    81  	type NegativeSqrtError float64
    82  
    83  	func (f NegativeSqrtError) Error() string {
    84  	    return fmt.Sprintf("math: square root of negative number %g", float64(f))
    85  	}
    86  
    87  A sophisticated caller can then use a [[http://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.
    88  
    89  As another example, the [[http://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.
    90  
    91  	type SyntaxError struct {
    92  	    msg    string // description of error
    93  	    Offset int64  // error occurred after reading Offset bytes
    94  	}
    95  
    96  	func (e *SyntaxError) Error() string { return e.msg }
    97  
    98  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:
    99  
   100  	    if err := dec.Decode(&val); err != nil {
   101  	        if serr, ok := err.(*json.SyntaxError); ok {
   102  	            line, col := findLine(f, serr.Offset)
   103  	            return fmt.Errorf("%s:%d:%d: %v", f.Name(), line, col, err)
   104  	        }
   105  	        return err
   106  	    }
   107  
   108  (This is a slightly simplified version of some [[http://camlistore.org/code/?p=camlistore.git;a=blob;f=lib/go/camli/jsonconfig/eval.go#l68][actual code]] from the [[http://camlistore.org][Camlistore]] project.)
   109  
   110  The `error` interface requires only a `Error` method; specific error implementations might have additional methods. For instance, the [[http://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:
   111  
   112  	package net
   113  
   114  	type Error interface {
   115  	    error
   116  	    Timeout() bool   // Is the error a timeout?
   117  	    Temporary() bool // Is the error temporary?
   118  	}
   119  
   120  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.
   121  
   122  	        if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
   123  	            time.Sleep(1e9)
   124  	            continue
   125  	        }
   126  	        if err != nil {
   127  	            log.Fatal(err)
   128  	        }
   129  
   130  * Simplifying repetitive error handling
   131  
   132  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.
   133  
   134  Consider an [[http://code.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.
   135  
   136  	func init() {
   137  	    http.HandleFunc("/view", viewRecord)
   138  	}
   139  
   140  	func viewRecord(w http.ResponseWriter, r *http.Request) {
   141  	    c := appengine.NewContext(r)
   142  	    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
   143  	    record := new(Record)
   144  	    if err := datastore.Get(c, key, record); err != nil {
   145  	        http.Error(w, err.Error(), 500)
   146  	        return
   147  	    }
   148  	    if err := viewTemplate.Execute(w, record); err != nil {
   149  	        http.Error(w, err.Error(), 500)
   150  	    }
   151  	}
   152  
   153  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.
   154  
   155  To reduce the repetition we can define our own HTTP `appHandler` type that includes an `error` return value:
   156  
   157  	type appHandler func(http.ResponseWriter, *http.Request) error
   158  
   159  Then we can change our `viewRecord` function to return errors:
   160  
   161  	func viewRecord(w http.ResponseWriter, r *http.Request) error {
   162  	    c := appengine.NewContext(r)
   163  	    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
   164  	    record := new(Record)
   165  	    if err := datastore.Get(c, key, record); err != nil {
   166  	        return err
   167  	    }
   168  	    return viewTemplate.Execute(w, record)
   169  	}
   170  
   171  This is simpler than the original version, but the [[http://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`:
   172  
   173  	func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   174  	    if err := fn(w, r); err != nil {
   175  	        http.Error(w, err.Error(), 500)
   176  	    }
   177  	}
   178  
   179  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)`.
   180  
   181  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`).
   182  
   183  	func init() {
   184  	    http.Handle("/view", appHandler(viewRecord))
   185  	}
   186  
   187  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.
   188  
   189  To do this we create an `appError` struct containing an `error` and some other fields:
   190  
   191  	type appError struct {
   192  	    Error   error
   193  	    Message string
   194  	    Code    int
   195  	}
   196  
   197  Next we modify the appHandler type to return `*appError` values:
   198  
   199  	type appHandler func(http.ResponseWriter, *http.Request) *appError
   200  
   201  (It's usually a mistake to pass back the concrete type of an error rather than `error`, for reasons discussed in [[http://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.)
   202  
   203  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:
   204  
   205  	func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   206  	    if e := fn(w, r); e != nil { // e is *appError, not os.Error.
   207  	        c := appengine.NewContext(r)
   208  	        c.Errorf("%v", e.Error)
   209  	        http.Error(w, e.Message, e.Code)
   210  	    }
   211  	}
   212  
   213  Finally, we update `viewRecord` to the new function signature and have it return more context when it encounters an error:
   214  
   215  	func viewRecord(w http.ResponseWriter, r *http.Request) *appError {
   216  	    c := appengine.NewContext(r)
   217  	    key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil)
   218  	    record := new(Record)
   219  	    if err := datastore.Get(c, key, record); err != nil {
   220  	        return &appError{err, "Record not found", 404}
   221  	    }
   222  	    if err := viewTemplate.Execute(w, record); err != nil {
   223  	        return &appError{err, "Can't display record", 500}
   224  	    }
   225  	    return nil
   226  	}
   227  
   228  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.
   229  
   230  It doesn't end there; we can further improve the error handling in our application. Some ideas:
   231  
   232  - give the error handler a pretty HTML template,
   233  
   234  - make debugging easier by writing the stack trace to the HTTP response when the user is an administrator,
   235  
   236  - write a constructor function for `appError` that stores the stack trace for easier debugging,
   237  
   238  - 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 [[http://golang.org/doc/articles/defer_panic_recover.html][Defer, Panic, and Recover]] article for more details.
   239  
   240  * Conclusion
   241  
   242  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.