Sunday, November 6, 2011

An error-handling scheme with closures

Every time when a function returns we should test whether it resulted in an error: this can lead to repetitive and tedious code. Combining the defer/panic/recover mechanism with closures can result in a far more elegant scheme that we will now discuss. However it is only applicable when all functions have the same signature, which is rather restrictive. A good example of its use is in web applications, where all handler functions are of the following type:
                                         func handler1(w http.ResponseWriter, r *http.Request) { … }

Suppose all functions have the signature:     func f(a type1, b type2)
The number of parameters and their types is irrelevant.
We give this type a name:  fType1 = func f(a type1, b type2)
Our scheme uses 2 helper functions:
i)                     check: a function which tests whether an error occurred, and panics if so:

func check(err os.Error) { if err != nil { panic(err) } }


ii)                   errorhandler: this is a wrapper function. It takes a function fn of our type fType1 and returns such a function by calling fn. However it contains the defer/recover mechanism, outlined in § 13.3

func errorHandler(fn fType1) fType1 {
    return func(a type1, b type2) {
        defer func() {
            if e, ok := recover().(os.Error); ok {
log.Printf("run time panic: %v", err)
            }
        }()
        fn(a, b)
    }
}

When an error occurs it is recovered and printed on the log; apart from simply printing the application could also produce a customized output for the user by using the template package (§ 15.6).

The check() function is used in every called function, like this:

func f1(a type1, b type2) {
    …
    f, _, err := // call function/method
    check(err)
    t, err := // call function/method
    check(err)
    _, err2 := // call function/method
    check(err2)
    …
}

The main() or other caller-function should then call the necessary functions wrapped in errorhandler, like this:

func main() {
    errorHandler(f1)
    errorHandler(f2)
    …
}

Using this mechanism all errors are recovered and the error-checking code after a function call is reduced to check(err).In this scheme different error-handlers have to be used for different function types; they could be hidden inside an error-handling package. Alternatively a more general approach could be using a slice of empty interface as parameter and return type.

No comments:

Post a Comment