Beware Choosing the Most Complex Tool for the Job

I once saw a TV show about competing groups of archeologists trying to demonstrate how the ancient Egyptians raised stone obelisks weighing hundreds of tons.

One group of archeologists built a complex apparatus involving a wooden frame and lots of rope. It looked impressive, but it didn’t work.

The other team built a sand pit, dragged the obelisk to the top, and gradually removed sand from below the obelisk until it reached its final position. Simple, unglamorous, and it worked on the first try. (See the whole series of photos.)

The leader of the wooden-frame group admitted they were mistaken in basing their design on the most complex ancient technology they could find, sailing ships. Instead, he said, “We should have asked, ‘What is the simplest way they could have done it?'”

***

From the ancient Egyptians, jump forward about four thousand years to me, sitting at my computer, writing a new testing framework for Clojure. I was excited about the new Clojure features datatypes and protocols. I based my whole framework around them. It was a beast. Lots of weird edge cases and complex interactions that were hard to reason about and even harder to debug. Datatypes and protocols may be a powerful tool, but that doesn’t always make them the right tool.

After my fourth or fifth rewrite of Lazytest, I started asking myself, “What is the simplest way I could do this?”

The answer was staring me in the face. Clojure is a functional language (mostly). What’s the simplest way to do anything? Functions!

All of a sudden, complexity started to fall away. My protocols, most of which only had a single method anyway, became ordinary functions. My typed data structures became ordinary maps. The code shrank by many lines, and it was vastly easier to understand. Even better, I discovered new possibilities in the simpler design.

Functions are a fantastic abstraction because they can be composed. In the new Lazytest, everything is a function: test cases, test suites, and contexts. The RSpec-like describe macros are still there, but they’re simpler. They do what macros are supposed to do: provide a convenient syntactic layer over functional definitions, not define a completely new language.

I haven’t completely finished the new API — fixtures, now renamed back to “contexts,” are not supported yet. But I’m much happier with this version than with the old one. I actually feel like this is something I’d be willing to release soon. So give it a spin and tell me what you think.

3 thoughts on “Beware Choosing the Most Complex Tool for the Job”

  1. A superb maxim that cannot be understated. I feel that, in any new endeavor I’ve ever started, that the obviously novice mistakes everyone made (including me) were to make things more complex than necessary. Never truer than in programming, especially in functional styles.

    Thanks for an interesting post and all your work on Lazytest.

  2. > All of a sudden, complexity started to fall away. My protocols, most of which only had a single method anyway,
    > became ordinary functions. My typed data structures became ordinary maps. The code shrank by many lines,
    > and it was vastly easier to understand. Even better, I discovered new possibilities in the simpler design.

    Totally agree. This is exactly what I experienced when re-writing SQLRat: http://bitbucket.org/kumarshantanu/sqlrat/src

  3. Hi Stuart,

    Using Lazytest and loving it so far :)
    I’ve got a question that I wanted to run by you.

    I want to setup a stateful context (lets say a db connection) and then define constants for some data extracted from the db, that I will use in a collection of tests (to stay dry).

    I’ve tried the combination of (with (x) (given [y (z @x]) (it …)) but it does not work (illegal state exception). examining the source, it seems that givens are evaluated before contexts (at macro expand time) so @x is unknown… is this correct? how should I go about resolving this issue?

    Should/can I use nested contexts? Not sure if context local bindings will be available in the test case (in the doc, contexts are illustrated with print statements…), and using another stateful context for a constant does not feel right.

    Many thanks for the cool tool you’ve crafted.
    I apologize if this is not the right place to post.

    Best

    J

Comments are closed.