A facelift for addiction treatment
Why do we update an app, for example on our phone?
Usually, a new design or a new feature becomes available that we’re interested in. Sometimes the app had a few bugs, and these are fixed in the new version.
Or perhaps we’ve changed our phone and need the version that’s compatible with the new device’s operating system.
From the user’s point of view, it seems obvious to download the new version of a piece of software, as there’s generally nothing to lose and everything to gain.
Why keep your applications up to date?
So why does it seem so difficult to keep the frameworks and packages in our projects up to date?
It’s true that it’s a bit more complex on this side of things. You have to manage major changes, which can sometimes alter the application’s behaviour or prevent it from working altogether. You also have to retest everything to make sure nothing has been overlooked.
And then, you have to make time for it. And time is something teams never have enough of!
Many teams therefore operate on the principle: ‘we’ll update when it’s necessary/for a good reason’. This is to avoid all that ‘unnecessary’, time-consuming, and sometimes slightly risky work.
And then one day comes when we’re forced to update the dependency. For example, a particular framework is no longer maintained by the development team (and it’s dangerous not to have security patches anymore), or we absolutely need the new functionality from the NuGet package for the project.
And that’s when disaster strikes: what was a fairly simple task at the start turns into the epic Jira issue of the year.
Libraries often have dependencies of their own, and sometimes a great many in certain languages, such as JavaScript, where libraries are small and fragmented.
We fall behind by several major versions, breaking changes pile up, and nothing works anymore.
We’re stuck, the technical debt is considerable, and the only way out sometimes seems to be starting from scratch, to ‘start’ on a clean slate.
Many projects are therefore rebuilt every 3–4 years!
However, keeping your applications up to date has many advantages:
To begin with, just like with your phone, the patches released greatly improve the project’s security and the developer’s experience; fewer ‘in-house’ patches to provide and fewer tests specific to these vulnerabilities, too.
It also allows you to stay informed about new developments, to identify new features that can simplify the team’s work and add real value to the project, or, conversely, to seek a more suitable alternative when a dependency takes a different direction from the team’s.
Finally, those dreaded latest changes are much easier to handle in a single version update rather than across five major versions.
Who will handle the migration?
Once you’ve made the decision to keep your applications up to date, a question arises:
Who will handle the migration? The project team is perfectly capable of handling it; you simply need to… plan for it.
Just as with code maintenance, test coverage, code reviews and bug fixes… the team will need to set aside time for monitoring and keeping the project’s dependencies up to date, which they know inside out.
To do this, they can combine a ‘Boy Scout Rule’ approach – ‘tidying up’ a little each time – with a slightly larger periodic migration, following the major releases of the project’s core dependencies (for example, every six months for an Angular project).
When teams have fallen too far behind and migrations become more painful, it is also possible to appoint a dedicated team. Some organisations have a team responsible for the technical aspects of a whole range of applications: choosing frameworks, packages and workflows. They are therefore all assigned to this task.
These teams are often made up of experienced developers who can carry out migrations fairly efficiently on projects within their usual remit, drawing on the experience gained from the previous migration. It is important to bear in mind that a comprehensive update, which can sometimes be quite complex, requires quite specific skills.
Although in-depth knowledge of the project itself is not always necessary, a thorough understanding of the dependencies before and after the migration is essential.
Furthermore, skills in debugging, troubleshooting, and sometimes even resourcefulness, are far more important than ‘pure’ development skills. This role is therefore better suited to senior or less experienced developers.
In this scenario, communication with the project team will be essential to maintain consistency.
How should we go about it?
Preparation
The project team – if they are not the ones handling the migration themselves – can kick-start the process by migrating trivial packages that have little impact. As they are closest to the project, they are best placed to deal with minor functional or technical bugs that would otherwise take up an external team’s time unnecessarily.
Firstly, if test coverage could be improved, this is the ideal time to refine it in order to avoid side effects caused by the migration.
Once the groundwork has been laid, the migration of the most impactful elements can begin.
Methodology
It is advisable to implement an iterative working methodology, such as SCRUM. This is all the more important if the team responsible for the migration is not the same as the project team, and/or if it is handling the migration of several projects in succession.
On the one hand, communicating effectively with team members who possess the functional and technical knowledge of the project is crucial to ensuring the migration goes smoothly.
On the other hand, regularly reviewing the work carried out and planning the next steps enables the process to be continuously improved and helps identify recurring errors so that they can be managed effectively for the remainder of the migration.
To refer back to the SCRUM methodology, this would correspond to the Backlog Refinement and Sprint Retrospective meetings.
Common challenges include:
Insufficient test coverage.
First and foremost, it is essential to ensure that the project has sufficient test coverage—including unit and integration tests—to prevent regressions. These tests must be easily accessible to the developers responsible for the migration; for example, they should be able to run them on their own machines or have the necessary permissions to do so on the integration platform.
Recent changes across several major versions.
Major versions are synonymous with radical changes, which are often not backwards compatible. In the event of a significant delay in a dependency, the migration can become tricky. To avoid anomalies in the project, it may be necessary to apply them one by one, after first studying the development team’s changelog.
Cross-project migrations.
Some organisations share code between teams to maintain consistency across projects: authentication, database connections, etc.
This shared code must obviously be identified for migration first, whilst ensuring that the legacy code remains available to ensure a smooth transition.
Different setups.
Conversely, teams are sometimes given complete autonomy over their application’s architecture, the choice of dependencies, and even how they structure their code. This can be confusing, and it will take some time for a team new to the project to adapt.
Team reluctance
Stepping outside one’s comfort zone can require a significant effort for a team. It is essential to ensure everyone is on board to avoid bottlenecks, and to fully integrate the migration into the teams’ sprints.
In the case of a dedicated team, a member of the project team may be designated to act as a liaison, or even participate in the migration.
Attendance at both teams’ daily stand-ups is recommended, to ensure visibility in both directions and to foster communication, which is essential.
A few tools to make our lives easier:
To avoid a tedious and time-consuming task, here are a few tools to help us keep our project dependencies up to date on a daily basis.
Visual Studio: the package manager
It’s simple and effective, albeit manual: regularly check for updates to the packages installed in our solution.
Dotnet Obsolete
https://github.com/dotnet-outdated/dotnet-outdated
This is a similar tool, but a command-line tool for .NET Core. It can be integrated into a continuous integration process to alert you when updates are available.
Dependabot
https://github.com/dependabot
It works with your preferred repository manager (GitHub, GitLab or Azure DevOps). It will scan your Ruby, JavaScript, Python, PHP, .NET, Rust or Elixir project at a frequency you define, and will even create pull requests to apply the updates. This really helps to lighten the team’s workload.
Release notes for our dependencies
It may seem obvious, but keeping up to date with new releases and applying them at the right time is essential. You may decide to apply the latest version or stay a few versions behind for the most critical dependencies, to ensure their stability.
In a nutshell:
Keeping a project’s dependencies up to date is part of a process of continuous improvement.
This extends the project’s lifespan by limiting technical debt and avoiding periodic rewrites, reduces the risk of security breaches, and encourages monitoring and re-evaluation of dependency choices.
A team with the right skills, good organisation and the right tools can make this task—which may seem daunting and unnecessary at first glance—much easier.