mirror of
https://github.com/golang/go
synced 2024-11-21 20:44:39 -07:00
doc: add Error Handling article
Originally published on The Go Programming Language Blog, July 12, 2011. http://blog.golang.org/2011/07/error-handling-and-go.html Update #2547 R=golang-dev, r CC=golang-dev https://golang.org/cl/5475060
This commit is contained in:
parent
06a9bc6835
commit
c400a0b7db
@ -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
|
||||
|
447
doc/articles/error_handling.html
Normal file
447
doc/articles/error_handling.html
Normal file
@ -0,0 +1,447 @@
|
||||
<!-- Error Handling and Go -->
|
||||
|
||||
<p>
|
||||
If you have written any Go code you have probably encountered the built-in
|
||||
<code>error</code> type. Go code uses <code>error</code> values to
|
||||
indicate an abnormal state. For example, the <code>os.Open</code> function
|
||||
returns a non-nil <code>error</code> value when it fails to open a file.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/func Open/`}}
|
||||
-->func Open(name string) (file *File, err error)
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
The following code uses <code>os.Open</code> to open a file. If an error
|
||||
occurs it calls <code>log.Fatal</code> to print the error message and stop.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/func openFile/` `/STOP/`}}
|
||||
--> f, err := os.Open("filename.ext")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// do something with the open *File f
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
You can get a lot done in Go knowing just this about the <code>error</code>
|
||||
type, but in this article we'll take a closer look at <code>error</code> and
|
||||
discuss some good practices for error handling in Go.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>The error type</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <code>error</code> type is an interface type. An <code>error</code>
|
||||
variable represents any value that can describe itself as a string. Here is the
|
||||
interface's declaration:
|
||||
</p>
|
||||
|
||||
<pre>type error interface {
|
||||
Error() string
|
||||
}</pre>
|
||||
|
||||
<p>
|
||||
The <code>error</code> type, as with all built in types, is
|
||||
<a href="/doc/go_spec.html#Predeclared_identifiers">predeclared</a> in the
|
||||
<a href="/doc/go_spec.html#Blocks">universe block</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The most commonly-used <code>error</code> implementation is the
|
||||
<a href="/pkg/errors/">errors</a> package's unexported <code>errorString</code> type.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/errorString/` `/STOP/`}}
|
||||
-->// errorString is a trivial implementation of error.
|
||||
type errorString struct {
|
||||
s string
|
||||
}
|
||||
|
||||
func (e *errorString) Error() string {
|
||||
return e.s
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
You can construct one of these values with the <code>errors.New</code>
|
||||
function. It takes a string that it converts to an <code>errors.errorString</code>
|
||||
and returns as an <code>error</code> value.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/New/` `/STOP/`}}
|
||||
-->// New returns an error that formats as the given text.
|
||||
func New(text string) error {
|
||||
return &errorString{text}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Here's how you might use <code>errors.New</code>:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/func Sqrt/` `/STOP/`}}
|
||||
-->func Sqrt(f float64) (float64, error) {
|
||||
if f < 0 {
|
||||
return 0, errors.New("math: square root of negative number")
|
||||
}
|
||||
// implementation
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
A caller passing a negative argument to <code>Sqrt</code> receives a non-nil
|
||||
<code>error</code> value (whose concrete representation is an
|
||||
<code>errors.errorString</code> value). The caller can access the error string
|
||||
("math: square root of...") by calling the <code>error</code>'s
|
||||
<code>Error</code> method, or by just printing it:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/func printErr/` `/STOP/`}}
|
||||
--> f, err := Sqrt(-1)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
The <a href="/pkg/fmt/">fmt</a> package formats an <code>error</code> value
|
||||
by calling its <code>Error() string</code> method.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It is the error implementation's responsibility to summarize the context.
|
||||
The error returned by <code>os.Open</code> formats as "open /etc/passwd:
|
||||
permission denied," not just "permission denied." The error returned by our
|
||||
<code>Sqrt</code> is missing information about the invalid argument.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To add that information, a useful function is the <code>fmt</code> package's
|
||||
<code>Errorf</code>. It formats a string according to <code>Printf</code>'s
|
||||
rules and returns it as an <code>error</code> created by
|
||||
<code>errors.New</code>.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/fmtError/` `/STOP/`}}
|
||||
--> if f < 0 {
|
||||
return 0, fmt.Errorf("math: square root of negative number %g", f)
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
In many cases <code>fmt.Errorf</code> is good enough, but since
|
||||
<code>error</code> is an interface, you can use arbitrary data structures as
|
||||
error values, to allow callers to inspect the details of the error.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For instance, our hypothetical callers might want to recover the invalid
|
||||
argument passed to <code>Sqrt</code>. We can enable that by defining a new
|
||||
error implementation instead of using <code>errors.errorString</code>:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/type NegativeSqrtError/` `/STOP/`}}
|
||||
-->type NegativeSqrtError float64
|
||||
|
||||
func (f NegativeSqrtError) Error() string {
|
||||
return fmt.Sprintf("math: square root of negative number %g", float64(f))
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
A sophisticated caller can then use a
|
||||
<a href="/doc/go_spec.html#Type_assertions">type assertion</a> to check for a
|
||||
<code>NegativeSqrtError</code> and handle it specially, while callers that just
|
||||
pass the error to <code>fmt.Println</code> or <code>log.Fatal</code> will see
|
||||
no change in behavior.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As another example, the <a href="/pkg/encoding/json/">json</a> package specifies a
|
||||
<code>SyntaxError</code> type that the <code>json.Decode</code> function
|
||||
returns when it encounters a syntax error parsing a JSON blob.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/type SyntaxError/` `/STOP/`}}
|
||||
-->type SyntaxError struct {
|
||||
msg string // description of error
|
||||
Offset int64 // error occurred after reading Offset bytes
|
||||
}
|
||||
|
||||
func (e *SyntaxError) Error() string { return e.msg }
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
The <code>Offset</code> 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:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/func decodeError/` `/STOP/`}}
|
||||
--> 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
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
(This is a slightly simplified version of some
|
||||
<a href="http://camlistore.org/code/?p=camlistore.git;a=blob;f=lib/go/camli/jsonconfig/eval.go#l68">actual code</a>
|
||||
from the <a href="http://camlistore.org">Camlistore</a> project.)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <code>error</code> interface requires only a <code>Error</code> method;
|
||||
specific error implementations might have additional methods. For instance, the
|
||||
<a href="/pkg/net/">net</a> package returns errors of type
|
||||
<code>error</code>, following the usual convention, but some of the error
|
||||
implementations have additional methods defined by the <code>net.Error</code>
|
||||
interface:
|
||||
</p>
|
||||
|
||||
<pre>package net
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
Timeout() bool // Is the error a timeout?
|
||||
Temporary() bool // Is the error temporary?
|
||||
}</pre>
|
||||
|
||||
<p>
|
||||
Client code can test for a <code>net.Error</code> 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.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error.go" `/func netError/` `/STOP/`}}
|
||||
--> if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
|
||||
time.Sleep(1e9)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
<b>Simplifying repetitive error handling</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Consider an <a href="http://code.google.com/appengine/docs/go/">App Engine</a>
|
||||
application with an HTTP handler that retrieves a record from the datastore and
|
||||
formats it with a template.
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error2.go" `/func init/` `/STOP/`}}
|
||||
-->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)
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
This function handles errors returned by the <code>datastore.Get</code>
|
||||
function and <code>viewTemplate</code>'s <code>Execute</code> 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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To reduce the repetition we can define our own HTTP <code>appHandler</code>
|
||||
type that includes an <code>error</code> return value:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error3.go" `/type appHandler/`}}
|
||||
-->type appHandler func(http.ResponseWriter, *http.Request) error
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Then we can change our <code>viewRecord</code> function to return errors:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error3.go" `/func viewRecord/` `/STOP/`}}
|
||||
-->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)
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
This is simpler than the original version, but the <a
|
||||
href="/pkg/net/http/">http</a> package doesn't understand functions that return
|
||||
<code>error</code>.
|
||||
To fix this we can implement the <code>http.Handler</code> interface's
|
||||
<code>ServeHTTP</code> method on <code>appHandler</code>:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error3.go" `/ServeHTTP/` `/STOP/`}}
|
||||
-->func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if err := fn(w, r); err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
The <code>ServeHTTP</code> method calls the <code>appHandler</code> function
|
||||
and displays the returned error (if any) to the user. Notice that the method's
|
||||
receiver, <code>fn</code>, is a function. (Go can do that!) The method invokes
|
||||
the function by calling the receiver in the expression <code>fn(w, r)</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Now when registering <code>viewRecord</code> with the http package we use the
|
||||
<code>Handle</code> function (instead of <code>HandleFunc</code>) as
|
||||
<code>appHandler</code> is an <code>http.Handler</code> (not an
|
||||
<code>http.HandlerFunc</code>).
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error3.go" `/func init/` `/STOP/`}}
|
||||
-->func init() {
|
||||
http.Handle("/view", appHandler(viewRecord))
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To do this we create an <code>appError</code> struct containing an
|
||||
<code>error</code> and some other fields:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error4.go" `/type appError/` `/STOP/`}}
|
||||
-->type appError struct {
|
||||
Error error
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Next we modify the appHandler type to return <code>*appError</code> values:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error4.go" `/type appHandler/`}}
|
||||
-->type appHandler func(http.ResponseWriter, *http.Request) *appError
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
(It's usually a mistake to pass back the concrete type of an error rather than
|
||||
<code>error</code>, for reasons to be discussed in another article, but
|
||||
it's the right thing to do here because <code>ServeHTTP</code> is the only
|
||||
place that sees the value and uses its contents.)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
And make <code>appHandler</code>'s <code>ServeHTTP</code> method display the
|
||||
<code>appError</code>'s <code>Message</code> to the user with the correct HTTP
|
||||
status <code>Code</code> and log the full <code>Error</code> to the developer
|
||||
console:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error4.go" `/ServeHTTP/` `/STOP/`}}
|
||||
-->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)
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
Finally, we update <code>viewRecord</code> to the new function signature and
|
||||
have it return more context when it encounters an error:
|
||||
</p>
|
||||
|
||||
<pre><!--{{code "progs/error4.go" `/func viewRecord/` `/STOP/`}}
|
||||
-->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
|
||||
}
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
This version of <code>viewRecord</code> is the same length as the original, but
|
||||
now each of those lines has specific meaning and we are providing a friendlier
|
||||
user experience.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It doesn't end there; we can further improve the error handling in our
|
||||
application. Some ideas:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>give the error handler a pretty HTML template,
|
||||
<li>make debugging easier by writing the stack trace to the HTTP response when
|
||||
the user is an administrator,
|
||||
<li>write a constructor function for <code>appError</code> that stores the
|
||||
stack trace for easier debugging,
|
||||
<li>recover from panics inside the <code>appHandler</code>, 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 <a href="defer_panic_recover.html">Defer, Panic, and Recover</a>
|
||||
article for more details.
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<b>Conclusion</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
312
doc/articles/error_handling.tmpl
Normal file
312
doc/articles/error_handling.tmpl
Normal file
@ -0,0 +1,312 @@
|
||||
<!-- Error Handling and Go -->
|
||||
|
||||
<p>
|
||||
If you have written any Go code you have probably encountered the built-in
|
||||
<code>error</code> type. Go code uses <code>error</code> values to
|
||||
indicate an abnormal state. For example, the <code>os.Open</code> function
|
||||
returns a non-nil <code>error</code> value when it fails to open a file.
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/func Open/`}}
|
||||
|
||||
<p>
|
||||
The following code uses <code>os.Open</code> to open a file. If an error
|
||||
occurs it calls <code>log.Fatal</code> to print the error message and stop.
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/func openFile/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
You can get a lot done in Go knowing just this about the <code>error</code>
|
||||
type, but in this article we'll take a closer look at <code>error</code> and
|
||||
discuss some good practices for error handling in Go.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>The error type</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <code>error</code> type is an interface type. An <code>error</code>
|
||||
variable represents any value that can describe itself as a string. Here is the
|
||||
interface's declaration:
|
||||
</p>
|
||||
|
||||
<pre>type error interface {
|
||||
Error() string
|
||||
}</pre>
|
||||
|
||||
<p>
|
||||
The <code>error</code> type, as with all built in types, is
|
||||
<a href="/doc/go_spec.html#Predeclared_identifiers">predeclared</a> in the
|
||||
<a href="/doc/go_spec.html#Blocks">universe block</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The most commonly-used <code>error</code> implementation is the
|
||||
<a href="/pkg/errors/">errors</a> package's unexported <code>errorString</code> type.
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/errorString/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
You can construct one of these values with the <code>errors.New</code>
|
||||
function. It takes a string that it converts to an <code>errors.errorString</code>
|
||||
and returns as an <code>error</code> value.
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/New/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
Here's how you might use <code>errors.New</code>:
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/func Sqrt/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
A caller passing a negative argument to <code>Sqrt</code> receives a non-nil
|
||||
<code>error</code> value (whose concrete representation is an
|
||||
<code>errors.errorString</code> value). The caller can access the error string
|
||||
("math: square root of...") by calling the <code>error</code>'s
|
||||
<code>Error</code> method, or by just printing it:
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/func printErr/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
The <a href="/pkg/fmt/">fmt</a> package formats an <code>error</code> value
|
||||
by calling its <code>Error() string</code> method.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It is the error implementation's responsibility to summarize the context.
|
||||
The error returned by <code>os.Open</code> formats as "open /etc/passwd:
|
||||
permission denied," not just "permission denied." The error returned by our
|
||||
<code>Sqrt</code> is missing information about the invalid argument.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To add that information, a useful function is the <code>fmt</code> package's
|
||||
<code>Errorf</code>. It formats a string according to <code>Printf</code>'s
|
||||
rules and returns it as an <code>error</code> created by
|
||||
<code>errors.New</code>.
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/fmtError/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
In many cases <code>fmt.Errorf</code> is good enough, but since
|
||||
<code>error</code> is an interface, you can use arbitrary data structures as
|
||||
error values, to allow callers to inspect the details of the error.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For instance, our hypothetical callers might want to recover the invalid
|
||||
argument passed to <code>Sqrt</code>. We can enable that by defining a new
|
||||
error implementation instead of using <code>errors.errorString</code>:
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/type NegativeSqrtError/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
A sophisticated caller can then use a
|
||||
<a href="/doc/go_spec.html#Type_assertions">type assertion</a> to check for a
|
||||
<code>NegativeSqrtError</code> and handle it specially, while callers that just
|
||||
pass the error to <code>fmt.Println</code> or <code>log.Fatal</code> will see
|
||||
no change in behavior.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As another example, the <a href="/pkg/encoding/json/">json</a> package specifies a
|
||||
<code>SyntaxError</code> type that the <code>json.Decode</code> function
|
||||
returns when it encounters a syntax error parsing a JSON blob.
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/type SyntaxError/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
The <code>Offset</code> 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:
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/func decodeError/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
(This is a slightly simplified version of some
|
||||
<a href="http://camlistore.org/code/?p=camlistore.git;a=blob;f=lib/go/camli/jsonconfig/eval.go#l68">actual code</a>
|
||||
from the <a href="http://camlistore.org">Camlistore</a> project.)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <code>error</code> interface requires only a <code>Error</code> method;
|
||||
specific error implementations might have additional methods. For instance, the
|
||||
<a href="/pkg/net/">net</a> package returns errors of type
|
||||
<code>error</code>, following the usual convention, but some of the error
|
||||
implementations have additional methods defined by the <code>net.Error</code>
|
||||
interface:
|
||||
</p>
|
||||
|
||||
<pre>package net
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
Timeout() bool // Is the error a timeout?
|
||||
Temporary() bool // Is the error temporary?
|
||||
}</pre>
|
||||
|
||||
<p>
|
||||
Client code can test for a <code>net.Error</code> 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.
|
||||
</p>
|
||||
|
||||
{{code "progs/error.go" `/func netError/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
<b>Simplifying repetitive error handling</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Consider an <a href="http://code.google.com/appengine/docs/go/">App Engine</a>
|
||||
application with an HTTP handler that retrieves a record from the datastore and
|
||||
formats it with a template.
|
||||
</p>
|
||||
|
||||
{{code "progs/error2.go" `/func init/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
This function handles errors returned by the <code>datastore.Get</code>
|
||||
function and <code>viewTemplate</code>'s <code>Execute</code> 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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To reduce the repetition we can define our own HTTP <code>appHandler</code>
|
||||
type that includes an <code>error</code> return value:
|
||||
</p>
|
||||
|
||||
{{code "progs/error3.go" `/type appHandler/`}}
|
||||
|
||||
<p>
|
||||
Then we can change our <code>viewRecord</code> function to return errors:
|
||||
</p>
|
||||
|
||||
{{code "progs/error3.go" `/func viewRecord/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
This is simpler than the original version, but the <a
|
||||
href="/pkg/net/http/">http</a> package doesn't understand functions that return
|
||||
<code>error</code>.
|
||||
To fix this we can implement the <code>http.Handler</code> interface's
|
||||
<code>ServeHTTP</code> method on <code>appHandler</code>:
|
||||
</p>
|
||||
|
||||
{{code "progs/error3.go" `/ServeHTTP/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
The <code>ServeHTTP</code> method calls the <code>appHandler</code> function
|
||||
and displays the returned error (if any) to the user. Notice that the method's
|
||||
receiver, <code>fn</code>, is a function. (Go can do that!) The method invokes
|
||||
the function by calling the receiver in the expression <code>fn(w, r)</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Now when registering <code>viewRecord</code> with the http package we use the
|
||||
<code>Handle</code> function (instead of <code>HandleFunc</code>) as
|
||||
<code>appHandler</code> is an <code>http.Handler</code> (not an
|
||||
<code>http.HandlerFunc</code>).
|
||||
</p>
|
||||
|
||||
{{code "progs/error3.go" `/func init/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To do this we create an <code>appError</code> struct containing an
|
||||
<code>error</code> and some other fields:
|
||||
</p>
|
||||
|
||||
{{code "progs/error4.go" `/type appError/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
Next we modify the appHandler type to return <code>*appError</code> values:
|
||||
</p>
|
||||
|
||||
{{code "progs/error4.go" `/type appHandler/`}}
|
||||
|
||||
<p>
|
||||
(It's usually a mistake to pass back the concrete type of an error rather than
|
||||
<code>error</code>, for reasons to be discussed in another article, but
|
||||
it's the right thing to do here because <code>ServeHTTP</code> is the only
|
||||
place that sees the value and uses its contents.)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
And make <code>appHandler</code>'s <code>ServeHTTP</code> method display the
|
||||
<code>appError</code>'s <code>Message</code> to the user with the correct HTTP
|
||||
status <code>Code</code> and log the full <code>Error</code> to the developer
|
||||
console:
|
||||
</p>
|
||||
|
||||
{{code "progs/error4.go" `/ServeHTTP/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
Finally, we update <code>viewRecord</code> to the new function signature and
|
||||
have it return more context when it encounters an error:
|
||||
</p>
|
||||
|
||||
{{code "progs/error4.go" `/func viewRecord/` `/STOP/`}}
|
||||
|
||||
<p>
|
||||
This version of <code>viewRecord</code> is the same length as the original, but
|
||||
now each of those lines has specific meaning and we are providing a friendlier
|
||||
user experience.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It doesn't end there; we can further improve the error handling in our
|
||||
application. Some ideas:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>give the error handler a pretty HTML template,
|
||||
<li>make debugging easier by writing the stack trace to the HTTP response when
|
||||
the user is an administrator,
|
||||
<li>write a constructor function for <code>appError</code> that stores the
|
||||
stack trace for easier debugging,
|
||||
<li>recover from panics inside the <code>appHandler</code>, 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 <a href="defer_panic_recover.html">Defer, Panic, and Recover</a>
|
||||
article for more details.
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
<b>Conclusion</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
115
doc/progs/error.go
Normal file
115
doc/progs/error.go
Normal file
@ -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() {}
|
53
doc/progs/error2.go
Normal file
53
doc/progs/error2.go
Normal file
@ -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() {}
|
60
doc/progs/error3.go
Normal file
60
doc/progs/error3.go
Normal file
@ -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() {}
|
71
doc/progs/error4.go
Normal file
71
doc/progs/error4.go
Normal file
@ -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() {}
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user