Legacy code is a fact of life. It is a combination of code smells, changed business requirements and ageing technology. It can be difficult to work with - especially in a flexible language like Python. There are many definitions, but in his classic work on the topic, Michael Feathers writes that legacy code is just code without tests. Another take on it is that of the authors of The Mikado Method, who mean that legacy code is just code that someone else has written - keeping in mind that someone else can be yourself just ten minutes ago.
It is difficult for developers to control the ever-changing needs of the world outside, but we do have control over the health of our codebase. If you have clean code it is easier to handle changed business requirements and replace obsolete technology as you go. Funnel was founded in 2015 so the technology choices are still quite fresh, although the product has evolved over the years. Many of the ways the Funnel dev teams work are known to safeguard high code quality. These strategies enable us to implement new functionality quickly and with confidence.
Small iterations and legacy code
Product development at Funnel does not include a roadmap or long term planning. Instead, we aim to implement new functionality using small experiments. Working in short iterations means that we can learn new things and enhance the product without taking big risks. Hence, developers at Funnel work close to production and deploy code several times per day. Deploying often means that each team must be able to deploy without affecting the code of other teams too much. A decoupled microservices architecture and organising teams according to Conway’s law enables safe continuous deployment. Releasing code into production is painless. However, making small experiments and deploying often would not be possible if our codebase wasn’t in good shape.
Testing the legacy
There is one tried and tested way of cleaning up a legacy codebase, as described by Michael Feathers. A legacy codebase often provides real business value and we want to look after our stakeholders. First, we need to make sure that we do not break anything. To make sure this does not happen, we need to cover code that needs to be changed with unit tests. Then we do the refactorings and add tests to those. At Funnel, many teams like to use the built-in refactoring tools in IDE:s like PyCharm as much as possible, for safe changes with less intellectual load. Once the code in question is in better shape, new features can be added.
It can be difficult to add unit tests to messy legacy code and it might not be possible to test the functionality you want to change straight away. In a legacy application, it is also likely that the documentation is missing or is out of date. Maintaining documentation is hard work. Unit testing is an easy way of documenting the code, whilst safeguarding existing functionality. Code that can be tested easily is also likely to be simple and of high quality. How Funnel sees testing is one of the things new graduates are taught when starting their career at Funnel. Unit tests are considered integral to achieving self-documenting code.
Like in other organisations, most Funnel employees are working from home now. The organisation has also grown rapidly in the last few years. Self-documenting code, conversation and forming relationships have always been important in our daily life. However, during this period of remote work, the latter two have grown more difficult. This has made documentation more important and emphasised just how explicit self-documenting code has to be when more people have to read it.
At Funnel we believe in teamwork and collaboration. One way of formalising this is mob programming, which is popular amongst our self-organising teams. Mobbing comes with several benefits. It spreads knowledge in the organisation and makes the codebase less person dependent. It is also nicer and more efficient to discuss a solution as you code, rather than writing numerous comments during a code review.
One major point of mob programming is that no team member is left behind. Refactoring, rewriting old code, can be a great way to learn how to work together in a mob. It spreads knowledge of existing functionality and increases code ownership in the team. Shared code ownership fights legacy, as readability is likely to be improved and authors of the code are more likely to be available. This is good for the organisation and individual developers, who might eventually want to try new things without changing jobs.
Fighting the legacy
Being a young organisation, Funnel does not own old legacy monoliths, although we have retired old technology when migrating from Python 2.7 and older versions of Node. Despite this, we are constantly working on maintaining and improving the health of our codebase. For our developers, refactoring and testing are integral to the process of adding new features. We are expected to keep our workplace clean.
The strategies Funnel has adopted for preventing code rot go hand in hand with our values and how the company operates at large. Developers are given freedom, but also have a responsibility to do what they consider to be best for the product in the long run. This large degree of freedom is checked by the work being public, compared to other organisations. Examples of this include mob programming, which is a social activity, and that we release code often. If a team has difficulties releasing new features, they will soon receive help.
Clean code is a moving target. Since the world around us is constantly changing, code will always be "legacy" and in some form of decline. Worth keeping in mind is that despite the well-known qualities that define good code, it is also a matter of personal taste. It is up to each team to decide how their code should be.