Friday, September 16, 2016

Technical Debt

Habitable Code / Clean Code

Let's define Habitable (or, Clean) Code as one in which programmers coming to code later in its life can understand its construction and intentions, and change it comfortably and confidently.

Technical Debt would be the work that remains to make the code habitable.
The debt metaphor was coined by Ward Cunningham, to illustrate that like with financial debt, the cost of change grows over time. Instead of money we borrow time
Every developer who spent some time with code experienced this: a design that was once elegant turns over time to a big ball of mud, to spaghetti. Complexity grows, and changing the code feels like walking through a mine-field. How do we get back to safety?

What makes us feel unsafe changing code?

Mostly, a culture of laying blame. Fear driven development. Addressing that has less to do with technical debt, but I explore some ideas about safety here

What else? Here's a partial list:
  • Low automated test coverage
  • Late, high cost, infrequent integration testing milestones 
  • Failing test cases
  • Code anti-patterns (static analysis warnings)
  • Low cohesiveness of classes
  • High cyclomatic complexity
  • Various architectural anti-patterns that make the design hard to understand 
  • Known (and unknown…) bugs
  • Missing API documentation
  • Duplication
  • Long methods with deep nesting
  • Misleading names and metaphors
  • Lack of adherence to coding standards (inconsistent style)
  • “TODO” comments
How did we let our tests and code get this way? Could be any number of reasons, but essentially they would fall under these 3 categories:
  • Planned decision - being aware that the code needs more work we release anyway due to some constraint but intend to fix the code in the immediate future
  • Accidental - and here the big question is do we learn from it and become more competent - or we simply don't know any better
  • Reckless - we don't give a damn 
I would assume that the first one is a desired state, where you occasionally take on debt and pay it back before it becomes a big problem. Dealing with the last one is not about tools and techniques - there some deep underlying problem to address. 
But from what I've seen, it's mostly accidental, and here we can improve by learning ways to assess, avoid and reduce technical debt.

Assessing Technical Debt

Some frameworks (like sonar) are good at providing metrics and visualizations of different aspects of technical debt, integrating static code analysis data regarding style, anti-patterns, complexity, duplication, cohesiveness and information regarding test coverage. 
Like any tool - you need to know when and how to use it. I'll let you in on a secret: 

     You don't have to fix all of it 

Remember why we care about technical debt? It increases our cost of change. But maybe there are areas of the code we don't really change that much? 
What we need to care about is code we change often. This is where the mess hurts the most.

Because of that attribute, assessing the amount of technical debt is not as important as dealing with it. Some would even say dont waste time tracking technical debt with some very good reasoning.

Preventing New Technical Debt

As a strategy you need to change course - commit to a new behavior. The team needs to take upon itself some new rules and stick to them. If they have a definition of done, this might be a good place to add some helpful practices. But drawing a line, saying "from this day on..." is a good start.

Some concrete practices:
  • Test-driven-design
  • Group design (whiteboard sessions)
  • Education & team agreement (e.g. adhere to „clean code“ practices)
  • Pair programming / continuous code reviews
  • Continuous integration + automated tests + static analysis tools
A note regarding tools: they won't help unless regressions are visible (dashboards which the team actually sees) with a clear agreement on what we do whenever there's a regression. 
Hint: fix it. Now.

Paying Back Our Old Debt

To reduce debt, you are probably going to identify an area to improve, pad it nicely with automatic tests, and refactor. Rinse, repeat. Since there's always room for improvement you need some way to balance the effort that goes into this kind of work with your need to build stuff. So unless some major rewrites are needed, the most effective, and simple way I know of is what Uncle Bob calls "the good boy scout rule", paraphrasing on "Always leave the campground cleaner than you found it". 
Another option is to set goals based on metrics if you are using something like sonar, but again - this is easy to get wrong and wastefully get people fixing code that's hardly ever touched.

The Curse of Knowledge

So now you know. Its no longer accidental from now on. How did we call not taking action to deal with known technical debt? Oh, right - reckless. 
It might be a hard sell for some stakeholders if you need to make some meaningful investment to change course, but if they care about the future of your product, the sooner you can agree, the better. 

Further Reading