Desire verb [ T not continuous ] /dɪˈzaɪər/
to want something, especially strongly.
Long have I heard and reaped the benefits of TDD (Test Driven Development). But I’m starting to sense that much of the material written and explained about TDD is by fanatics. Don’t get me wrong. I see the value when learning a new tool to overcompensate. If you have never written a test before coding, then I’m all in for trying for a month or two to always write a test before, write the least amount of code, and iterate.
As with most tools, the implementation is as important as to knowing when to use it, and when not to1.
I like the analogy of this learning as an infection. It may be because I’m writing this amid a pandemic, or that the experience was explained to me as being “test-infected”. But either way, I think that TDD is a bacteria.
Not all bacteria are bad. Some are cute and cuddly2 and helps in our digestion. But some are mean and make out belly ache. In order to protect yourself, you need to build up an immunity. And there are, broadly speaking, two approaches:
- The risky way is to contract the infection. Set out to TDD all the things for a while. Don’t question it, just do it.
- The hard way is to develop and apply a vaccine. Analyze when to apply the technique; how to simplify a problem or a part of the problem to attack it with TDD. This is difficult to do right, but you can work at it little by little until you develop a potent response.
You can have too much of a good thing.
I’ve come to terms with the idea that I no longer do TDD, but my take of it: Desire Driven Development. When discussing this blog entry, many people did agree that they also find a middle ground between strict TDD and testing3 code.
Desire-Driven Development by Example
If I want to become a famous author, I need to distill the idea down to a few, simple, easy to comprehend steps. Bonus points if they form an acronym.
- Start with your Scratchpad. This can be a new test suite. extending an existing one. If your language/technology allows for an interactive workspace or REPL, those too can work. Anywhere where you can code, and it can eventually be run.
- Assume that all the code is done already. All features implemented. No bugs. Well-designed, neat, and functional.
- Write down the acceptance criteria and what the expected outcome should be. Remember, all the classes, methods, functions that you want exist already, and work exactly as you expect.
- Follow down the compile errors, missing methods, classes, runtime checks, et al. until your scratchpad does indeed work as you intended.
- It might come the time where you’ll need to update the criteria written in step 3. You are allowed to do so but you need to first think hard about the change.
- (unchanged4) Refactor as needed.
The linchpin of this technique is that I usually want to write and read code in the same way. There is no feasible way that I can know what other people will understand when, in the future, they reason about my code. But I am constant5. Then I should strive to produce code that astonished me the least6. Hopefully, like minded individuals will be equally un-astonished.
And bare in mind, this original code in step 3 has no concept of the inner workings, the details, the implementation. Pure and unadulterated issue-resolving code.
If I gave you my 5 letter acronym7, I can top it of with a 5 words summary:
TDD, but lightweight
The same, but different too
The major distinction between TDD and Desire Driven Development is that the latter focuses on the outer most implementation. The technique spends a lot of time thinking, contemplating, and in awe of what the final implementation will feel like. Anything that is needed to make step 3 possible, I don’t care about testing, or developing. I’ll go so far as to say, I would like to have the most freedom of changing it at a whim. Writing tests for this inner layer makes change harder.
Hairy step 4.
It might come a time where I have a complicated piece of logic that I wished was already there. Then I usually Shahrazad8 and DDD again from that point onwards. But this is a conscious decision I make. I don’t zealously write tests for each intermediate abstraction.
Sometimes, I need to change the beautiful code written in step 3. That is because my squishy human brain9 can cope with the inherent vagueness of my desires, without considering the minutia required for it to be codable, but more often than not; when I realize that additional contextual information is needed (like an extra parameter; or a fatter way of building the environment), I look back at my original assumption and try to make that the default behavior, with as minimal code change to the original desire.
The water shapes the rock, and the rock shapes the water
Whilst writing this, I realized that I, personally, am a big fan of writing my custom DSL10s. I like languages that allow me to express these DSL with little to no extra syntax and I gravitate to statically typed languages with good inference and tooling.
Bonus points when the language’s bottom11 helps the compiler insert error messages which are more appropriate to the context in which it appears12, or in runtime when
throw Error() caries with it the stack trace.
These all are great characteristics to have for DDD.
The more I think about it; the more I’m convinced that DDD is less about Development, and more about Design. SO I might need to change the title at some point. But for the time being, I’ll try to piggy back on TDD. Maybe DDD is not for everyone or every use case, but it is how I usually code 😺.
I don’t think that putting “testing” and “TDD” at the two ends of the spectrum of writing code is a newfangled idea. ↩
“““inspired””” by Kent Beck’s Test-Driven Development by Example step 5: Refactor as needed. ↩
The Ship of Theseus is a thought experiment that raises the question of whether an object that has had all of its components replaced remains fundamentally the same object — (Wikipedia)[https://en.wikipedia.org/wiki/Ship_of_Theseus]. I reckon for this particular aspect of my code reasoning abilities: I’ll always be the same, or more knowledgeable. ↩
There is a card in Magic: The Gathering™ called Shahrazad that represents a function call, with its stack and return point. It states: “Players play a Magic subgame, using their libraries as their decks. Each player who doesn’t win the subgame loses half their life, rounded up.” ↩
Cognitive dissonance occurs when a person holds contradictory ideas. — (Wikipedia)[https://en.wikipedia.org/wiki/Cognitive_dissonance] ↩
Bottom (⊥) refers to a computation that never completes successfully. ↩