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