Test-first fundamentalism is like abstinence-only sex ed: An unrealistic, ineffective morality campaign for self-loathing and shaming.
In effect, Hansson echoes Jim Coplien’s complaints in Why Most Unit Testing Is A Waste. Coplien is a fascinating character in the C++ world , an exponent of astonishing techniques for doing things that, perhaps, are sometimes best left undone but which are great to know how to do. Coplien argue, in essence, that:
- Unit testing is bound to miss lots of bugs, because you can test only a small fraction of the state space for even trivial objects.
- Finding the right seams and the crucial boundary conditions is not harder than simply proving correctness, which is where we got on the bus.
- What we really need to do is to test systems and subsystems, anyway.
Hansson writes that
Test-first units leads to an overly complex web of intermediary objects and indirection in order to avoid doing anything that's "slow". Like hitting the database. Or file IO. Or going through the browser to test the whole system. It's given birth to some truly horrendous monstrosities of architecture. A dense jungle of service objects, command patterns, and worse.
This echoes Brent’s concern about splitting big objects into a basket of bunnies, but it’s essential to understand how good design rules can lead to bad design if you don’t keep your eyes open.
The Primrose Path
Isolated behavior is good. Being able to instantiate an object simply is good. Avoiding complex dependency graphs is good. Being able to use an object without a database or the network is good.
But it’s never good to go overboard and let the pursuit of the good create disaster; you seldom want to give the bunnies a skein of colorful yarn and hope for the best. Specifically, it’s quite easy, when splitting a messy big object into smaller objects, to choose bad boundaries. It’s good to be simple here, but only if that doesn’t add too much complexity there. It’s good to isolate this, but only if isolating it doesn’t mean that everything else has to jump through hoops.
This happens all the time in design. A friend of mine was designing an embassy for a funny-sized lot. The reception areas gradually grew to make them lighter and grander. The staircase grew to balance the reception area. A garden pavilion provided a great space for casual entertaining and only required moving the whole building a bit on the site. Small, sensible adjustments. And then: it turned out that the driveway radius was now smaller than the turning radius of a limousine. And while you or I could live with that, an embassy can’t, and you really don’t want to discover this after you’ve done all the drawings of the beautiful but impossible garden pavilion.
Aye, madame, ’tis common
One of the core design assumptions in Tinderbox is that a map’s or an outline’s LayoutPolicy doesn’t know about the DrawingPolicy, and the DrawingPolicy doesn’t know about the LayoutPolicy. That seems a sensible separation of concerns. I wasn’t sure it was right when I first tried it, ten years back. I’m still not sure it’s right. Perhaps they really belong together. Or perhaps they should have been divided differently. One day, we’ll see what the alternatives are; until then, it works.
Another design decision involves the way timeline bands are managed. My reflex is to put all the work into the TimelandBand and make the list of bands, BandList, a simple composite. One day, I tried something different: I made BandList the smart class and hollowed out TimelineBand until it was little more than an BandListIterator. That worked, but it never felt right, and I spent last weekend moving functionality around until timelines felt less acrobatic.
Tech Fundamentalism Is Scary
Methodological fundamentalists descend on technology from time to time. Hell, TDD and agile started as a rebellion against an older tech fundamentalism. As a profession, we’re suckers for The Word and want to know The True Way. Sometimes, the methodologies do help: structured programming, literate programming, strong types, weak types, objects, patterns, functional, agile: it’s all been good for us.
But, perversely, these helpful little ideas often become modes of punishment. The zealots with fire in their eyes arrive to tell us that you’re doing it wrong. Pointy-haired managers support them because it reinforces their authority, and because it’s easy to see surface flaws and hard to understand deep structure. And – let’s face it – a lot of us are all too receptive of that message. The beatings will continue.
The mass of guilt that weighs upon the field deadens our conferences. That guilt arises from the divergence of what we like from what we think we should like. We enjoy exciting new systems that do what nothing else could do; we think we should like systematic demonstrations that this widget lets students do a task 5% faster than that one. We enjoy daring prototypes and agile development; we think we should be planning our work and proving correctness. We enjoy astonishing code; we think we should write code so clear that our most mediocre students (and the management team) will grasp it without effort.