I’ve been doing some radically different design after creating Dire a couple of months ago. My stance on local exception handling departs from what almost anyone else is doing right now - and there’s been some interesting repercussions because of it.
The style of design that I’ve been pushing is one of airtight separation of concerns. Clojure’s runtime capabilities have allowed me to transparently alter control flow for exception handlers, assertions, and hooks. The beauty is that all the machinery to perform the aforementioned logic can be grouped into their own files - and loaded only when you want them. It’s like dependency injection at a far more flexible level.
After a few weeks of applying these concepts to a larger code base, I’m noticing an interesting phenomena at the code level. Rich Hickey noted that design is about taking the problem apart as much as possible. My conjecture is that when you’re aggressive about dismantling the problem space, the code in each module will display similar visual characteristics.
Here are excerpts of some files in the (closed source) project I’m working on. I unimaginatively changed the functions to be about cows and chickens - but you get the point. We’re concerned about structural appearance - not what the code actually does.
This first one is a file whose only concern in the world is logging. Notice how all the forms pretty much look the same.
Here’s a bit of a file whose only concern is reacting to thrown exceptions. They all look pretty similar!
Yet another instance - a file whose only job is to strip information off incoming API requests and pass them along for further destructuring:
Last example. All of the Datomic queries get their own file:
All of the functions in each file are participating at the same level of abstraction, and hence look roughly the same. There’s no surprise conditionals or loops any of them. They all flow together, and can be reasoned about with ease. This is a serious reduction in cognitive complexity.
If I could make an analogy, it’s a bit like a pipe organ. All of the pipes have roughly the same shape, but some of them are of different length and width. The groups of pipes correspond to the Clojure files. Each pipe represents a function. Very similar to those around it, but just a little different to make it useful.
Keep in mind, these were excerpts from a few files in a much larger project. I’m having a blast with this code base, and I think what I’ve pointed out is a green light that the design is solid.
To summarize, my conjecture is this: Thoughtful abstractions in design will lead to code that is visually similar in each module. If you notice this in your code base, you’re probably doing a great job with the design.
Hit me up at @MichaelDrogalis with your thoughts. (It’s a conjecture - I could be totally wrong!)
Also, give Dire a try for logging in your system. I’m a little biased since I’m the author!
I was going to write a nice narrative about how I wrote some beautiful code, but I’m just going to show it to you. I’d hate for my message to get drowned out in some boring text.
You might recall that I wrote Dire a few months ago. I’m going to show you how to separate logging, preconditions, postconditions, and exception handling in an extremely elegant way. These concerns stay out of your application logic. And I do mean out.
Here’s some trivial code that gets the point across. Notice that all of these concerns get their own file!
Here’s why the above design is kind of incredible. No one wants to see logging output during tests. In your test cases, simply don’t require dire-example.logging! That’s it! Don’t want preconditons? Don’t import the file! This is design elegance for Clojure on a new level. Tweet me at @MichaelDrogalis with your thoughts.
Picture this. You’ve just finished writing a nice, clean function that clearly solves a single problem. It’s beautiful. It’s isolated. You’re happy.
You poke around at it for a while and realize some viable inputs make it explode. Part of you wants good error handling to keep the end user sane, but another part of you instinctively doesn’t want to wrap that beautiful code in another form. The code is pure as-is. With a mildly nauseous feeling in your stomach, you catch the exceptions and handle them inside your function. At least your end user won’t hate you, I guess.
I’ve had the above experience too many times. And until recently, I didn’t know what to do about it. I believe this is in part why Clojure’s infamous for poor error messages. It’s a pain to pollute code that solves your problem with error handlers. It’s worth taking the time to explain what bugs me so much about try/catch, not just in Clojure, but in any language that takes the same approach to error handling:
Now that I’ve spelled out my problem with traditional error handling in Clojure, we can move onto the solution. In Rich’s talk The Language of the System, he directs the audience to read Joe Armstrong’s (the creator of Erlang) dissertation on error handling. Rich said something to the effect that it takes error handling and completely turns it on its head. I was curious, so I gave it a read. If you haven’t read it before, check it out. It’s a quick read filled with a lot wisdom.
In his paper, he lays out 4 main points that Erlang’s error handling was built around to provide incredible fault-tolerancy (page 106):
Points 2 and 3 are very specific to Erlang, so I won’t touch on those in this post. Read the paper to get a better idea of his philosophy. Points 1 and 4, however, made great candidates for the kind of functionality I wanted to bring to Clojure.
The goals for the functionality I wanted to introduce can be enumerated by a few points:
These goals match up as solutions to the problems I listed about try/catch earlier. Cool, sounds like I’m on the right track.
So I sat down and wrote the darn thing. I called it Dire (GitHub source) because I felt that we could use it that badly. Let’s see some code:
There’s a concise API for adding catch clauses, finally clauses, and even pre and post conditions. There’s also non-bang (!) variants that don’t mutate your functions (they’re the second bunch of examples listed on GitHub).
Speaking of pre and post conditions. You know what the problem is with assertions of that nature in virtually all languages? They only offer error detection! Error handling is a two part mechanism. Part 1 is detection: check. What we’re missing is part two: Reaction. When things have been detected to have gone awry, what are we going to do about it? Dire let’s you react with arbitrary defined functions. I think turning off assertions when we’re not in development mode is a bad idea. We’re throwing away the chance to gracefully conduct our program when it’s in danger.
So please, let me know what you think. Was this a worthwhile effort? Tweet me at @MichaelDrogalis
Designing systems that obey the Open/Closed principle is tough. Here’s a nugget of wisdom about using topics to create a system that’s more fluid in terms of extensibility. I’ll demonstrate in Clojure.
A topic is a type of message queue where all subscribers consume every message. An example of this in the small is Clojure’s juxt function.
What happens when your functions in juxt are computationally expensive? What happens if you constantly add new functions? A topic makes great sense for both of these cases. It allows you to add new functions to the computation without having to modify the innards of any existing code. Just add a new subscriber, and away you go.
The following example is rather contrived, but I wanted to keep it as simple as possible to get the point across. I used Clamq ontop of ActiveMQ. As you read, visualize how simple it is to add another computation.