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.

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.

TDD doesn’t require any of those things, you’re misreading what he said. For example, TDD does force you to make your objects pluggable (when you work in statically typed languages), but the correct solution isn’t to make factories so you can create objects from within other objects safely, the correct solution is to hand in all dependencies via the objects constructor, you know, to “construct” the object. This leads to loosely coupled and easily testable code. This requires nothing more than an interface, not a factory. It’s also how you should be writing your code anyway, whether you test or not, this is how good code isn’t written, loosely coupled.

TDD doesn’t require public member variables either, you don’t test variables, you test the behavior of public methods. TDD also doesn’t require you to make private methods public, but if you really feel you need to test a private method, it’s a good sign that you have a hidden class crammed inside the class you’re trying to test, so you extract them out and make that method public on a new abstraction, thus simplifying the old class, and making a new class that is testable.

I’m not a TDD nut, but TDD does force one to learn how to write loosely coupled, pluggable, well abstracted code, and it only comes as a shock to those who don’t already write their code in that manner. TDD is very painful for folks accustomed to writing tightly coupled, long method, little abstraction, poorly factored code. If TDD hurts, it’s because you’re doing it wrong.

Uh, where does Tim state, or even come within 100 miles of suggesting, that “making member variables non-private .. [is] now good design”? You either have massive problems with reading comprehension, or you’re just glossing over and wildly extrapolating from what he’s written to make a (tedious, not particularly interesting) point.

I swear, every other programmer fancies themselves a keen pundit / philosopher these days. 99.9% of them would be much better off writing more code instead. You might be better served spending the time to actually understand what TDD entails (“If TDD requires recycling the same endless factories…” suggests that, uh, you have no clue) – that’ll help you alot more than trying to write the latest greatest sweeping generalization (the X myth, Y considered harmful, blah blah blah) in CS.

X may improve your design. It may not. The discussion has to be around whether X does or not, instead of a random list of things you consider defective.

For example, information hiding is a good thing most of the time. If some strategy causes you to publish a lot of internal variables, the question to ask is whether or not the trade-off is worth it. Likewise, storing things is a good idea in programming. If you’ve got some arbitrary programming construct you are using that doesn’t map to the database, it’s a fair question as to whether you forgot you had to store it or not. If you forgot, is it part of an important relational structure, or do you need to store it elsewhere?

I think too often we read books on whatever — TDD, Domain Modeling, SOA, etc — and then try to make everything fit into whatever received wisdom we get from them. I think I see you doing the same thing in this article, which is rather ironic, no?

Thanks for the food for thought. Questioning your strategy is always a good idea!

[…]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.[…]

This is true. However, there is an exception as always. If you use C++ and declare your variables before using them, the types have to be default constructible. This might hurt you when doing generic code since sometimes, you do *not* want to enforce default constructibility of types on your users.

Well, it at a minimum it does improve your design to the extent that others are able to quickly grok what you’re trying to do. For example I may see something like a singleton and have to suppress the urge to retch but at least I know whats going on.

When it comes to architectural decisions we can argue pragmatism vs principle until the cows come home, but I’ve seen some nightmares in the security and performance realms when developers have gone for pragmatism.