Imagine a situation in which you need developers to add new features to an existing and well-functioning product. Let’s say you want to add billing to your online lending platform. In cases like this, developers may conclude that they’ll need to update the app’s code. After all, they might reason, why else would the client want to update perfectly functioning code? And you, the client, might think that adding a couple of features has nothing to do with a code update and that the developers simply want to make more money. However, the truth is that their decision is based on thorough code analysis and business logic.
In this article, we’ll talk about when updates are necessary and how to make them without affecting the app’s functionality. It’s especially important in fintech, as the technology – and the users’ skills – keep developing. And as this happens, they get more demanding and want more features like better security or the ability to clear payments online.
What does legacy code actually mean? Legacy code is the pre-existing code of a product – already functioning or that’s just finished – but not yet launched, that needs to be completed in some way. When a business has an app that needs additional features, or the code is almost done but the original development team couldn’t complete it, it means it’s time to update the legacy code.
As you can imagine, developers can’t just smack a feature on top of the code, since everything in the code is interconnected. Numerous factors will affect this change. And since all code is unique, there’s no single recipe for how to do it. When you ask for an update to your product’s code, you may soon find out that the code doesn’t allow it because of weak code architecture, poor code quality, or insufficient test coverage. Which is why, whenever you send this type of request, the first thing the development team will do is a code review. As soon as they get the results, they will suggest a scenario that would work best for your application.
Before developers commit to changing the code, they need to know what they’re dealing with. For instance, in the nearly eight years we’ve been working in fintech, we formed criteria to evaluate projects we’re offered to gauge the potential level of difficulty. This way, we know what to do if someone comes asking for a calculator or a smiley face in the right bottom corner, and we know what solution to offer to businesses. Here are the possible scenarios:
1. Developers don’t commit to the project when they see the domain of the app is too complex and unclear. It’s possible that adding to it would take up too many resources with no equivalent benefits for their company. This can happen when the original code was written badly, and clearing up someone else’s mess just isn’t worth it. Or, if the request is unrealistic – like re-writing the entire software for the stock market from scratch – maybe you should take some time to rethink your request.
2. Developers write the code from scratch – in some cases, development teams don’t work with the pre-existing code base at all. They may use the old code to understand the product’s business logic or to analyze functionality that was planned for the future but wasn’t added. This allows them to avoid making the same mistakes the previous developers made.
At Django Stars – and I’m sure many other companies share this conviction – the biggest benefit of this approach is that we can guarantee a clear and smooth structure in the end product. At this point, it may be hard to understand why your old code doesn’t fit the bill anymore, but you should talk about it with your technical partner and go through all the pros and cons. Often, starting from zero is the faster solution and will save you a lot more money than adding to the old code.
To give you an example: once we had a client who asked us to update the code that he himself started writing years ago, before he outsourced it to someone else. But it had at least 20 spots that needed updating before new logic and/or features could be added to the code. And with such a large number of upgrades, you can easily damage one part of the code by changing something in one of those 20 code parts. But if we were to write our own code according to this client’s specification but with a more transparent architecture, there would be only one, at most two, parts that would require more work.
3. Developers work with the client’s code base, adding desired features while updating (refactoring) the old code step by step, part by part. This is only possible when the code structure of your product is clear and logical, and also fully test covered. But in any case, you have to fully talk this through with your development team. Agree upon whether they should rewrite one part of the code and then build the new logic, do it simultaneously, or if some other way is more appropriate.
At this point, it’s important to understand that refactoring the legacy code is a vital part of code maintenance and, basically, of the life of any product. It’s also why you have to make sure you **provide sufficient documentation** to the development team.
As you can imagine, this scenario is more demanding than #2, when you write code from scratch. Not because developers don’t know the technology used, or don’t understand the code. Code is all about the vision of the ones who write it, and about the architecture. Working on legacy code written by someone else can be difficult, as their logic most probably will be different from yours. Which is why you should accept that when developers work with someone else’s code, they can’t guarantee the same high quality as when they work with their own code logic and structure.
It’s possible, though, that your new development team will be able to give structure to the old code if there’s a mechanism that properly works. In this case, they don’t need to change or re-write this part of the code. They only have to know what format the incoming data should have, and the format of the outcome data you should get.
4. In the rarest cases, it’s possible to continue working on the old code base by just adding features. This is only possible when the code is so flawlessly written and thought through that you don’t need to change anything at all. However, this has never happened to us, as everyone has their own vision of code architecture. You’d have to be extremely lucky to find code written by a soulmate. Someone else’s code is very hard to get into.
If you and your development team finally decide to start updating the code, there are several factors that define to which extent it should be changed, and how critical these changes are. These are age, code architecture, test coverage, and deployment.
- Age. Whether the code is one year old, or five, can make a great difference. For instance,you should understand that five years in Python and Django – the technology we work with – is a galaxy far far away. So much has changed during this time, and the code most probably will have huge holes in its security system, etc. Some parts of the code will be too old to migrate to a new version – it will be easier to re-write them.
- Code architecture. Is it monolithic or microservice? If developers can divide the app into parts (like CRM, CMS, website and file storage), they can update it part by part. But if there are no visible parts, the only option left is to rewrite the code. Usually, if it’s not possible to divide code into parts, it’s badly written, and refactoring it will be a pain. Which is, again, may be difficult to grasp, but it’s better to listen to your technical partner.
- Test coverage. This defines whether the code is properly covered with tests (and what tests exactly) or if the team needs to write them.
- Deployment. The team will check how the deployment goes when the new code enters the old system.
At this point, we’ve covered all the technical aspects, but there are also some managerial issues you have to resolve before you begin with the update. The to-do list isn’t long:
Make a list of what you’re expecting from the app, the changes that you want, and the goals you want to achieve. You and your team should be on the same page about what you’re going to do.
In order not to lose data (and, consequently, your client), make sure the team establishes how the app will work during the update and how the transfer from old code to new code will be done. For instance, in our Python programming practice we once created a proxy that switched users to the old or the new code base depending on their requests and the current stage of the code update.
Some other times we’ve had to transfer a large user info base, so we created a mediating script that helped us get the users one by one from the old version to the new one. This way we could test how the transfer works and see that none of the information got lost. But please note that every product will require an individual solution.
One of the reasons for this are the different risks that are characteristic of different cases. To name an easy one – a bad database structure causes slower working speed.
If there’s enough test coverage, it’s great. If there is no test coverage, it makes sense to first write the tests – or, rather, potential ways to proceed with the update and corresponding tests. And only then will you write the implementation. This is something you have to settle on before your team begins the work.
At the same time, they need to decide which tests they will use. To give you a picture, there are unit and integration tests. Unit tests are the ones we use on a single feature, while integration tests are more laborious and test how different elements work together. An experienced team will check if there are any relevant tests in the old code base that can be used. If they use an old test on a new feature, and the test is successful, this proves that this particular update is flawlessly integrated into the old code base and everything works fine.
Now we’ve looked at the legacy code update from every possible angle. We established what it is and how it works, and what needs to be done for a smooth and successful update. First, developers evaluate what they have to work with, and whether they can work with it at all. Then, they check age of the code, its architecture, and test coverage, which will define their further steps. And as soon as you coordinate your goals and expectations from the project with your team, settle on the transfer flow, and ensure sufficient test coverage, you’re good to go.
My first and foremost motivation for writing this article was to help clear up any possible misunderstandings between businesses and teams that provide web development services. As my team and I have learned over the years, these can occur in the places you least expect them. In conclusion, I hope I have explained the inner workings of a development team when it comes to a legacy code update, and that this information will help you and your business grow and improve.
This guide about How to Successfully Update Legacy Code was originally posted on Django Stars blog. Written by Alexander Ryabtsev - Software Engineering Lead at Django Stars