The Compound effect in software


A common problem among software projects is its increasingly growing time to maintain and add new features. The balance between adding new features and fixing bugs seems to change over time. In a brand new project it’s all about implementing new ideas but as time passes by the code is slowing us down. This is due to all the (small) decisions that were made over the duration of the project: the compound effect.

As developers we make decisions about our code all the time. Should we write a quick fix or solve it the right way? Do we write automated tests or will we skip it because it takes longer than writing the production code? Do we only do the minimal necessary work to implement a new feature or do we also apply the boy scout rule; to leave the code in a better condition than we found it.

The answers to these questions depend on an array of circumstances. Are we under time pressure or are we just tired after a long day? We might pick the easy way out. However, every small decision we make will influence other decisions that will be made in the future.

Before we continue with some examples, let me give you a short definition. Compounding, compound interest or the compound effect is the aggregation of changes over time. Every change leads to more changes, the more changes there are the more additional changes will happen. This can go in two directions: positive or negative, but they tend to grow exponentially. Let me show you some examples.

The power of 1 percent¹

In 110 years the British cyclists had never won in the Tour de France. Their results and reputation got so bad, that one of the top bike manufacturers refused to sell bikes to them because they were afraid of bad publicity. At some point the Brits decided to hire Dave Brailsford. Who stuck to a strategy he called: “the aggregation of marginal gains”. He looked at all the aspects of bike riding, and tried to improve everything by only 1%. At first little could be noticed, but after 5 years of marginal improvements the results started to compound. The British cyclists “suddenly” won 60% of all the gold medals at the olympic games in 2008, set 9 Olympic records and 7 world records and won the Tour de France 5 times in 6 years. During ten years they won 178 world championships and 66 Olympic or Paralympic gold medals.

The compound effect can work for us, but it can also work against us. Let’s take a look at the other side of the coin when it’s spiralling out of control.

Broken window theory³

Let’s say we have a street somewhere in a city that’s in good condition, it’s clean, well maintained and all the buildings are in use. One day one of the shop owners moves away from the street, the building becomes vacant. So far all of this is still fine, until by chance a window gets broken; let it be by an accident, storm or violence. Normally, if the shop is occupied the owner fixes the window as soon as possible otherwise it makes it less attractive for customers to shop there. But in this case it might not be fixed at all.

So what’s the big deal? It’s only a broken window. This is however, where the compound effect starts to kick in. People walking there at night might feel it’s not so bad to put some graffiti on the building, it’s not like it’s used anyway, right? A window is already broken, how bad can it be? But now, with a broken window and graffiti on the walls it probably won’t take long until another window gets broken, more graffiti will be added and this might even open the doors for squatters to occupy the building. The worse the appearance of the building is, the more trouble it will attract, people might start avoiding this street altogether. The other shops in the street get less customers, and might decide to move away as well! Then things get worse and worse exponentially over time; this is the compound effect too.

Compounding Software

The same thing happens with software applications. We get assigned to work on a project that’s in horrible condition; hard to read, untested and difficult to change. If we have to do a minor bug fix, we tend to feel like it’s way too much work to clean it up as well. The fix might take 15 minutes, but cleaning it up and testing it, the rest of the day, if not more. So it’s tempting to just apply the fix without testing and to forget about the boy scout rule: to leave the code in a better condition than you found it.

It didn’t feel so bad making that small fix, it was quick, the manual test went well, maybe you even got some positive feedback from your manager for handling it so quickly. However, the consequences of this approach might not become apparent until weeks or months later.

The challenge with this approach is that bugs are more common in untested, hard to read code. It’s only a matter of time before another minor bug fix needs to be done on this piece of code. Every addition makes it more complex and being untested and hard to read doesn’t help. It’s likely that over time more parts of the system depend on this part, making the whole system increasingly unstable. A colleague or your future self might want to refactor it in a later stage. Which has become harder to do, because without those tests, how do you know if everything still works as it should? Without code that is easy to read, it is harder to oversee the consequences. The problems don’t stop there. It might become a big frustration to work on this particular project, lowering team morale. Fixing bugs costs more time, motivation is lost to clean up other parts and the time it takes to implement a new feature becomes longer. The compound effect is hurting us.

