The "X Improves My Design Anyway" Myth
It’s amazing how often the bandwagon impulse overrides clear thinking. Perfectly intelligent people will idiotically claim that global warming isn’t happening, because the political party that represented their views on abortion happens to say so. Compassionate and considerate children often turn into bullies in junior high school because they need to belong to a group. And in software development, advocates of programming techniques will adopt a masochistic kind of “I deserved it anyway” attitude toward the things that it makes hard.
An example of the last variety popped up recently in Tim Ottinger’s blog on TDD. The way it manifests itself here is in the “X improves my design anyway” myth. This myth says that even though I have to do things a harder way now, it’s really good for me anyway and I should have been doing it all along. Much like castor oil is good for children, I suppose. Tim Ottinger now advocates changing style and convention guidelines in companies to say that you should be doing things the hard way anyhow, so that the problems caused by TDD just sort of magically go away.
To be clear, I’m not saying TDD is a bad idea. I’m saying that competent software developers ought to make rational choices, by considering the advantages and disadvantages of things for a particular purpose. This is directly opposed to the idea that we ought to stack the deck by defining away the disadvantages and pretending they are good design that we just didn’t recognize until now.
Among the things Tim thinks are now good design:
- Making member variables non-private, so that tests can get to them. Also, weakening the distinction between public and private interface (apparently, in order to help people understand the code… huh?)
- Wrapping basically every new object allocation in a factory method with some kind of abstraction for changing the implementation on the fly (in other words, the thing that makes J2EE hell). Tim says “Calling a concrete class constructor inside the body of a method now makes you cringe.” That doesn’t look like a good way to do things to me.
And after all this, the conclusion is: “For the most part, TDD forces you to start doing the things you always should have done…” Bingo! The myth in action.
Of course, Tim isn’t just stupid. There is no one who doesn’t do this. Other examples include:
- Declaring all your variables in a block at the top of a Pascal procedure is a little inconvenient, but choosing your local variables carefully in advance of writing the code improves your design.
- It’s hard to map arbitrary data structures in your application language to the relational database, but designing your data in a relational way first improves your design.
- Monads can be verbose and difficult to use in Haskell, but that makes people wary about using monads, which improves design.
- Multiple inheritance is impossible in Java, but untangling your multiple inheritance hierarchies improves your design.
This myth happens when we adopt a philosophy or approach to building software. Invariably there are certain tasks that are made more difficult by the new approach, and there are somewhat painful workarounds to these scenarios. However, because the word “workaround” is a bit of an ugly one, we want to sweep this under a rug. The easiest way to do this is to take the painful workaround, and claim that it was the right way to go about things anyway. Then we get “X improves my design anyway” – where X is some procedure used to work around a limitation of the approach.
Of course, X very well may improve your design. If that’s the case, it’s worth the time to step back from the context (programming language, TDD, relational databases, etc.) that motivated you to do X in the first place, and see whether X makes sense without that context. If so, great! Tell people about it, and maybe it’ll catch on as a design technique in its own right. But if it only makes sense in the context of your technique, then it’s time to start calling it what it is: a workaround for a shortcoming of the technique. (Hint: if you just can’t imagine writing good code without your technique and so think this exercise is futile, then you are too far on the bandwagon already; let somebody else decide.)
In Tim’s case, a lot of his recommendations simply don’t stand the test of scrutiny. The debate over public member variables happened, and was lost. If TDD requires more public member variables, that’s a workaround for a limitation of TDD; it’s not good design. J2EE (sorry, JavaEE!) is needlessly complex and difficult to use, and people are moving to other languages and frameworks because of it. If TDD requires recycling the same endless factories and “okay, how the hell do I get an instance of this interface now?” that plagued JavaEE, then that’s also a limitation of TDD as well. Some other recommendations do stand the test of time. Ultimately, though, the problem is the attempt to take these decisions out of the hands of competent developers, and put them in the hands of dead convention and style guidelines. This is denial – pretending that there is no decision to be made, when there clearly is a decision.