Alrighty-then. This Hard-tdd vs Soft-tdd thing.
A couple of days ago, I worked through some underplayed premises of TDD here. Along the way, I touched on what I call Hard TDD vs Soft TDD.
The terms derive from AI, where proponents differ on soft-AI vs hard-AI. A semantic association, not a real analogy, so i’ll skip that. Hard vs soft here isn’t about technique, it’s about what we believe the value of the technique includes.
And don’t be confused, there are (at least) three positions: no-TDD, soft-TDD, and hard-TDD. I am a hard-TDD man, myself.
The terms have to do with the value of TDD. Most standard discussions of TDD as a process look at the results and offer the extrinsic value produced by them.
The tests are good because, variously, they’re progress pitons, they’re living documents, they’re validators, and so on. I believe all that, of course. Hard-TDD certainly incorporates all of soft-TDD and none of no-TDD. But hard-TDD makes a very bold claim.
TDD designs are better, even if we deleted all the tests.
Take 2 codebases, one produced w/TDD, one produced w/o. Do not consider the tests themselves. Hard-TDD predicts the w/ is better designed. This sounds mystical, and i’m sure to hear about that from those who (naturally, healthily) resent the dogmatic behavior of some TDD folks.
But it’s not mystical at all. I haven’t worked out all the links rigorously, but I’ve made a start, and I’ll sketch that out here.
Consider the SOLID principles. You don’t have to be 100% sold to see that they clearly have merit as design forces, yes? After all, in many ways they’re restatements of 40 years of work defining and describing "good design".
Take a case like ISP, the interface segregation principle. This idea is about proper ‘jointing’ of problems when designing. It pushes us two directions at once, towards smaller classes with shorter API’s, and, along w/the SRP, towards tight self-contained APIs. SRP = single responsibility principle, the idea I used to call Just One Thing[tm].
So these principles strongly suggest we give grave consideration to how we break problems up, and that we attend closely to size. And here’s the thing. That is exactly what the practices of TDD drive us toward as well.
I should pause here. It’s clear to me that "my" TDD isn’t everyone’s.
The noobification of everything has spawned mantras & flowcharts galore. When I say TDD, I mean the loose catalog of moves I’ve been led to. They include precepts from CI, from TDD itself, from experience owwie and otherwise. The judgment premise should remind us all of that. I don’t have or teach a flowchart for my TDD in any simple format. I have a toolbelt with a lot of tools. I use my judgment constantly in applying them.
All that having been said, the closest phrase I have to describe what I do is in fact TDD. So I will stick with it. The world needs another X-driven-Y label like it needs more holes in its wobbly little head.
Back on track, then. The ISP+SRP isn’t the only force that TDD leans us towards from the SOLID world. Microtesting pushes me towards the DIP — dependency inversion principle, colloquially, don’t make imporant things depend on details.
When I’m microtesting to make some new class, I am constantly spawning collaborations and putting those in other classes. Not from a sense of design purity, but from a sense of rich, raw, unmediated, overpowering, umm, laziness.
It works like this. I know X is gonna need parts that deal with Y. I don’t roll that work into X, I push it to Y. I do this because I am after X right now, the heart of X, the center of X. I’m not after the detail of the Y-parts.
Think of it this way. I need to parse these lines. That’s the central job of the CharacterParser class. And CharacterParser does not care where the source lines come from. It doesn’t matter. That is unimportant. I don’t hesitate to name the Lines class (or in this case, use the modern line-oriented stuff built in to the JDK).
I give my CharacterParser a Lines object. Parsing out d&d characters doesn’t depend on where that text comes from. The full solution depends on that. But the force of wanting to microtest the interesting part of CharacterParser leads me quickly to DIP.
And that’s it, for now, on my underlying reasoning for backing hard-TDD.
There are other variants on "what is a good design" than SOLID, some older, some newer, some in conflict. Much has been made in recent years, partly via functional programming, of immutability & function composition, for instance.
As a TDD’ing microtester, I love immutability and I love composition. Not because of the deep insights of design theory. Because they are easier to microtest. Immutability is nice because there’s no invisible state, my methods all return testable objects, and I never have to reach inside. Function composition is nice because it makes it easy to satisfy myself that A works, B works, and + works, and thus that A+B works.
Hard-TDD, which is my current working stance, is that testable designs are better designs intrinsically, even w/o tests.
They’re better because the natural act of being a lazy TDD’er using microtests is to shape designs towards our best current grasp of "good". That’s not a universal position. Fortunately, the soft-TDD case is itself pretty compelling, and hard-TDD certainly embraces it.
Thanks for tolerating. If you want to talk about any of this, just give me a ping. Tip your waiters, they work hard.