It is not so obvious to predict the consequences of writing that small bug fix. But look at it from a higher level, ask yourself, how might this affect me, the team or the application in a month from now, or a year, or 3 years?

Working on badly structured code where every time you fix a problem something else breaks, is frustrating. Demotivated developers spend less time on improving the system leading to more problems in the future. Small decisions matter.

Albert Einstein once said:

“Compound interest is the eighth wonder of the world. He who understands it, earns it … he who doesn’t … pays it.”

Do you want to pay it, by being unproductive, frustrated and costing the company money? Or do you perhaps want to earn it, by being productive, happy and saving the company money?

By breaking the cycle of bad habits and consistently applying good habits, we can let the compound effect work in our favor. Make small steps of improvements every day, so that it will compound over time. Just like Dave Brailsford did when he helped the British Cyclists to win a lot of races and set new world records. “The aggregation of marginal gains” is all he did.

The Japanese would call this approach: Kaizen⁴:

“The message of the Kaizen strategy is that not a day should go by without some kind of improvement being made somewhere in the company.”¹

The plateau of latent potential

Certainly, the benefits of this approach do not always appear to be clear from the start. This is called: The plateau of latent potential¹.

When a feature is implemented we often get feedback very quickly. We can show our (preliminary) work to our team and stakeholders and then we can make additional changes to it, to receive additional feedback. We have a linear function of effort and result. This is not always the case with improving the quality of the code.

As illustrated in the image above, when you start with small improvements, you might expect results right away, but they are still hard to find. Quite some effort is put in, but with little reward; this is the valley of disappointment.

Only by consistently applying good habits; small code improvements, over a longer period of time, will the results become visible and start to compound. The easier it is to read code, the easier it is to add a new feature. The better the code is tested, the more bugs are prevented and the safer it is to clean up the code. The cleaner the code, the easier it becomes to clean the rest. Time spent on building new features increases, while time spent on fixing bugs decreases. This improves the mood of the developers as well. Leading to more energy and motivation to keep the system clean and to improve it even more. Not to mention that it becomes easier for a new developer to start working on the project.

Where to begin

Readability

Naming is important. Give classes, functions and variables good names. Be clear and precise. Make the intention jump out. This also applies to higher level concepts like packages, modules and services. Another name for this is: “Screaming Architecture”⁵. It simply means that just by looking at the architecture, it’s clear what its purpose is. Make sure to rename any text, name or title that is not clear right away, the moment you encounter it. Make code read like a book.

Remove unnecessary indentations and empty lines. Your IDE can do some of the work for you. Comments that don’t add anything, or worse, commented out code, should be removed.

It’s easy to add logic to an existing function like a procedural script. But it makes code hard to understand. Extract logic to classes and functions so that they have only one reason to change. It should have a clear purpose with a name that indicates exactly what this is. Another benefit is that it makes code reusable!

Extensibility

Code must be able to be extended with minimal impact on the current code base. If you find yourself changing existing code, especially with conditional logic (i.e. if-statements) then this is often a sign of bad design. It’s good practice to improve the structure of the code so that the next time something has to be added, it will be straight forward. Think about this when writing a new feature. The ‘correct’ solution is often the one that can be extended easily.

Testing

Reproduce a bug first with an automated test before writing a fix. This ensures three things. First, that the bug is actually fixed. Second, that the test correctly catches the problem. Third, that it will never come back.

Writing a test for a new feature should be done for at least two reasons: Firstly, to prove the new feature works as you intended. Secondly that the feature keeps working correctly in the future. And this is a very important, often overlooked point. Sometimes it looks like the test isn’t adding any value or you have seen it work manually. But who’s to say it still works correctly after some refactoring or adding new functionality to it?

Conclusion

The benefits of small improvements are way bigger than you might predict from the start. Apply above mentioned points for everything you touch, to improve the code at least 1% every time and see your results grow exponentially over time.

Sources

1 Atomic Habits — James Clear
2 The Compound Effect — Darren Hardy
https://en.wikipedia.org/wiki/Broken_windows_theory
https://en.wikipedia.org/wiki/Kaizen
5 Clean Architecture — Uncle Bob


Leave a Reply

Your email address will not be published. Required fields are marked *