diff --git a/doc/Makefile b/doc/Makefile index f65e538d97..daa0a5ea2b 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -8,7 +8,14 @@ TARG=tmpltohtml GOFILES=\ tmpltohtml.go\ -all: tmpltohtml go_tutorial.html effective_go.html go1.html articles/defer_panic_recover.html +HTML=\ + articles/defer_panic_recover.html\ + articles/error_handling.html\ + effective_go.html\ + go1.html\ + go_tutorial.html\ + +all: tmpltohtml $(HTML) %.html: %.tmpl tmpltohtml ./makehtml $*.tmpl diff --git a/doc/articles/error_handling.html b/doc/articles/error_handling.html new file mode 100644 index 0000000000..1a69324107 --- /dev/null +++ b/doc/articles/error_handling.html @@ -0,0 +1,447 @@ + + +
+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
+predeclared in the
+universe block.
+
+The most commonly-used error
implementation is the
+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 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
+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 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 +actual code +from the Camlistore project.) +
+ +
+The error
interface requires only a Error
method;
+specific error implementations might have additional methods. For instance, the
+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 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 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 to be discussed in another article, 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: +
+ +appError
that stores the
+stack trace for easier debugging,
+appHandler
, logging the error
+to the console as "Critical," while simply 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 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. +
diff --git a/doc/articles/error_handling.tmpl b/doc/articles/error_handling.tmpl new file mode 100644 index 0000000000..75800ae21a --- /dev/null +++ b/doc/articles/error_handling.tmpl @@ -0,0 +1,312 @@ + + +
+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.
+
+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.
+
+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
+predeclared in the
+universe block.
+
+The most commonly-used error
implementation is the
+errors package's unexported errorString
type.
+
+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.
+
+Here's how you might use errors.New
:
+
+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:
+
+The 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
.
+
+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
:
+
+A sophisticated caller can then use a
+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 json package specifies a
+SyntaxError
type that the json.Decode
function
+returns when it encounters a syntax error parsing a JSON blob.
+
+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:
+
+(This is a slightly simplified version of some +actual code +from the Camlistore project.) +
+ +
+The error
interface requires only a Error
method;
+specific error implementations might have additional methods. For instance, the
+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.
+
+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 App Engine +application with an HTTP handler that retrieves a record from the datastore and +formats it with a template. +
+ +{{code "progs/error2.go" `/func init/` `/STOP/`}} + +
+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:
+
+Then we can change our viewRecord
function to return errors:
+
+This is simpler than the original version, but the http package doesn't understand functions that return
+error
.
+To fix this we can implement the http.Handler
interface's
+ServeHTTP
method on appHandler
:
+
+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
).
+
+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:
+
+Next we modify the appHandler type to return *appError
values:
+
+(It's usually a mistake to pass back the concrete type of an error rather than
+error
, for reasons to be discussed in another article, 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:
+
+Finally, we update viewRecord
to the new function signature and
+have it return more context when it encounters an error:
+
+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: +
+ +appError
that stores the
+stack trace for easier debugging,
+appHandler
, logging the error
+to the console as "Critical," while simply 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 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. +
diff --git a/doc/progs/error.go b/doc/progs/error.go new file mode 100644 index 0000000000..3f98709f7c --- /dev/null +++ b/doc/progs/error.go @@ -0,0 +1,115 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code snippets included in "Error Handling and Go." + +package main + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net" + "os" + "time" +) + +type File struct{} + +func Open(name string) (file *File, err error) + +func openFile() { // OMIT + f, err := os.Open("filename.ext") + if err != nil { + log.Fatal(err) + } + // do something with the open *File f + // STOP OMIT + _ = f +} + +// errorString is a trivial implementation of error. +type errorString struct { + s string +} + +func (e *errorString) Error() string { + return e.s +} +// STOP OMIT + +// New returns an error that formats as the given text. +func New(text string) error { + return &errorString{text} +} +// STOP OMIT + +func Sqrt(f float64) (float64, error) { + if f < 0 { + return 0, errors.New("math: square root of negative number") + } + // implementation + return 0, nil // OMIT +} +// STOP OMIT + +func printErr() (int, error) { // OMIT + f, err := Sqrt(-1) + if err != nil { + fmt.Println(err) + } + // STOP OMIT + // fmtError OMIT + if f < 0 { + return 0, fmt.Errorf("math: square root of negative number %g", f) + } + // STOP OMIT + return 0, nil +} + +type NegativeSqrtError float64 + +func (f NegativeSqrtError) Error() string { + return fmt.Sprintf("math: square root of negative number %g", float64(f)) +} +// STOP OMIT + +type SyntaxError struct { + msg string // description of error + Offset int64 // error occurred after reading Offset bytes +} + +func (e *SyntaxError) Error() string { return e.msg } +// STOP OMIT + +func decodeError(dec *json.Decoder, val struct{}) error { // OMIT + var f os.FileInfo // OMIT + 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 + } + // STOP OMIT + return nil +} + +func findLine(os.FileInfo, int64) (int, int) + +func netError(err error) { // OMIT + for { // OMIT + if nerr, ok := err.(net.Error); ok && nerr.Temporary() { + time.Sleep(1e9) + continue + } + if err != nil { + log.Fatal(err) + } + // STOP OMIT + } +} + +func main() {} diff --git a/doc/progs/error2.go b/doc/progs/error2.go new file mode 100644 index 0000000000..fe72350181 --- /dev/null +++ b/doc/progs/error2.go @@ -0,0 +1,53 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code snippets included in "Error Handling and Go." + +package main + +import ( + "net/http" + "text/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) + } +} +// STOP OMIT + +type ap struct{} + +func (ap) NewContext(*http.Request) *ctx { return nil } + +type ctx struct{} + +func (*ctx) Errorf(string, ...interface{}) {} + +var appengine ap + +type ds struct{} + +func (ds) NewKey(*ctx, string, string, int, *int) string { return "" } +func (ds) Get(*ctx, string, *Record) error { return nil } + +var datastore ds + +type Record struct{} + +var viewTemplate *template.Template + +func main() {} diff --git a/doc/progs/error3.go b/doc/progs/error3.go new file mode 100644 index 0000000000..8305edc420 --- /dev/null +++ b/doc/progs/error3.go @@ -0,0 +1,60 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code snippets included in "Error Handling and Go." + +package main + +import ( + "net/http" + "text/template" +) + +func init() { + http.Handle("/view", appHandler(viewRecord)) +} +// STOP OMIT + +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) +} +// STOP OMIT + +type appHandler func(http.ResponseWriter, *http.Request) error + +func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + http.Error(w, err.Error(), 500) + } +} +// STOP OMIT + +type ap struct{} + +func (ap) NewContext(*http.Request) *ctx { return nil } + +type ctx struct{} + +func (*ctx) Errorf(string, ...interface{}) {} + +var appengine ap + +type ds struct{} + +func (ds) NewKey(*ctx, string, string, int, *int) string { return "" } +func (ds) Get(*ctx, string, *Record) error { return nil } + +var datastore ds + +type Record struct{} + +var viewTemplate *template.Template + +func main() {} diff --git a/doc/progs/error4.go b/doc/progs/error4.go new file mode 100644 index 0000000000..661dcdc2b6 --- /dev/null +++ b/doc/progs/error4.go @@ -0,0 +1,71 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code snippets included in "Error Handling and Go." + +package main + +import ( + "net/http" + "text/template" +) + +type appError struct { + Error error + Message string + Code int +} +// STOP OMIT + +type appHandler func(http.ResponseWriter, *http.Request) *appError + +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) + } +} +// STOP OMIT + +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 +} +// STOP OMIT + +func init() { + http.Handle("/view", appHandler(viewRecord)) +} + +type ap struct{} + +func (ap) NewContext(*http.Request) *ctx { return nil } + +type ctx struct{} + +func (*ctx) Errorf(string, ...interface{}) {} + +var appengine ap + +type ds struct{} + +func (ds) NewKey(*ctx, string, string, int, *int) string { return "" } +func (ds) Get(*ctx, string, *Record) error { return nil } + +var datastore ds + +type Record struct{} + +var viewTemplate *template.Template + +func main() {} diff --git a/doc/progs/run b/doc/progs/run index d8efe96c25..b4c63f8b3e 100755 --- a/doc/progs/run +++ b/doc/progs/run @@ -31,6 +31,13 @@ effective_go=" eff_sequence.go " +error_handling=" + error.go + error2.go + error3.go + error4.go +" + go_tutorial=" cat.go cat_rot13.go @@ -52,6 +59,7 @@ go_tutorial=" for i in \ $defer_panic_recover \ $effective_go \ + $error_handling \ $go_tutorial \ go1.go \ ; do