Skip to content

The idea behind top down development

marick edited this page Feb 27, 2013 · 2 revisions

One way of coding is what Abelson and Sussman have called "programming by wishful thinking". You begin to write some tough function. Partway through, you see a piece of the problem that could nicely be solved by another function. Instead of writing that second function, you just assume that someone else already did. You'll solve your problem in terms of it, then go find it. When it turns out you can't find it, you write it. In the process, you might have an idea for a third function that you hope someone else already wrote.

And so on—until all the functions you need actually have already been written.

An awkwardness with this kind of coding is that you can't actually run the original function until all the work has been done. (You have to populate a whole dependency tree before you can run its root.) For some of us, that's a long time to work without seeing concrete forward progress.

Midje supports this top-down style with prerequisites. A prerequisite documents the relationship between one function and another (which need not be written yet). As an example, suppose that your job is to read old-style LED numbers represented like this:

(fact
  (account-number ["    _  _     _  _  _  _  _ "
                   "  | _| _||_||_ |_   ||_||_|"
                   "  ||_  _|  | _||_|  ||_| _|"])
  => "123456789")

Call that input a "parcel". (I'm thinking of it as something like a two-dimensional parcel of land on a map.) You might reason that you can solve the problem by getting the digit for each character, then putting them all together into a string. That could look like this:

(unfinished digit-parcels digit)

(fact "an account number is constructed from character parcels"
  (account-number ..parcel..) => "01"
  (provided
    (digit-parcels ..parcel..) => [..0-parcel.. ..1-parcel..]
    (digit ..0-parcel..) => 0
    (digit ..1-parcel..) => 1))

There are a couple of interesting things here. First, the relationship between account-number and two helper functions is laid out before the helpers are defined. We've broken a larger problem into three smaller problems, one of which (how results are assembled) can be coded and checked immediately.

The other interesting thing is that we've abstracted away from a concrete data representation by using metaconstants. By doing that, we've documented that account-number doesn't actually depend on the "shape" of the data at all: it hands that issue off to digit-parcels. When coding in this style, you can often go quite a way before having to commit to specific data structures.

Stepping up a level of abstraction, I find that using prerequisites lets me (but does not require me to!) think of a program as a logical structure. It is a collection of functions annotated with checkable facts. Moreover, those functions are connected by dependency relationships that are documented with facts:

Interrelationships

Clone this wiki locally