Table of Contents
Table of Contents
- How to Know It’s Time to Update?
- Ok, We’ll Update. What Are The Options?
- Summary and Considerations
- Don’t Panic and Update: Legacy Codebase Update Checklist
A programming saying to live by is “code that you don’t touch gets rusty over time”. We have to realize that our piece of software is not a completely isolated, independent unit (even when using containerization like Docker) but something that lives in a continuously evolving ecosystem. The platform where we start running the first version of our system is probably not the same one that runs it today. Or tomorrow.
To make things more complicated we rarely build everything from scratch, instead, we rely on dependencies – hats off to the underappreciated community of Open Software – which rely on dependencies, which rely on dependencies… which end up with a huge matrix with constraints on specific versions that we can or cannot use under the hood.
If we neglect the need for regular maintenance we can wake up one day being forced to make a huge change to our system – because we are not able to add a new feature or bug fix or, in the worst-case scenario, we are even no longer able to run the application.
How to Know It’s Time to Update?
First, to understand: what’s old code in Ruby?
But first things first – what’s old Ruby code? There is an old maxim saying “The best code is no code at all”. Why? Because every line of code you write is already a legacy code that might contain bugs and needs to be maintained. There is always a risk that a line that works today will stop working tomorrow for dozens of reasons.
That’s why we write automated tests and run them as often as possible so we can find:
- Regressions and unexpected problems connected with changes in the environment that runs our software
- Known security issues in our software. I've seen dozens of applications with Gemfile fixed on forked versions or simply not updated gems since the initial installation
- Breaking changes in newer versions of dependencies
And how does that affect the business?
There is a huge business risk running on an outdated legacy system – it might slow down the development of new features, make it harder to onboard new team members, trigger a breakdown or be a foundation of security leaks that can cost businesses a fortune.
According to the Data Breach Report by IBM, which surveyed 550 companies from 17 countries in 2022:
- Data breaches cost, on average, $4.35 million to the affected companies
- Each stolen record costs $164 on average
- It takes 277 days on average to identify and contain a data breach
- The industries with the most costly breaches are Healthcare, Financials, Pharmaceuticals, Technology, Energy, Services, and Industrial
Source: Data Breach Report by IBM
Ok, We’ll Update. What Are The Options?
The easiest approach seems to be the best one – just make regular updates and test your codebase in the environment as closest to production as possible.
Regular updates are like brushing your teeth twice a day and visiting your dentists every half a year – it does not bring much pain and can help you enjoy a beautiful smile to old age.
It doesn’t guarantee you won’t have any problems but it reduces the risk to a bare minimum. But if you visit a dentist only when pain comes out, you can quickly lose all your teeth.
We’re happy to live in an era when most of the job can be done for us by others. Tools like dependabot or renovate can automatically check what can be updated and prepare pull requests with changes, so it’s mostly a matter of goodwill.
What if my codebase is already out of date?
In case you are learning your lesson and you already have an outdated legacy codebase, you don't have to start crying.
But beware of jumping into deep water and start improving everything in a single step. It's a straight path to a broken application with infinite hidden traps waiting for you.
What I would suggest instead is to make as small a step forward as possible. Update one thing by one version, verify if everything works, and repeat. Do not stop working on new features in the meantime since updating itself might be a long process. If there are backward incompatible changes, adjust your codebase first (and break it down to multiple tasks if needed), verify it works, and then make the update. Learning about refactoring patterns can also help to improve legacy codebase in general.
It might be worth mentioning, that a clean codebase with clean architecture, among many other benefits, can also minimize the cost of upgrades or swapping the dependency, or even replacing whole modules of an app with something else, written from scratch or replaced with 3rd party.
If I don’t have time, what should I prioritize?
The riskiest decision you can make is to postpone security-related updates.
Security updates are easy to ignore with the false belief “it won’t happen to me”, but it’s a real Russian roulette that can kill the business.
The next in line are updates that fix annoying bugs or block your development.
The last one is just regular updates, which are like “brushing the teeth” - it’s not a problem if you skip it from time to time, but don’t make it a bad habit.
Old Ruby Codebase - Summary and Considerations
In last words, I would like to emphasize the need to perform updates on a regular basis to maintain a healthy application. Underestimating this can hit our business badly in many unexpected ways. So, as always, prevention is better than a cure.
If you neglect that part of development, don’t panic; simply introduce a repair plan with slow but continuous improvement and you can drag yourself out of trouble sooner or later.
Don’t Panic and Update: Legacy Codebase Update Checklist
- Assess what needs to be updated
- Break down updates into tasks - the smaller the better
- Create a realistic plan with focus and milestones - it’s not possible to update and test “everything” in one sprint
- Prioritize as: security-related updates, blockers, and regular updates.
- Don’t neglect new features and maintenance - updating itself is a long process
- Perform updates by one version at a time, verifying if everything works
- Handle backward incompatible changes with care - adjust your codebase first
- Take the opportunity to improve processes for next time - Why was the update necessary? Which risks could have been minimized? Is the test coverage sufficient?