And why Test-Driven Development is better
Many people learned how to create software by writing executable code. Some of us learned the skill in school/university, some of us learned it by attending brief courses/bootcamps, and some learned it in solitude.
Learning how to program computers is far from being trivial. It may take years before someone becomes good at it. Robert Martin once remarked how it took him 20 years of daily practice before he reached the stage where he felt he was able to write quality code.
My coding journey
I first started learning computer programming while I was at the university. The challenge was to learn how to envision some English-like text being converted to some binary code that a machine (a computer) can execute. We started with FORTRAN, and had to learn how to make the computer calculate some values based on the algorithms we were trying to formally express. The idea was to teach the machine to follow some imperative steps.
All along, it was obvious that our attempts to handle code-as-text were prone to errors. A naive programmer would envision some text (something along the lines “if this value then do that, otherwise, do that other thing”), and would then hope that the text converted to the binary code would cause the computer to do exactly as expected. But oftentimes naive programmers get surprised to learn that when the text has been converted to machine-readable code and the code runs, the outcome of the processing differs from the expectations.
That is the moment when naive programmers discover the joys of debugging.
Free-hand coding leads to free-hand debugging
Let me first explain what do I mean by free-hand coding. To me, writing code is similar to cutting some material. As a matter of fact, we even have a phrase “cutting code”. The way it works is as follows: a person (the programmer) who is about to make the change envisions it first. For example, the programmer thinks “now I will cut some code that will check the value of the order total and if the total is greater than $500.00 I will create a routine to apply 10% discount to the order total”.
The above describes the intention of the programmer who is about to cut the code. In a free-hand coding style, all that’s left to be done is to roll up one’s sleeves and start cutting the code.
There are many ways to skin the proverbial cat and there is also more than one way to cut the code that will process the order total as intended.
What often happens is that the programmer cuts the code, then compiles the app, runs the app, logs in with some test credentials, navigates the app, then manually types in some test data and sends test data to the app for processing. Then, when the app displays the results of the processing, the programmer checks to see if the results match the expectations.
It is not unlikely that the results don’t match programmer’s expectations. For example. programmer has entered some orders whose total is $1,000.00, and when the programmer clicked on the “Pay now” link, the expectation was that the processing algorithm will apply 10% discount on the $1,000.00 total and will adjust the total to be paid to be $900.00. But for some reason the app is still showing $1,000.00 to be paid. Just a hypothetical example.
Now the programmer realizes that there is a bug. Something in the programming logic has not been implemented correctly. In the simplistic case such as this one, the programmer may open the source code, read line by line carefully, and hopefully catch the error in the programming logic.
But as may be the case that happens quite often, in more complex situations just reading the source code may not reveal where the bug in the program lies. When that happens, the programmer must resort to debugging.
Same as the free-hand coding style described above, the debugging activity is also free-hand. The programmer will interrupt the processing by interjecting a break point at the convenient line in the source code. That interruption will enable the programmer to examine some intermediary steps and values that are produced during the execution of the code.
Why is free-hand coding bad?
As described above, free-hand coding involves a lot of manual steps. Not only are those manual steps slow and tedious, they are also error-prone. There are many opportunities for the programmer to introduce typos while entering test data into the app. And whenever an error occurs, the programmer must repeat the song and dance after fixing the error.
Debugging too is a tedious manual process. Any time an error occurs and it is necessary to debug the app, after fixing the error we may later encounter the same error emerge again. The previous debugging session has to be repeated manually, as there is no way to automate it.
Overall, free-hand coding tends to be an extremely wasteful, unproductive activity.
Is there a better way?
Yes there is. A better way of creating software is to introduce automation into the software creation process. Rather than doing everything pedestrian style (i.e., manually), we should instruct the machines to perform the same chores on our behalf.
How do we do that? By writing executable expectations.
Executable expectation (what we call a ‘test’) is the first consumer of the code that is yet to be written. Before we write any code, we should first formulate our intention. We already are doing that, even when working free-hand style (a pedestrian approach described above). The only difference between free-hand coding and coding guided by the automation is that with automation, we express our intention by writing it down, rather than holding it in our head.
Why is that way better? Well, it’s better for a couple of reasons. The first reason is that mulling over our intention and putting it into an executable form gives us the opportunity to envision how would we like to use our code. Since the code isn’t there yet, we are under no constraints to adapt to the previously hardened design. In our executable test, we are free to envision any way we’d like our code to be used. That freedom enables us to come up with a friendly, intuitive, easy to use design.
Secondly, we start from a broken situation. Why is the situation broken? Because our executable expectation (our test) is trying to interact with the system, but since the code that enables that interaction hasn’t been written yet, the attempt fails.
Why is it desirable to start from a broken situation? It is good to start that way because we know that:
it is us, and no one else, who broke it
it is us, and no one else, who then fix that breakage
we add each new functionality by first breaking it and only then fixing it
In that arrangement, any time we have a system that is not broken, we know that it is indeed fully functional (i.e., no hidden bugs or defects lurking in some murky corners).
What is the biggest advantage of not doing free-hand coding?
While it is definitely essential to deliver a system with full confidence, knowing that it functions as expected, there is more than one way to accomplish that. One way to accomplish that is to always first formulate an executable expectation, witness that expectation fail, and then making it pass.
However, the structural quality of such approach is debatable. Some experts claim that it makes more sense to focus on quality design. Hard to argue with that sentiment, so I’ll focus on what I think is a bigger advantage of the executable expectations approach. A bigger advantage than producing quality design.
While executable expectation approach cannot guarantee optimal design, one thing it can definitely guarantee, better than any other approach, is the reduction of the time waste associated with the process of building a system. As was already described above, when I discussed the process of free-hand coding, teams who are not following the executable expectation discipline (i.e., Test-Driven Development — TDD) tend to waste a lot of time doing everything manually. TDD is indeed helpful in reducing, and often almost eliminating manual chores.
That is, in my experience, the biggest advantage of abandoning free-hand coding. And as I began this article admitting that I’ve started my career as a free-hand coder, today I would never agree to go back. Today, I let the machine do the heavy lifting, I let the machine compile, build, configure, run, and exercise the diffs I am making. When practicing that approach, I save huge chunks of time that would otherwise be wasted on manual heavy lifting.
And we have a question from the audience now: what is the advantage of having huge chunks of free time once we abandon free-hand coding?
Excellent question: my answer would be that by reclaiming huge chunks of free time when doing TDD, I get a better chance to focus on honing my design skills.
Thank you, and please send more questions my way.