1. 程式人生 > >Error handling and Go

Error handling and Go

12 July 2011

Introduction

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 discussed in the Go FAQ, but it's the right thing to do here because ServeHTTP is the only place that sees the value and uses its contents.)

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:

  • give the error handler a pretty HTML template,
  • make debugging easier by writing the stack trace to the HTTP response when the user is an administrator,
  • write a constructor function for appError that stores the stack trace for easier debugging,
  • recover from panics inside the appHandler, logging the error to the console as "Critical," while telling the user "a serious error has occurred." This is a nice touch to avoid exposing the user to inscrutable error messages caused by programming errors. See the 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.

相關推薦

19 Error handling and Go

art internal acc writer generate cloud inspect new distinct Error handling and Go 12 July 2011 Introduction If you have written any Go c

Error handling and Go

12 July 2011 Introduction If you have written any Go code you have probably encountered the built-in error type.

coverity&fortify1--Poor Error Handling: Overly Broad Catch

hang htm 特殊 處理方法 nbsp class sql cep err 1.描述:   多個 catch 塊看上去既難看又繁瑣,但使用一個“簡約”的 catch 塊捕獲高級別的異常類(如 Exception),可能會混淆那些需要特殊處理的異常,或是捕獲了不應在程序中

[譯]Javascript中的錯誤信息處理(Error handling)

java https ror 信息處理 esc execute 函數 丟失 youtube 本文翻譯youtube上的up主kudvenkat的javascript tutorial播放單 源地址在此: https://www.youtube.com/watch?v=PMs

[Selenium+Java] SSL Certificate Error Handling in Selenium

abi ges git ssl cor bus cti inter rri Original URL: https://www.guru99.com/ssl-certificate-error-handling-selenium.html SSL Certificat

21 JSON and Go

with pick pat change nco previous lar ont mes JSON and Go 25 January 2011 Introduction JSON (JavaScript Object Notation) is a simple dat

解決boot2docker - docker Error: client and server don't have same version

since I just updated docker to 1.1.0 I get Error response from daemon: client and server don't have same version (client : 1.13, server: 1.12)

轉載 -- 構建健壯的Nodejs應用:錯誤處理 Building Robust Node Applications: Error Handling

  Building Robust Node Applications: Error Handling https://strongloop.com/strongblog/robust-node-applications-error-handling/   by&

golang 系列 (四) defer、error、panic, go語句

defer語句 defer 語句僅能被放置在函式或方法中。defer 不能呼叫內建函式以及標準庫程式碼包 unsafe 的函式. // 讀取檔案, 轉換為 byte 陣列 func readFile(path string) ([]byte, error) {

【文藝學生】Learning with exploration, and go ahead with learning. Let's progress together! :)

文藝學生 Learning with exploration, and go ahead with learning. Let's progress together! :)

Error Handling Operators

CatchError 從一個錯誤事件中恢復,將錯誤事件替換成一個備選序列 let sequenceThatFails = PublishSubject<Strin

《程式碼大全》之錯誤處理技術(Error-Handling Techniques)

1、返回中立值        處理錯誤資料的最佳做法就是繼續執行操作並簡單地返回一個沒有危害的數值。 2、換用一下正確的資料        在處理資料流的時候,有時只需返回下一個正確的資料即可。 3、返回

RxJava 錯誤處理操作符(Error Handling Operators)

RxJava系列教程: 一般來說,Observable不會拋異常。它會呼叫 onError 終止Observable序列,以此通知所有的觀察者發生了一個不可恢復的錯誤。 但是,也存在一些異常。例如,如果 onError 呼叫失敗了,Observable

Swift Course: Error Handling Fundamentals

Handling ErrorsWhen an error is thrown, some surrounding piece of code must be responsible for handling the error — for example, by correcting the problem,

Error Handling in Golang

What are errors? Errors indicate an abnormal condition in the program. Let's say we are trying to open a file and the file does not exist

Namespaces and Go Part 1

Linux provides the following namespaces and we will see how we can demonstrate these with Go Namespace Constant Isolates Cgroup

Namespaces and Go Part 3

In Part 2 we executed a shell with modified hostname using UTS namespace. In this article, we will explain how we can use PID and Mount namespaces. By is

Namespaces and Go Part 2

In Part 1 of Namespace article series ,we were unable to set hostname from a shell even though the user was root That program was missing UTS namespace w

RxJava 學習筆記(九) --- Error Handling 錯誤處理操作

1. onErrorReturn —> 指示Observable在遇到錯誤時發射一個特定的資料 onErrorReturn方法返回一個映象原有Observable行為的新Obse