Technical Debt

Technical debt is acquired when you take shortcuts while developing your software. It helps you get the changes in place faster, but it results in code that is harder to understand.

Ward Cunningham coined this term while he was working in a financial institution to explain why they were refactoring. His boss at the time was a financial guy, and this was financial software, so a financial metaphor was the best way to explain this principle.

When you want to buy a car, and you don’t have the money, there are basically two things you can do. You can wait and save until you do have the money to buy the car, or you can borrow it. Translating this to writing code, you can implement the feature correctly, with clean and clear design. This way, when it needs to change, it is easy to understand and changing it will be faster. Or, you can do it the quick and dirty way. This is faster, for now. But when the time comes to change the code, it will take more time.

The two components of financial debt also apply to technical debt. There is interest, and there is principle. We pay interest when we need to change dirty code, and we pay down the principle when we clean that code up.

Martin Fowler divides the term Technical Debt into four categories. Reckless vs. Prudent, and Deliberate vs. Inadvertent:
Deliberate, Reckless: “Design is boring and time consuming. This works, so who cares?”
Deliberate, Prudent: “We really should do this, but the deadline is approaching fast. We’ll note our shortcuts and refactor after the deadline.”
Inadvertent, Reckless: “What’s wrong with an entitymanager in the weblayer?”
Inadvertent, Prudent: “This seemed like a good idea at the time. Now we know how we should have done it.”

Here are a few things to help get technical debt under control.

Naming

Choose good names for your functions and variables, and rename to better names when your understanding of the code changes.
You are not just telling the computer what to do. You are also writing a document for future developers. You are creating a language in which to communicate your thoughts about the problem at hand. This language is a mixture of reserved words of the programming language (for, if), terms that are used in the domain (account, customer), and terms that are common in our industry, such as design patterns (action, command, listener). The easy part isĀ  getting the computer to do what we want it to do. The hard part is telling our future selves what we are thinking.

Shorter methods

“Rule 1 of methods: they should be short. Rule 2 of methods: they should be shorter than that!”
“Functions should do one thing. They should do it well. They should do it only.”
Longer methods tend to do more than they should. Splitting methods into smaller methods makes them more readable, manageable, and reusable. Another advantage of smaller methods is that you get lots of them, and that makes it easier to group them in the correct classes.

Unittests

While the production sourcecode is the primary document we deliver, there is another document: unittests. When written correctly, unittests are quite helpful in further explaining the code. They will tell how objects are created and used. This is one of the reasons Test Driven Development is useful. They are also quite useful for spotting design flaws. For example, when you find an uncovered private method, and it’s just too hard to reach it through the public interface of the class, chances are that the class has too many responsibilities and should be split into multiple classes.

Remove duplications

With shorter methods comes a more fragmented codebase: functionality is split into more fragments. This usually means that duplications become more visible. The duplications are already in the code, but sometimes they are hidden. Duplications are generally considered bad, because it usually means that you need to apply the same change to multiple pieces of code. And that’s easy to forget. According to Kent Beck, eliminating duplications is a powerful way of getting a good design.

Single Responsibility Principle

What does the class do? What reasons does it have to change? How many responsibilities does it have? A class should have only one role, only one responsibility. For example, it should only validate input, or format some text. Some ways of finding the responsibilities of a class include grouping of method names and drawing the relations of the class members.
Responsibilities can live on the interface level, and on the implementation level. Classes can either delegate the responsibility to other classes, or they can implement the responsibility themselves. When a class implements too many responsibilities, it’s easier to move just the implementations those responsibilities to other classes, and keep the original methods. These methods then act as a gateway to the new classes. Over time, we can modify the system so it only uses the new classes, and then we can remove the gateway methods.

Test driven development

TDD is the ultimate agile practice. You can’t get faster feedback cycles than with TDD: often a cycle lasts well under a minute. Because of this, you always know that you’re moving to the target.

However, I’ve met very few people who know about TDD, and even fewer who actually practice it. I was at a technical meeting a couple of months ago, where the presenter asked the question who knew about Test driven development. Out of about 50 developers who were present, only about 10 raised their hands. The question who had experience with it managed to raise only three or four hands.

But it’s even worse than that. I’ve worked on quite a few projects, and most of them had a limited set of unittests. The project that I’m working on now only has about 42% coverage, and a good deal of code is covered by useless or unreadable tests. Useless, because the tests don’t actually check the behavior. Unreadable, because of duplications, long setups and multiple asserts per test.

As an industry we know that unittests are important to catch bugs early on. But unittests are often written after the production code, and that’s boring and complicated. So, often it is skipped altogether. Writing unittests before writing production code, or actually, alternating between writing tests and production code, would help.

The main sequence of TDD is as follows:

  • Red: Write a test to specify the behavior. This test will fail because the behavior doesn’t exist yet, resulting in a red bar.
  • Green: Write the behavior specified by the test. Get it working as soon as possible, it is OK to make a mess.
  • Refactor: Eliminate duplications. Both from the production code and from the tests. This last point is important, because the tests need to be readable to be useful.

When you follow this sequence over and over again, you’ll start to notice that the code you produce is quite nice to read, and well factored. The code will tell you how it wants to be designed: design emerges, instead of being imposed on the code.

Fixing bugs is also faster with TDD. Once you’ve located the problem, write a test to verify that problem. Then adjust the test so it will verify the correct behavior. At that point you don’t need to go through the compile/deploy/test cycle again. You just run the unittest to see if you’re making progress. Then, when you fixed the behavior according to the test, you can always test it manually.

The biggest downside of TDD is that it is hard to learn. Perhaps this is the reason why it is not practiced as much as it should. It is a mindset, a habit that needs to be formed. It will take months to get good enough. Therefore, it is recommended to practice on a side-project, or at least practice on small, clearly defined pieces of behavior. But once you do use TDD, you will be a better developer.