I’ve recently been trying to improve parts of uSwitch.com’s Clojure pricing code for the last few days. Mostly this has been to add error handling that provides a form of graceful degradation.
The majority of the application deals with pricing the various consumer Energy plans that people can pick from. Although we try and make sure everything validates before it hits the site we’ve found a few weird problems that weren’t anticipated. This can be bad when errors prevent the other results from being returned successfully.
Ideally, we’d like to be able to capture both the errors and the plans we couldn’t price; clients can use the data we have and know what’s missing.
I’m sure I had read an interview in which Rich Hickey said how Clojure’s dynamism helped deal with error handling in a clean and safe way. I can’t find a reference (so it’s possible I made that up), but, I then was looking through The Joy of Clojure and found this:
“there are two directions for handling errors. The first … refers to the passive handling of exceptions bubbling outward from inner functions. But built on Clojure’s dynamic Var binding is a more active mode of error handling, where handlers are pushed into inner functions.”
The code below shows a trivialised version of the problem- a calculation
function that might raise an error part-way through the collection.
Instead, we can use binding
and an error handling function to dynamically handle the problem and push this down the stack.
Note that we’re using declare
to define our error-handler
Var
, and, that we have to mark it as :dynamic
as we’re going to be dynamically binding it.
The next stage is to progress from just returning nil
and to capture the errors whilst the records are being processed. We can use Atoms to hold the state and then append more information as we flow through.
The above example introduces an errors
atom that will capture the responses as we map across the sequence. But it definitely now feels a little icky.
- We use
do
to both add the error to our list of errors, and returnnil
. - Because we’ve now introduced side-effects we must use
doall
to realise the sequence whilst thehandle-error
function is rebound.
I have to say, I’m not too sure whether I prefer this more dynamic approach or a more traditional try...catch
form.
The dynamic behaviour is definitely very cool, and the authors of The Joy of Clojure say that “using dynamic scope via binding is the preferred way to handle recoverable errors in a context-sensitive manner.” so I’m very inclined to stick with it. However, I think I’ve managed to make it less nice: what originally looked like a neat use of closure to capture errors in a safe way now seems less good? I’d love to hear what more experienced Clojurists make of it.
Other references: