(for a fuller treatment, see John Ryan’s six part video Managing Technical Debt)
It’s What We Don’t Know That Can Hurt Us
And the worst are the phenomena that sneak up on us. A classic example is the boiled frog. A frog in a pot of water brought slowly to a boil will not jump out before it’s too late. The amphibian adjusts to its environment, ultimately to its detriment.
Humans share the ability to adapt to their environment with their froggy friends. While we would jump out before the water got too hot, we do share the ability to unconsciously adjust to a gradual change over time.
Without quality assurance techniques, software development teams slowly cook in the complexity of the system they are building.
At first, starting with Test-Driven Development (TDD), standing up a Continuous Integration (CI) process, or including our QA testers in our story writing sessions, seem more like a drag on progress than a help. That’s because, at first, they are.
It does prolong the conversation over stories when your testers are challenging the clarity of requirements. It does take time away from writing code that might run in production when you are writing scripts to automate builds and deploys. Until you become proficient in TDD, it initially does take extra time in toggling back and forth between the executable spec and code writing mindsets. So, if we are only considering the temperature of the water this very second, we feel like not doing these things is the right move: we’re making progress, right?
It may not take long to feel the effects of neglecting our quality assuring practices; the water starts to heat-up gradually. If the team does not have feedback loops in place (Collaborative story writing, TDD, CI are all examples of feedback loops: regular checks that tell us how well we are tracking toward our goal), it’s as if the hot water is collecting in a reservoir, just waiting to get dumped (e.g. the first time we attempt to integrate or when we start to write tests or when we begin to develop against more interesting/meaty stories).
Sooner or later, what started out looking like a fast barn-raising — early wins with working software emerging quickly — becomes a slog as more and more problems arise. It takes longer and longer to get a good build. A feature that took take a day or two to implement is now taking a week.
(Again, all this even assuming you have some kind of feedback loop to signal something’s afoot.)
The problem here is that it will be months before the situation deteriorates in this way. There’s a significant gap in time from that fateful decision point and now feeling the accumulated effects of the mounting technical debt.
Left alone this situation spirals out of control, and “suddenly” the team finds itself constantly fighting fires and having little time to develop new features to suit the business’s developing needs. In the most extreme cases, the whole effort fails: it becomes too costly to continue with this mess and the organization limps along, burning cash and precious human attention until it can flush the project.
How did it get this way?
Perhaps we could get a clue if we could just frame the essential “physics” of the situation. We need to understand why, at the beginning of the project, not writing tests seems like the right thing to do. It’s not. Plowing ahead with this false sense of confidence is the seed of our folly.
Here are a couple equations.
This first statement highlights the key factors in making progress. For each story that we implement, we introduce a change in the code. The time we take to ensure quality (e.g. write tests) takes time away from writing code that might run in production. So, “Progress” is made from the sum of the “Change”s we make, slowed by the effort it takes in “Quality Control” measures. There is also an important second term to our Progress measure: the cost of finding, diagnosing, and fixing defects. That’s time purely spent correcting work, and entirely subtracts from our capacity to make progress.
The second relation circumscribes the positive effect of thoughtful quality assurance in terms of a probability function. We are seeing here that the possibility of realizing a defect is in proportion to the size of the change we are making and the complexity of the software we’re touching (directly or indirectly). However, that probability is significantly reduced when we put into place feedback mechanisms that detect defects at the soonest possible point.
We can combine these two “equations” to get a grasp of the larger story. When we begin a development effort, what’s the complexity of the code? It’s zero; there’s nothing there. What’s the chance you are going to introduce a defect? Next to zero! What’s the immediate apparent payoff of paying attention to quality? Almost nothing.
It gets worse. If we took time to write unit tests, what did that do to our apparent progress (first equation)? It’s less than what it would have been if we just charged ahead and hacked up our implementation. It seems to stand to reason then that your gut feel is right: you’re better off not putting in extra efforts in ensuring a quality solution.
However, as we get going, the heat turns up as the system accumulates complexity. As complexity increases, if there’s no meaningful quality control, it becomes more likely that we will encounter some nasty defects. This causes the second term of the first equation (i.e. effort required to address defects) to become a significant element: the team spends more and more time squashing bugs.
What can we do?
Fundamentally, we need to put quality first. It’s really that simple (simple != easy). Make producing a quality result the minimum requirement for the job. It’s an attitude of professionalism. The lore of the lone wolf hacker single-handing solutions are no longer appropriate for today’s requirement that teams work together. The modern professional programmer comes to the team’s repository with two items in hand: in her left, the code it takes to implement the new feature; in her right, the ability to verify that her code works and integrates with everyone else’s. To do less is to shirk her responsibility to the team.
The essential aspect of the Agile mindset has always been to deal with reality as it is, not the way we wish it would be. In that spirit, we are charged to get in touch with the fundamental “physics” of the complex system in which we operate. My hope is that this tiny insight into that world may inspire you to dig in further. Does the set of relations presented here (dare I call them “Ryan’s Law?”) ring true for you? How would you modify these “equations” to better describe the world you live in?
From there, what one thing can you do to take the next step to up your game? Perhaps it’s picking up a tool or a book that helps make concrete for you what quality code looks like. Here are some suggestions (pick ONE):
- Ever actually tried TDD? Get a hands-on no-holds-barred intro by firing up your copy of Eclipse and diving into James Shore’s “Let’s Play TDD” (http://jamesshore.com/Blog/Lets-Play/Lets-Play-Test-Driven-Development.html). Batteries included, no assembly required. Just click and let the experience begin.
- Are there any manual steps in your build and deploy? If you’re developing a Java app, check out “Pragmatic Project Automation” (http://pragprog.com/book/auto/pragmatic-project-automation).
- Want to better understand the shape of well-designed code? Start a study group (2 or more people) around Martin Fowler’s “Refactoring” (http://www.refactoring.com/).
- Pick one of any of these highly recommended books: http://stackoverflow.com/questions/1711/what-is-the-single-most-influential-book-every-programmer-should-read.