Admitting you’re wrong is anything but easy. It’s a shot in the arm of the pride that we all hold dear – in our personal or professional lives – and often, for better or worse, in both. Five years of marriage taught me to admit fault or error when it’s valid. And lately, this humility, has spilled into my career as a software developer.
One can be wrong in myriad of ways. There is fault in attitude, fault in actions, and fault in opinion – with Test Driven Development I was wrong on all three. Gentle reader, following are the ways I wronged TDD and myself:
Ways I was wrong:
1. The way I write software is fine.
2. I write complicated software – this won’t work here. Object composition and encapsulation precludes TDD’s success.
3. This will slow me down.
4. It’s a fad.
Let’s dissect these. My attitude was prideful (1); my abilities do not need improvement I told myself. This is the sweet trap of stagnation we knowledge workers lay for ourselves. Spending so much time creating models in our heads and expressing ideas in a language for machines ripens the opportunity to see our reflection as Turing, Beck or Fowler.
The applications I build are anything but trivial (2). Is this a true statement? Sure. Not yes – aren’t most applications complicated? And how would one consistently define one system as being more complex than another? I could do it via lines of codes or cyclometric complexity – but that isn’t really what I told myself. In fact, I belittled TDD as too small for the job because of my boastfulness stemming from fault number one (1).
Asking the question “will this slow me down?” is a trivial scapegoat for avoiding doing any task better (3). This one is endemic of human nature because it’s difficult to argue the counterpoint – of course doing something new will be slower at first. So does drying the dishes before putting them away, but the average domesticate prefers their cabinets not moonlighting as mold spore factories, so they will wipe that Crate and Barrel flower pattern down.
A couple months before I noted to write an essay on the benefits of TDD I planned a counter thesis – TDD is a fad (and it sucks)(4). Yes, gentle reader, as egregious as it sounds I was quite comfortable in my anti-TDD stand; after all I was deluding myself with points one, two and three. In fact, I reacted from cognitive dissonance; if I don’t write software well then TDD can not possibly be of benefit. Stated inversely: if TDD works there is something wrong with the way I write software. That kind of conclusion is demoralizing and my brain rejected it before I could even fully consider the implications. I rejected TDD to keep my ego in place for the job I spend my days contributing to. This of course, led to confirmation bias (Nicholas Taleb terms this platonic confirmation in his book the Black Swan, http://www.fooledbyrandomness.com/) as I sought out evidence – again, the first three points – on how I was right and the TDD crowd was wrong.
If you can’t teach an old dog new tricks, like your author, it’s like pulling water from a rock to help a developer change a habit. Now, tell them to not write cod and then test, but to write tests that write the code. My struggle persisted. So, I looked at the advantages – which are numerous.
Advantage #1 – Consumer Friendly
Aesthetics are more a part of coding than the secretly vain development community would like to admit. What is the class named? Does it take the correct parameters? Is it a subclass of something else? All these things are figured out iteratively because the test is the code. In order to write the test “GetAccountNumber” you write what you would like to see in your code.
public void Test_GetAccountNumber()
IAccount account = new Account(44444); //this class does not exist yet!
int a = account.GetAccountNumber(); //this method does not exist yet!
This idiom of works produces more readable code because it’s already been thought out and used before it ever hits the application.This is synonymous to building UI first for a UI app.
Start with the consumer.
Advantage #2 – Interface Based Development
Entire books are written on interface based development. Composition is preferred to inheritance and interfaces are the way to compose your objects, therefore interfaced based development is preferred. Writing your tests first give you the opportunity early on to program to an interface, saving much grief later on.
Advantage #3 – Prove it works the first time
As humans we look for ways to avoid pain and injury. Which hurts less: a) writing a method called GetAccountNumber and testing it to fill a drop down on a web page and watching the drop down render as empty, or b) run the unit test for the method and watch it pass or fail. It is infinitely easier to debug the unit test. The only parameters for the method GetAccountNumber in the unit test are what actually gets passed to the method; like the primitive number 413. The parameters for testing the method in the web page are the entire context of the web page – possibly the entire site – as any number of run time errors can occur while manipulating other elements.
With TDD, the chances of success the first time a method or class is used in an application greatly increase and application debugging goes down. They are inversely proportional.
Advantage #4 – Proof it works after changes.
No matter if the program is a flight simulator, a line of business app or a huge e-commerce platform, all programers struggle with using their classes after they’re written. Even with some up front class modeling, the first time a class is used is almost always a disastrous event.
For instance, let’s say the class built is called Mortgage. It looks like this:
public Mortgage(datetime startYear, dateTime endYear, double loan)
public datetime GetStartYear()
This is all simple enough. The Mortgage class is plugged into an existing web app and the methods that it exposes are used by this application. Again, this is the normal course of events for software development the world over for the past, say, 30 years.
Just a week later a new application feature is needed to calculate the amount of interest paid for the length of the loan – which requires knowing the interest rate. Notice though, interest rate was not past in though the constructor. There are now two options – 1) overload the constructor with a new version that accepts the interest rate or 2) expose a public property to set the interest rate.
Option 2 does not sound very elegant and can lead to object state issues as the object is not created atomically with all the data it needs to perform it’s behaviors. So, the new GetInterest method could be called and throw an exception because it does not have the data it needs. (I’ve done this…it was a really, really bad idea.)
Option 1 sounds like a golden parachute over the later option. So, the constructor is overloaded with a new version:
public Mortgage(datetime startYear, dateTime endYear, double loan, double interest)
There are now two constructors for this object, and they need to stay this way for the time being because the Mortgage class is already used in 5 different places in the application. In fact, Joe Developer is using the Mortgage class with the first constructor and adding new methods to the class. Looking at that fact, it’s thought best not to change that first constructor because who knows what may break, or worse: misbehave at runtime.
The new Interest feature is now coded and approved by whomever approves these kinds of things and everyone is happy – until iteration 3. A routine that Joe Developer coded needs to slide that Interest number into a grid. Joey D thinks “no problem” and calls the GetInterest method: bang, dead. It blew up. There isn’t an interest value in the object because he constructed his object with the original constructor.
This could have been avoided by simply eliminating that first constructor which was leaving the object in an invalid state when the GetInterest method was added. But that was scary. Since there weren’t any tests on the methods of the Mortgage class it was deemed to risky to change the places where the first constructor was used and now every developer from this point on will run into the same problem. Of course they can now decide to alter all the instances of the first constructor in the application – but the damage was already done; defects.
Advantage #5 – Make the Brownfield easier to navigate.
As a professional we usually work in environments we did not bootstrap ourselves. The environments are preexisting and have characteristics of their own. Often, the code isn’t even legacy – it’s just there. Any environment like this is referred to as a Brownfield and means integration work. Integration is difficult because analyzing what may break with new or upgraded features can’t be seen until functional testing. Writing tests first in a Brownfield will make your life easier – and the guy who comes next.
Advantage #6 – Be a professional – take responsibility for your product
Simple economics dictate the likelihood of having a tester for every coder is the same as successfully writing software using a pencil. Writing tests that prove code works before testers touch it ups the quality dramatically. It’s a way to take responsibility for the product being created.
Advantage #7 – Have fun. Red, Green, Refactor.
Remember when writing software was fun. When you referred to it as “coding” or “hacking out some code”? Replace your TPS reports with TDD and bring a little fun back into the development cycle.
Doing Test Driven Development means that one small piece of functionality is being written and tested at a time. It’s written. It breaks. The code that the test is trying to run is written. It breaks again: Red. Tweak. It passes: Green. Now that it passes go and make it better. Improving the code is no longer a fantasy. It’s easily doable because it’s all covered by tests. Refactor those primitives into an object or replace conditionals with polymorphism. Or – this is fun- replace some meddlesome constructors with factory methods. It’s not a dream with TDD – changes can be made as long as the test go Green.
Advantage #8 – Sleep better at night
Turn off the monitor(s), grab the keys and go home assured that things are working. Pretend that it’s Monday and you cranked out the following code; four new classes and ten new methods. That’s a good amount of new code introduced into an application in one day. The build succeeded, good, but you’re still not sure that when your teammates get latest things will functionally work. Now, pretend you had tests over all that new code and they all were Green when you left. No worries. Better sleep.
Advantage #9 – End up with better software.
This is really what it’s all about. Test Driven Development makes your code refactorable, which means it’s easier to change, which means it’s easer to optimize, which means it runs better which is correlated to less defects, less QA and easier releases. And all that adds up to better software.