Tuesday, November 20, 2012

The Simplest Thing

I recently took part in a code dojo, using the scoring for bowling kata, where we were interested in intention revealing and expressive code. The rules set up were typical, TDD, rotating pairs, no verbal explanation to new pairs, no talking to your current pair. Same old rules, same old results, no surprises. It seems to me that if you want different code to come out of the activity you may need to frame the activity differently.

The same old results of TDD as practiced in the typical code dojo is not a good thing. Write a test and implement a solution to it as quickly as possible. Satisfies each test with the least number of characters possible and in the shortest time possible.

Short and quick lead to producing the easiest solution to come to mind, or perhaps simply the first solution that comes to mind, and basically the most primitive solution. By primitive I mean that the solution is implemented as directly as possible in the primitives of the language.

But it's TDD, the goal is to write the simplest solution. Not to be simple minded about your solution. This depends on defining simple of course, but in my experience that's a measure that should be applied to the result code, not the process that generates it. TDD is not about doing the simplest work that results in something that satisfies the test, it's about doing the hard work of delivering something which is simple and satisfies the test.

Simple becomes a set of principles, some with potential metric support, some more like rules of thumb. Simple implementations for me are those which can't be used wrongly. Simple implementations make explicit the important things, not merely the static things that are easy in whatever language you're using but also the processes and dynamic properties of the system even if you have to twist the language to make them explicit. Simple often means hiding and wrapping primitives so that they don't interact and that the myriad things they might do but shouldn't are cut off.

Simple very often means qualities that are not captured in tests, simple is the judgement of a professional saying that the passing test is just the beginning, diligent professional conduct requires me to attend to these other values.

What I found was that we could have written better code faster in one of two ways, (the code I wanted had a layer abstracting frames, and chaining them together as the game plays). If I'd thought long and hard I could have presented the tests in an order that made the abstraction an immediate and obvious benefit. Or, trusting our professional intuitions and being principled, we could have implemented the highest interface in terms that corresponded to the description of the problem, that DDD approach would have automatically led to a frames based abstraction because that's how the domain experts talk about the problem. Or I could have trusted my little rules of thumb: wrap your primitives, abstract away the language. Perhaps if I'd been able to talk to my partner we'd've been able to establish that quality foundation, but once people started rotating the pressure was on to just follow suit on poor beginnings.

I don't think I've ever written code where an implementation that jumped from the highest level interface straight into code using raw language primitives was the best result. When working with code like that, grief and suffering are typical.

TDD is a process that helps me write good professional code by freeing me up from tracking whether my code is functionally correct, instead I can give more energy to all the other professional values that need to be embodied in my code. But this is my heresy, TDD says nothing about the quality of the implementation, but a poor quality implementation is a terrible cost a company.

The problem with that common type of TDD kata was that it punishes us for attending to code quality and architectural concerns (remember those other things in XP: coding standards and the system metaphor, they acknowledged that TDD needs balance).

So how do you get good, expressive, professional, intention revealing code? No guarantees, but you need at least to be able to identify good from bad, you need the boldness to say no to the bad, you need to be allowed to implement better than the poorest conceivable solution.

TDD is a game, it rewards getting stuff done quickly and crudely, it also rewards getting things that function. People who want the process to deliver quality are sadly disappointed, people who like to tick things off and generally score points can use it to churn out piles of muck. People who feel that quality matters often end up ignoring it when forced to practice with rules mavens and game players. What's really need is counter balancing games that rewards the other qualities of good code, qualities that have great business value but may not be easily measured like maintainability or job satisfaction. Such games may look like leader boards showing the latest model code for the team to strive to excede, perhaps as an ongoing vote or outcome from retrospectives. It may be a checklist of points that need to be judged as part of code review. TDD doesn't stand alone.

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.