Refactoring is the process of altering existing source code without changing the actual behaviour of the application. Or in other words: If we’ve done it right, no customer will see any changes at all.
Now good luck explaining that to your boss!
But seriously, why would anyone spend time and thus money rewriting software which is — from non-developer perspective — doing fine? In this first of three articles, I’ll explain why refactoring is essential for any tech company. In Part 2, I’ll suggest how you can deal with it in your daily routine. Finally, it will go more technical when I elaborate on the many manifestations of Technical Debt and talk about tools and processes to avoid accruing it (upcoming Part 3).
Does software age?
Would you board a plane from an airline which offers super cheat flights because they simply skipped all maintenance? So far they had no crashes, so all is fine! What could go wrong?
Everyone is ok with the fact that machines need to be maintained. Parts wear off and get replaced, liquids need to get exchanged. It’s apparent because it’s common sense. A car has to go through the regular check-up, hardly anybody speaks against it. You can even see it, e.g. when the tires are worn off. But software is just bits & bytes, so there’s nothing “real”, nothing tangible that could get old, or?
There is an easy solution: just keep it running on the same tech stack with the same developers for the next couple of years! Well, no. Let me give you an example why this is no option: I once worked in a company where the essential software was quite old. It was maintainable only by very few people who were working with it for a long time already. You would hardly find new programmers for it, because nowadays nobody wants to learn the programming language the software was written in anymore.
Now imagine that these guys quit or get seriously sick! This risk called the Bus factor: how many people have to get hit by a bus until the company is in trouble? Although a bit cynical, it describes the problem pretty well — the lower the factor, the bigger your problem, because you are relying on just a few specialists to run your business. A dedicated manager should care about this!
Piling up the debt
Every time we skip maintenance, or we postpone an unavoidable change on the code, we pile up Technical debt. Think of it as a loan from a “time bank”: not doing necessary work right now will buy you more time to work on new features. It is tempting, and sometimes it’s even the only chance to keep release schedules. Keep in mind that you just postpone it though. You have to pay it back. But why is that?
Let’s think of an example. We realise that our application makes use of Anti-patterns (bad practices which software developers should never use, yet we frequently do) in some parts of the code. Instead of solving these issues directly, we decide not to touch it right now, since we have to keep deadlines. “We’ll fix that later” we reassure ourselves. Of course we don’t note that down anywhere, so this technical debt will be forgotten very soon. Aaaand it’s gone.
And here’s the catch: lousy code tends to spread. Other developers who don’t know the application design so well or are less skilled might take over these pieces of code to use them elsewhere. That is not happening because of bad intention, but because they think it is ok to do so! Due to this so-called copy-paste or cargo-cult programming, an initially small problem is suddenly everywhere to be found in your application.
But that’s not all yet. Imagine now the version of the programming language your application is written in will reach the end of life cycle at some point. It will not be developed further, not even security fixes. Updating your code is one thing, but usually you have to update the frameworks and libraries you use as well to make them work with the new language version.
So in hindsight, we did it wrong in the beginning by not spending these few extra hours and fixing the root problem. The update now becomes especially hard to do because you have many lines of code to change instead just a few. In the long run, you might have to scrap the complete application because it became not maintainable anymore with a reasonable amount of effort.
What makes Technical debt so threatening is the fact that it is usually hardly visible. It’s not like a bunch of bugs which cause unhappy customers. It’s not just lying around somewhere, sleeping peacefully if just left alone.
It’s causing real harm by slowing down the development process. The more technical debt you accumulate in a project, the slower the development of features usually gets. This costs a lot of money. Like debt from a real bank, you have to pay interest on technical debt.
There are valid reasons for accumulating technical debt though, e.g.
- you are building a prototype,
- you want to A/B test a feature because you are not sure of its impact or
- you need to keep deadlines, e.g. to avoid lawsuits or angry customers
The question is not if you have to pay back technical debts, but when. And how much interest you paid.
If you decide to go with the debt, make sure it gets paid back as soon as possible! Otherwise, at some point, the application will become almost unmaintainable, and you’ve got another Legacy application in your portfolio. Down below, you’ll find some ideas on how to pay it back.
You got kicker tables, fruit baskets, free drinks and team events. Still, you’re wondering why developers are leaving, or your company is not able to acquire new talents? Maybe it’s time to have a look at your tech stack!
Software developers need to evolve themselves; otherwise, we will get bored, since we’re curious by nature. As mentioned above the IT landscape is changing fast, continually introducing new technologies we have to learn about. We usually get unhappy when we have to only work on Legacy applications because we fear that we‘re falling behind the industry standards too much. And because it’s boring, of course. This dilemma is the emotional aspect of technical debt.
If you want to keep your developers engaged, you have to accept the fact that they have to be trained regularly. Refactoring can be training as well.
Companies spend a lot of money on training their employees, so why not think of refactoring as part of that training? Surely, if you’re only thinking short term, this will cost you money. However, if you take into account the hiring costs of developers, including the onboarding process and the time until a new developer can work productively on a project, this quickly pays off. Not to mention the knowledge that leaves your company, or the negative impact on the atmosphere in the development department if colleagues are leaving.
Another important aspect is that Technical debt is causing frustration. Why? Because developers usually know that there are ways to do it better. We are not happy to deal with clumsy workflows when we know there are modern tools to ease processes, leaving us more time to do what we are being paid for – writing code!
It is super demotivating to produce two new bugs when you just try to fix one, just because the application you are working on is so complex that side-effects can not be predicted. Missing or outdated documentation and small test coverage of the code make each new release like playing the lottery.
In the end, it’s a win-win-situation (if not even a win-win-win). You keep (1) your developers trained, up-to-date and happy, while you ensure that (2) your application will last the next years and (3) your company stays competitive in the developer market. Wow, why would you not like to do that?
There will always be people who try to hack your application. Be it for fun, fame or profit. Exploited security holes will cost your company a lot of money. It can even endanger your whole business when your customers lose trust in your company.
The problem: applications often make use of frameworks, packages or libraries as well. That code is written and maintained by 3rd parties — updating those to newer versions often close security holes. Best-case the update is just a matter of downloading the latest release. However, there will be the day when the next major version of a library is released and comes with breaking changes. You can’t update to the next version anymore without changing existing code.
At this point, refactoring is necessary to keep the 3rd party code in your application up to date. There are ways to reduce the amount of work required to do the update, yet that takes more time beforehand, which is often not spend.
Of course also our code has security holes. Usually a lot more than we want to admit. There are (commercial) tools that help scanning your codebase for security problems. But someone has to fix the findings eventually. Sometimes it’s just a matter of adding a couple of lines of code, but often enough it requires significant code changes aka refactoring.
Code requires refactoring to be kept at least somewhat secure. You can take the risk and go on with all those open security holes, or you let them get fixed. It’s a risk management decision.
Parts of code which are no longer necessary are slowing down an application either by increasing the execution time or by increasing the amount of assets that the browser needs to download. If you don’t take care of it regularly, the application will eventually become bloated and cluttered. Probably you are no longer using some 3rd party tools, yet they still get downloaded by every users browser since they were never removed from your source code.
There are many other ways to improve the performance, like replacing slow technologies with newer and often faster ones. Since we (hopefully) all know that a slow website is still one of the main factors which affects the conversion rate negatively, we should tackle that immediately.
Another performance aspect is not affecting the customer, yet still causing high costs: internal development speed. If you refactor existing workflows to make them faster or require less manual work, you will speed up the development in general. Last but not least, removing dead or improving poorly written code will make the application more straightforward to understand for newbies and yourself.
After all these important points, let’s not forget the other extreme: we should not refactor just because we can. Developers will always find reasons to rewrite existing code. Sometimes I feel the urge to refactor my code I wrote three weeks ago, just because it does not feel right anymore.
Uncontrolled refactoring can easily lead to another extreme: Overengineering. Your software gets so well designed that the average developer will not be able to grasp it. It might be right according to the books or a recent blog post, but not everybody in your team is a coding ninja and will understand it so quickly.
Done is better than perfect.
There are very valid reasons to strive for making code as “good” as possible (how to measure “good” will be covered in Part 3 of this series). For example, if you are working on an Open Source project, where you want to make sure that many people want to participate.
However, this high level of quality requires a lot of time. As company, you might need to decide to work on a bit lower level. A feature or bugfix that takes significantly longer because of refactoring might in the end cost the company more (e.g. because of lost revenue) than the value we get from the refactoring itself. In Part 2, I will cover this topic more in depth.
Conclusion & Outlook
Refactoring is necessary to keep your software alive and secure while maintaining performance. It also serves as a motivator to keep developers up-to-date and engaged.
That alone should be enough arguments to make refactoring an essential part of the daily development work. But since technical debt usually remains hidden, it’s often not paid back fast enough, so it piles up like an invisible mountain. In the next part of this article I’ll suggest a way to solve this problem. Hang on!