9.6 KiB
11.1 Error handling
Go's major design considerations are rooted in the following ideas: a simple, clear, and concise syntax (similar to C), statements are explicit and don't contain hidden any hidden or unexpected things. Go's error handling scheme reflects all of these principles in the way that it's implemented. If you're familiar with the C language, you'll know that it's common to return -1 or NULL values to indicate that an error has occurred. However users who are not familiar with C's API will not know exactly what these return values mean. In C, it's not explicit whether a value of 0 indicates success of failure. On the other hand, Go explicitly defines a type called error for the sole purpose of expressing errors. Whenever a function returns, we check to see whether the error variable is nil or not to determine if the operation was successful. For example, the os.Open function fails, it will return a non-nil error variable.
func Open(name string) (file * File, err error)
Here's an example of how we'd handle an error in os.Open. First, we attempt to open a file. When the function returns, we check to see whether it succeeded or not by comparing the error return value with nil, calling log.Fatal to output an error message if it's a non-nil value:
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
Similar to the os.Open function, the functions in Go's standard packages all return error variables to facilitate error handling. This section will go into detail about the design of error types and discuss how to properly handle errors in web applications.
Error type
error is an interface type with the following definition:
type error interface {
Error() string
}
error is a built-in interface type, we can / builtin / pack below to find the appropriate definition. And we have a lot of internal error is used inside the package packet errors following implementation structure errorString private
error is a built-in interface type. We can find the corresponding definition in the builtin package below. We also have a lot of internal packages using the error in a private structure called errorString, which implements the error interface:
// errorString is a trivial implementation of error.
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
You can convert a regular string to an errorString through errors.New in order to get an object that satisfies the error interface. Its internal implementation is as follows:
// New returns an error that formats as the given text.
func New(text string) error {
return &errorString{text}
}
The following example demonstrates how to use errors.New:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// implementation
}
In the following example, we pass a negative number to our Sqrt function. Checking the err variable, we check whether the error object is non-nil using a simple nil comparison. The result of the comparison is true, so fmt.Println (the fmt package calls the error method when dealing with error calls) is called to output an error.
f, err := Sqrt(-1)
if err != nil {
fmt.Println(err)
}
Custom Errors
Through the above description, we know that a go Error is an interface. By defining a struct that implements this interface, we can implement their error definitions. Here's an example from the JSON package:
type SyntaxError struct {
msg string // error description
Offset int64 // where the error occurred
}
func (e * SyntaxError) Error() string {return e.msg}
The error's Offset field will not be printed at runtime when syntax errors occur, but using a type assertion error type, you can print the desired error message:
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
}
It should be noted that when the function returns a custom error, the return value is set to the recommend type of error rather than a custom error type. Be careful not to pre-declare variables of custom error types. For example:
func Decode() *SyntaxError {
// error, which may lead to the caller's err != nil comparison to always be true.
var err * SyntaxError // pre-declare error variable
if an error condition {
err = &SyntaxError{}
}
return err // error, err always equal non-nil, causes caller's err != nil comparison to always be true
}
See http://golang.org/doc/faq#nil_error for an in depth explanation
The above example shows how to implement a simple custom Error type. But what if we need more sophisticated error handling? In this case, we have to refer to the net package approach:
package net
type Error interface {
error
Timeout() bool // Is the error a timeout?
Temporary() bool // Is the error temporary?
}
Using type assertion, we can check whether or not our error is of type net.Error, as shown in the following example. This allows us to refine our error handling -if a temporary error occurs on the network, it will sleep for 1 second, then retry the operation.
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
continue
}
if err != nil {
log.Fatal(err)
}
Error handling
Go in the error handling on the use of the C check the return value similar manner , rather than most other mainstream languages used in unexpected ways , which caused a code written on a big disadvantage : error handling code redundancy , for this kind of situation is that we detect function to reduce reuse similar code .
Look at the following example code:
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)
}
}
The above example shows a template to get data and call has detected an error when an error occurs , call a unified processing function http.Error, returned to the client 500 error code and display the corresponding error data . But when more and more HandleFunc joined, so that error-handling logic code will be more and more, in fact, we can customize the router to reduce the code ( realization of the ideas you can refer to Chapter III of the HTTP Detailed ) .
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)
}
}
Above we defined a custom router , and then we can adopt the following approach to registration function :
func init() {
http.Handle("/view", appHandler(viewRecord))
}
When requested /view when we can become as logical processing code, and the first implementation has been compared to a lot simpler .
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)
}
The above example error handling when all errors are returned to the user 500 error code, and then print out the corresponding error code , in fact, we can put this definition more friendly error messages when debugging is also convenient positioning problem, we can custom return types of errors :
type appError struct {
Error error
Message string
Code int
}
So that our custom router can be changed as follows :
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)
}
}
Such custom error after modifying our logic can be changed as follows :
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
}
As shown above, in our view when you can visit according to different situations for different error codes and error messages , although this first version of the code and the amount is almost, but this show is more obvious error , the error message prompts more friendly , scalability is also better than the first one .
Summary
In programming , fault tolerance is a very important part of the work , in Go it is achieved through error handling , error although only one interface, but it can have many variations but we can according to their needs to achieve different treatment Finally introduce error handling scheme , we hope that in how to design better programs on the Web error handling to bring some ideas .
Links
- Directory
- Previous section: Error handling, debugging and testing
- Next section: Debugging by using GDB