Tuesday, November 20, 2012

TDD: Making Bricks or Building Houses

TDD has shown itself to be amazingly productive practice. With it's tight feedback cycle it's great for learning. Having very specific disciplines and methods mean some excellent tooling has been developed to support it. It's a natural fit for the small stories and task we favour for iterative development. It's strengths are also it's weakness, specifically, it drives an intense focus on the smallest units of development. And as time goes by it's becoming coming coupled ever more tightly to other practices that also focus down at small units of development.

That plays to a very human bias, substituting a hard question with an easier one. We puzzle of over a tricky question: are we making good software? TDD leads us to answer yes by substituting different questions: Have we written good units? Or, have we worked hard with sophisticated tools?

Good units do not make a good program any more than good bricks make a good wall or good musical instruments make a good orchestra.

The general tide in TDD is to keep looking down at small units. This is reinforced by mocking libraries and test frameworks and a culture that favours unit tests over test that aggregate units, (no one questions having a unit test but it is frowned on to have higher levels tests without unit tests, and slow running aggregate tests may well get axed in favour of a faster running test suite).

TDD teaching methods in some quarters have had a similar bias, discouraging conversation and discouraging looking further ahead than the next test. Further reinforcement comes from common patterns of pairing, ping-ponging for example. Likewise practices that focus on stories with little context, either chunked into iterations or striving to have just a few upcoming and potentially unrelated stories in a stream. Add to that developers who take pride in starting what has been presented as the next story without further consideration.

All these things make it easier to focus on the finest grain of problem solving. But practiced without balances they take focus away from the system as a whole being delivered, hiding synergies and commonalities and large scale structure. Indeed it can cause great frustration when someone practiced with the tools and techniques of TDD plows on into chaos dragging in their wake a partner who wanted more time to reflect or consider their work in a context broader than the current test or story.

In all this the mainstream of TDD risks being a "greedy reductionist". Greedy reductionists are a parody in science that says everything can be described in terms of the lowest level realties, basic particles etc, and that higher order abstractions are needless. Dennett gives an example that illustrates the problem: imagine a calculator that returns 3 when you press 1 + 1, everything else it does just fine; you can describe the operation of this calculator in terms of electrons and semiconductors and completely fail to explain it's most interesting and distinctive behaviour. TDD when focused on the smallest granularity risks the same mistake.

Focusing on small problems is called narrow framing and it can a powerful tool. In many situations the human mind instinctively, perhaps unavoidably, resorts to narrow framing. But narrowly framing problems can be deeply misleading about what makes a good decision. Our tendencies to over spend to guarantee a win or to avoid loss can be amplified in damaging ways if we don't take the big picture into account. Treating the parts of decomposed problems in isolation can exaggerate biases: TDD works against balancing our decisions in the context of the greater whole.

TDD helps us build good bricks, but it doesn't really say much about building good houses. Of course it's hard to build a good house from bad bricks, just there's a lot more that we fail to attend to if we only do TDD.

When the world was introduced to TDD it was in the context of system and practices that balanced those weaknesses. I personally have become very wary of programming with anyone people who don't actively practice techniques to balance the focus on the fine grain that is inherent in TDD.

There are many possible practices that can provide the balance; XP as a system has several built in:

The embedded customer and the system metaphor both help focus us on the whole system and it's values as a product, (sadly, they've often been the things left behind in emerging hybrid and customised development methodologies).

Code standards can be used to improve the focus on the whole and on code that works together as a coherent whole. One style of code standard identifies those areas of the code that embody the values at stake, (not just classes functions or files, but whole modules packages or pages that work as cohesive systems). Another style of coding standard uses guiding aphorisms, for example: business logic and events should be represented explicitly as objects; fan out should be limited; always wrap third party code; always wrap primitives and native library code. That last is can be argued but ties into the DDD principle of ubiquitous language: write layers and abstractions so that you are implementing in terms that reflect your users language. The quest for an over arching language rooted in the users expression of the problem domain, and having that language pervade the implementation can be a powerful tool for creating a coherent whole.

Perhaps one of TDDs greatest strengths is that it's easier to do than the counterbalancing practices and we naturally give energy to the things we do well or that are at least easy to do. Real growth, however, requires giving attention to what we do poorly. So the next time you go to do some deliberate practice or try to formalise your team's process, have the boldness to ask yourself what you need to do to improve or to cover for your weaknesses rather than just shining a light on your strengths.

No comments:

Post a Comment