Thursday, December 20, 2012

Fixing broken windows

At some point in our software development careers, we experience a phenomenon commonly described as the “broken window theory”. In terms of code, this loosely translates to mean that code quality will deteriorate over time because small lapses in quality will replicate themselves throughout the codebase. A corresponding belief is that the only way to improve quality is to either start with perfect code or start again (i.e. demolish and rewrite) with good intentions in mind. I would like to challenge this belief and present a way that even the worst legacy codebases can improve over time. If it’s broken – fix it!
Some my favourite background reading on on this topic include:

More than once, I have received questions from developers asking how they can convince their manager to let them rewrite the component that was badly written in a rush in the previous iteration. Let me tell you a secret, there is almost never time, business desire, or a budget to rewrite anything – even when poor quality is bringing team velocity to a halt like a tractor pulling a sled across a muddy field.

Sustainability is the key to overcoming the reluctance to rewrite. By its definition, sustainability points to being in it for the long haul and making the codebase a better place to work. Think of it as renting a dingy apartment and putting a coat of paint here, a new piece of furniture there, and continuing to make minor fixes. It gets better over time.
Improving something is almost pointless if you don’t measure what you’re improving. When untangling a jumble of string, how do you know if you’re creating more knots or less, unless you keep track of that straight part at the end? Measuring an increase in quality over time has a huge impact on how you feel about the codebase. It can also guide you in knowing where the most work is needed.

So how does one measure code quality? You should have a set of metrics that can be calculated automatically (e.g. cyclomatic complexity, coupling levels, etc.). These are discreet metrics, meaning they give definite numerical values which can be trended over time. You know you’re making an improvement if the average complexity goes down, despite that shiny new feature you’re just added.
It doesn't matter that your complexity is 1,000 times greater than it should be – what matters is that the next time you measure it, it is reduced. These metrics can be integrated easily into the continuous build system so that the review effort is minimal.

Besides the hard metrics, there are less defined ways to measure quality. How long does it take for you to implement a new feature compared to an ideal scenario? How long does it take to on-board a new developer? How long do you spend waiting for the build to complete each day? Are you confident with releasing this to production? All of these “gut” checks hint towards areas for improvement. So the next time you find yourself wishing things were better, stop for a moment and invent a few ways that you can make the situation even marginally better. In short, numerous small, iterative wins will trump big, disruptive wins any day.

Next time you go to your manager with the intention of begging them to give you time to rewrite the component that is badly implemented: don’t. Instead, approach them with a graph of metrics showing the baseline quality and the quality after you've implemented your next feature and fixed a few things along the way. Demonstrating a sustainable increase in quality without a large investment shows initiative and problem-solving. Besides, managers always like pretty graphs!
Here are a few tips on areas you should concentrate on when improving code:
  • Reduce friction and increase team productivity. Take some time to organize the solution structure so projects are easier to find. Optimizing the build so it builds faster is the first thing that should be tackled as it makes everything easier down the line.
  • Remove the human factor. Automate as much as possible. Write scripts for deploying to your test environment. Write a script that builds and runs all unit tests locally. The more repetitive things you can automate, the more time you can concentrate on doing things that are worthy of your time.
  • Measure! Don’t do anything without first measuring a baseline and subsequently measuring the impact. This is critical in providing information on how much impact you've had. It is also a great reward when you see things start to trend in the right direction.
  • Improve the code quality. Numerous measurable metrics designed to measure code quality exist, but probably the most important ones relate to reducing complexity and increasing the quality of tests.
  • Clean as you cook. Did you open a class to change a few lines of code? Review that class and its associated tests and do a quick cleanup while you are there. Missing tests? Write some. Didn't factor time into your estimates to do this extra work? Spend a little extra effort now and account for this next time.
  • Put barriers between poor code and good code. Define an integration point and translation layer where good code meets legacy code. Don’t inherit the bad when attempting to write the good. Leave the mess contained in the translation layer.
I've seen situations that range from systems outliving the language they’re written in, to systems which have grown “organically” with little thought to design. Gradual improvements will make your codebase a nicer place to live.

1 comment: