6 Steps for Legacy Software Migrations
Porting legacy code from Motif, MFC, Photon, or a previous Qt version to a more recent Qt version can be a big challenge. There are a number of pitfalls in a large porting effort that can significantly increase time, cost, and complexity, leading to risk of project derailment. You may have languages, frameworks, and windowing systems that are no longer supported or that leave you unable to find knowledgeable talent. On top of that, legacy UIs tend to look very dated compared to today’s fluid animations, context-sensitive controls, and responsive interactions, requiring more than just a simple port to bring them up-to-date. All of this may be so overwhelming that you aren’t sure where to even start.
Let’s say you’ve already made the decision to do a legacy software migration. So, what’s next? This blog will go over the most important steps in a legacy software migration, to help you execute your own.
Step One: Make It Build
The first step of a migration is to make the source code build on your system, to avoid working blindly when migrating the code base step-by-step afterwards. This might include commenting out source code temporarily, strip non-essential third-party dependencies or activating compatibility functions in the framework.
Do whatever it takes to make your application build. Stub out the bits that do not build, temporarily remove modules from your make files if they are too large to just stub out, use the preprocessor to remove blocks of lines from the build – just keep commenting
away until it builds.
The secret is to do this in a controlled, reproducible, and reversible way because you have to know exactly which bits you have temporarily removed so they can go back in again after they have been properly migrated. You might not need to migrate all of that stubbed out code; most codebases on long-evolved projects contain a fair amount of dead code, and a migration is the perfect opportunity to uncover and remove it.
To make sure you’re properly tracking migration-related comments, use a very distinct label, not just TODO. Although it may be a bit extreme, something like I_AM_STUBBING_THIS_OUT_BUT_IT_NEEDS_TO_GO_BACK_IN_LATER is a far better choice. Why? Chances are that your code is already littered with TODOs and you need to be able to clearly distinguish migration stubbing in the source. Don’t worry about making your code look ugly; these will all be removed once you are done.
Step Two: Start with the Fundamental Features
After you’ve managed to make things build, start migrating the key features. Establish a hierarchy in your code that identifies the key features of your application that are fundamental to everything else and work towards completing those pieces.
For a Motif migration, we would start by porting the core. It’s not very helpful to, for instance, work on a ‘Preferences’ dialog that lets the user configure how the drawing is displayed because you can’t create a drawing yet. It makes more sense to get the main window to draw content than to start with a ‘Preferences’ dialogue.
For an MFC migration, we would start by porting the dialogs, any reusable widgets, and finish with the main window. This is because the main window can keep using MFC while the dialogs use Qt, temporarily.
For the migration of an older version of Qt, the port is more horizontal. For instance, we would start by porting all usages of QPointer list, then port all usages of QCanvas and so on, and so on.
Step Three: Testing
To test the migrated code, developers run unit tests, if they’re ready, and do basic interactive testing. In addition, we have a quality team that can do more systematic testing, but this requires the customer’s giving us either a test plan or documentation that will help us create a test plan ourselves, such as a user manual. We don’t know the application as well as the customer does, of course, which is why it’s important that they provide us with this type of information. If the customer already has automated tests, for instance via Squish, that’s even better.
Once you’re confident that your test plan is complete and you can perform all tests with the expected outcome, then congratulations! You’ve completed your migration!
Step Four: Automated Tools
The parts of the migration that can be automated can range from simple search/replace to very complex parsing and replacement operations. We have developed internal scripts and tools for this. For example, in a migration from Motif to Qt, code like the following would likely occur rather often:
XmToggleButtonGadgetSetState(_onoff, sc && sc->isEnabled(), false);
This, with the right set of tools, can be automatically migrated to:
ui->_onoff->setChecked(sc && sc->isEnabled());
It’s not desirable for an engineer to type in all the input parameters each and every time; it’s time consuming and error-prone. However, an editor that’s configured with lots of intelligent macros to automate these types of transformations can be of tremendous help. Once the engineer identifies the situation, one can trigger the code transformation and review the outcome instead of having to manually look up parameter order, and copy and paste the separate bits.
Another example of tooling help is switching versions between the original, unchanged application and the new one under migration. We typically keep the two code trees next to each other, and use KDAB-designed tools to allow an engineer to quickly jump between the two versions of the same file in order to check on what any given piece of code looked like in the old version, open another window with the diffs, and so on. Search tools are also an indispensable part of a migration engineer’s toolbox. As an example, it’s a great help for an engineer to place the cursor over an identifier and hit a key combination to instantly locate its definition or any references throughout the source tree. While most modern IDEs offer these capabilities, many don’t function unless the source builds completely, which makes them useless for migration work. Text-based searches using complex regular expressions can provide many other ways to make pointed analysis, even on poorly-formed source code.
We have also developed actual C++ libraries to help with migrations by providing intermediate layers, for instance, Qt 3 compatibility functions and MFC containers for Qt items.
Step Five: Add New Features, If Any
Try not to add new features in the middle of the migration. We need to be able to compare the initial version with the migrated version. They should behave as much the same as possible. Of course, we’re happy to add the new features after the migration.
We know it is sometimes unavoidable for changes to occur during the migration, as clients sometimes need to make changes to the original version while we are doing the migration. In such cases, we can merge and port the customer’s changes after we have finished porting the original version of the application.
From KDAB’s Experience…
At KDAB, we’ve fine-tuned the migration process over 15 years, successfully migrating software from a wide variety of frameworks, with the majority migrating to Qt. Based on our experience, we know that, regardless of the framework, operating system, or language, all migrations share common steps that ensure success and have common pitfalls that derail the best of efforts. With this awareness, we’ve developed internal tools and know-how to help speed up the process. Feel free to contact our team at email@example.com or click the image below to find out more.