TDD Pro-Tip: "No, smaller."
In TDD, almost without exception, we want everything: a test, a method, a class, a step, a file, really, almost everything, to be as small as it can be and still add value to what we’re doing.
This is the most common gaping hole in the practice of the noob, and it’s a constant obsession for the olb. But it’s not just an old-timer’s tic, like the way my lips purse when someone uses the word "enterprise". There’s rationale here, deep rationale. Let’s poke at it.
We owe our entire trade to the invention of the transistor. A Core i7 quad-core cpu has about 700 million transistors in it. There are a couple of billion of them in an iPhone.
Do you know what’s great about transistors?
(for the lucky 10,000: a transistor is just an electronic switch. It’s got a wire that runs through it, just like the light-switch on your wall, and just like that switch you can open/close the through-wire. …
… Unlike the wall-switch, a transistor has another wire coming in, that doesn’t come out again on the other side. That extra wire plays exactly the same role as the mechanical flipper on your wall-switch: it opens/closes the through-wire.)
We had electronic switches before we had transistors, two different kinds, actually, vacuum tubes and relays, the latter of which is still used in lots of applications.
So why, given we had two other substitutes, what is so great about transistors?
They have no moving parts.
Because they have no moving parts, we can make them incredibly tiny. We can make them very fast. We can do it cheaply. We can guarantee their stability.
Now, they’re simple. Dead simple (as switches — they get more complicated when we use them to do other weird analog things). But it doesn’t matter, because we can throw literally billions of them at a problem.
They are so ridiculously cheap we can multiply them at will. Because they’re so stable they basically never break. A transistor only breaks when you literally crush it or literally fry it. That’s pretty much it. You can’t break one accidentally.
That’s a very long extended metaphor, and I freely admit to having stretched it out a long time. The thing I need you to understand is that this absolutely ubiquitous thing is tiny, fast, stupid-simple, and rock-solid stable.
Tiny, fast, plentiful, stupid-simple, dead-stable.
A TDD’er wants every element of her code and every aspect of her mechanic in making that code to have exactly those properties.
Two cases. One each from code and from mechanic.
First the code. Here’s a class from the kontentment project, and its test.
That class is a Fragment. Fragments have the job of changing the stuff on the screen. Inside a timed animation, and telling their caller when they’re done. This particular one changes it by adding something.
Isn’t she a beauty?
She’s a transistor. She’s incredibly small, 40 lines including whitespace and java-noise. She’s stupid-simple. And she’s dead-stable.
The contentment app has been in development for a couple of hundred hours of my time. The underlying function here, adding stuff to the screen inside an animation step, has been through at least a half-dozen workings.
Why oh why would I do such a thing, re-work that functionality so many times? Because in every previous rev, it worked "-ish". It mostly worked until it didn’t work.
I’d get some bug in the whole app. And i’d go in to try to figure out what was wrong. mostly what was wrong had nothing to do with Entrance’s functionality. mostly. But I could not be sure. I didn’t have tests, and I didn’t have it so simple I could tell at a glance.
Entrance was not yet a ‘transistor’ for me. I couldn’t just throw around a bunch Entrances and assume they would never be "the problem". So I kept pushing and pushing and pushing to get to that point.
The final piece of the puzzle was actually figuring out the responsibility of Fragment, the interface Entrance is implementing. Once I got that, all the fragment impls became transistor-like.
I no longer have to look at Entrance’s code when something seems to be entering oddly. Because Entrance is a transistor. Actual scripts have dozens or hundreds of entrances. They work every time, they’re tested, they’re stupid-simple and dead-stable.
Entrance is a transistor.
What about an example from my mechanic, my practice, not a made thing but the making of it?
Like most geeks, I divide my time in the code between "flail" and "flow". When i’m flailing, I am basically just doing some kind of romp around the code base, trying this, trying that, debugging here, println’ing there, running the stupid app a bunch of times.
I might be chasing down a problem, or trying out some new architectural concept, or just thinking thinking thinking, using whiteboard-as-code and code-as-whiteboard. What I am trying to do is get to a place where I know enough to flow.
When i’m flowing, I am working from head, I am adding a tiny test, passing that test, and pushing to head. Rinse, lather, repeat.
It’s visible in the commit log.
Flail: nothing for 2-4 hours.
Flow: 10-15 pushes in 2 hours.
The difference between these modes? In flail mode I don’t have any transistors to hook up. In flow mode, everything i’m doing is just one stupid-simple dead-stable step after another.
Rename this, pull up that, invert that condition, initialize that variable when you declare it, extract method, change signature.
Every one of those is a transistor to me. Each one is just a little tiny perfectly grasped (often automated and nearly always at least computer-assisted) step. Bam. Bam. Bam. Bam. That’s the sound the transistors make as I hook them together.
A TDD pro worries about getting the pieces she works with to be as small and straightforward as she can possibly make them.
She’s going for the feel of a transistor. And she’s going for that because once a thing is a transistor, she doesn’t have to worry about it anymore. It can be combined with other transistors to do just what she wants, just when she wants it.
From some angles, it seems like all of TDD is just this act: take something that works-ish and make it into a transistor or a combination of transistors.
Language fails. I cast for metaphors that might help, and that’s why you hear me with the "no hard shots", now the transistors, the thinking knee, and so on.
Part of me wants to apologize for not having that language all set to go. But the other part of me says, wait, what, how do we expect that language to just be there already? We just have to grope towards it, and that’s why I write these.
Your starter question, if you’re shooting for pro-TDD style: keep asking every day all the time, how could I turn this thing into a tiny simple stable stupid thing so I can then use it over and over without ever having to reason about its internals?
"No, smaller," should be a kind of default first-choice opinion about everything you make and everything you do to make it.