Error Handling

Go functions often return os.Error objects, which include a String() method.  This allows the caller to check for an error condition as follows:

if err := api.Func(); err != nil {
    // handle err
    fmt.Println("err: " + err.String())
}

By convention, the err pointer will be nil unless there is an error condition.

Bad Usage of Errors

You should avoid writing code like this:

err1 := api.Func1()

if err1 != nil {
   fmt.Println("err: " + err.String())
   return
}

err2 := api.Func2()

if err2 != nil {
...
   return
}
...

... or like this:

var err os.Error

if good == false {
    err = os.NewError("things aren't good")
}

if bad == true {
    err = os.NewError("things are bad")
}

if err != nil {
    fmt.Println("something's wrong: " + err.String())
}

In the first case, the errors are reported (by printing them) with if-statements scattered throughout the code.  With this pattern, it is hard to tell what is normal program logic and what is error checking/reporting.  Also notice that most of the code is dedicated to error conditions at any point in the code.

In the second case, it's hard to tell where errors are coming from and to control when to stop and report an error.  It's possible to use chained and nested if-else blocks to stop function execution when an error occurs, but this is what the return statement is for, after all!

Only Return Errors

As a general rule, never assign an error to a variable except within a compound if-statement:

if err := api.Func1(); err != nil {...}   // very acceptable

Don't report errors when you encounter them; rather, return an appropriate os.Error object and defer handling to the caller:

func foo (good, bad bool) os.Error {
    if good == false {
        return os.NewError("things aren't good")
    }

    if bad == true {
        return os.NewError("things are bad")
    }

    return nil
}

If you must check for errors and report them in the same function, you can wrap your error conditions in a closure:

func httpRequestHandler(w http.ResponseWriter, req *http.Request) {

    err := func () os.Error {

        if req.Method != "GET" {
            return os.NewError("expected GET")
        }

        if input := parseInput(req); input != "command" {
            return os.NewError("malformed command")
        }
    } ()

    if err != nil {
        w.WriteHeader(400)
        io.WriteString(w, err)
        return
    }

    doSomething() ...
...

This approach clearly separates the error checking, error reporting, and normal program logic.

Comments