TDD design trade-offs and junk food
When “doing TDD,” we consciously make design trade-offs to favor testability. … we end up with a lot of single-implementation interfaces because of testability concerns and from weaknesses with our tools.
When I first started doing TDD, many of the new designs that came out of this practice seemed backwards and counterproductive at first. After all, I wound up with many more classes, lots of interfaces, and most interfaces had exactly one implementation.
It turned out that before doing TDD, I wasn’t very good at OO design (not that I’m all that great now). I tried, but all in all it was largely a bunch of guesswork coupled with the fear of changing code.
In college, I decided to give up caffeine, fast food and soft drinks. Initially, healthy food tasted disgusting and it felt like I was going through junk food withdrawal. After a month or so of following a healthier diet practice, I decided to splurge on a cheeseburger. It was disgusting, and I was thoroughly nauseated. My body was not used to such high levels of fat, and was letting me know it was not pleased.
Starting TDD is giving up OO junk food. No more short cuts, god classes or big balls of mud, which seemed so comforting before. No more wallowing in the debugger, taking pride in knowing how to take apart the Gordian knot, or making a mental stack of variables. It hurt at first, seemed a little alien, but led toward leaner, tighter and more cohesive designs.
As for these so-called “trade-offs”, they are the first steps towards better designs. One-implementation interfaces aren’t a design smell, it’s separation of concerns in action, dependency inversion principle in action. Breaking big pieces into smaller cohesive units works on any scale, and you’ll be hard pressed to find the lower limit of its cost.
Because my initial OO designs had next to zero interfaces, it seemed rather silly to have a bunch of seemingly one-off implementations. Eventually, I saw interfaces for what they truly were: contracts. An interface is a contract, that any implementation needs to adhere to. Consumers of the interface do not care about implementation details, nor can they, as the interface provides no other information besides the signatures it provides.
When doing TDD in a top-down behavioral design fashion, I create interfaces well before I ever create an implementation for them. And that’s reducing cost, as I don’t write code I don’t absolutely prove that I need yet. TDD forces the developer to put down the cheeseburger, drop the chalupa and ditch the Mountain Dew, forming a leaner, tighter and more cohesive codebase.