A blog by Gary Bernhardt, Creator & Destroyer of Software

Specificity, Faking It, and Making It

31 May 2010

I recently asked this question on Twitter:

When I've faked it, but not yet made it, is it legit to push and pull specificity around?

Clearly, this makes sense to almost no one. In the spirit of The Tar Pipe, I present a step-by-step explanation of what this question means:

Faking It and Making It

Faking it is when, during TDD, you write something stupid to make a test pass. When you write the first fibonacci sequence test, for fib(0) == 0, the body of your fib function will say return 0. That's faking it.

You continue to write tests for functionality, then fake it to make them pass. But after each red-green cycle comes the required refactoring stage. As soon as your tests pass, you must remove any duplication. Don't blame me for this; it's one of the four elements of simple design.

Often, removing duplication means combining two special cases into a general case. For example, two special-cased checks for fib(0) == 0 and fib(1) == 1 can be collapsed into a single statement that passes both tests: return n.

After enough write-test-then-fake-it-then-refactor iterations, the class will arrive at a true implementation of its desired behavior. That's making it: there's no longer any faking. Or, in some cases, what looked like faking turned out to be the basis case of the class's behavior. In either case, the behavior has been made.

Defining Specificity

Details about how a class does its job are specificity. For example, rule 1 in Conway's game of life is "any live cell with fewer than two live neighbors dies, as if caused by underpopulation." You could write your game of life evolution function with that in mind:

    def evolve(world):
        for cell in world.cells:
            if cell.neighborhood.live_cell_count < 2:
                cell.die()
        ...

That's some serious specificity, though: the evolution algorithm gets very specific about the rules of evolution. A lower-specificity version of the function wouldn't talk about numbers at all:

    def evolve(world, rules):
        for cell in world.cells:
            if rules.cell_should_die(cell):
                cell.die()

The responsibility of knowing the specific rules has been moved out into a rules object, allowing this function to focus on its core responsibility of applying the rules to the world.

However, there's still more specificity that can be pulled out. Right now, the function asks the rules whether each cell should live, then conditionally tells the cell to die based on the answer. It would be better to tell the rules engine to do its thing directly, rather than asking:

    def evolve(world, rules):
        for cell in world.cells:
            rules.apply(cell)

The specificity about the relationship between rules and cells has been removed, making the function more abstract. Equivalently, the fan-out of evolve has decreased by one. Or, the coupling between evolve and rules has decreased from data coupling to message coupling (message coupling is weaker). Or, mumble mumble connascence mumble.

Pushing and Pulling Specificity

During code retreats, Corey Haines likes to challenge attendees to move specificity out into the test. This is what he means: move low-level details out of your system and into your test, making the system more abstract.

In our example above, we moved specificity about how cells live and die out of the production code. Depending on how we made that change, that specificity might continue to exist, but get moved into the test code instead. For example, in Ruby, the test code could pass a block that implements the actual steps. Ultimately, those details will go in a lower-level class that the current one depends on.

Pushing and pulling specificity is about moving implementation details around. Many people, when told about isolated unit testing, complain that it will lead to rigid, unchangeable definitions of the implementation. That's not the case, and this is one of the reasons – a TDDer practicing isolationism will learn how to move specificity out of necessity.

(I've written and deleted several paragraphs about the differences between pushing and pulling specificity, but haven't come up with anything I'm comfortable saying in public. You'll have to come up with your own ideas about the important differences, if any, between pushing and pulling.)

The Question at Hand

Now, we can get back to my original question:

When I've faked it, but not yet made it, is it legit to push and pull specificity around?

In other words, when I've not yet TDDed the full system – when it still contains faked-out behavior – is it safe, desirable, morally acceptable, etc., etc. to push and pull specificity to and from other classes?

Well, probably. But that's really not the point, is it?