Making a Software Module Future-Proof: Refactoring Legacy C++ to Modern Value Semantics

1.9.2026
Two bugs. Two full days lost. The answer was not the next patch, but a deliberately modernized core module. A case study.

By Tim Varelmann

This case study is shared with permission from Lucky Data.

Results at a Glance

  • A whole class of recurring bugs is now architecturally impossible. Fewer nasty surprises during development, more predictable releases.
  • Less code, and simpler code, means cheaper and faster development from here on.
    • Nearly half of the most bug-prone code is gone, with more functionality than before. Substantial simplifications in the downstream code on top of that.
    • A whole group of auxiliary classes became obsolete and was removed entirely. Ongoing maintenance work that simply no longer happens.
  • Streaming from the backend has replaced the bulk loads that used to slow users down at startup. The first screen shows real data almost immediately.

About the Client

Lucky Data is a German IT company providing IT services. It also develops logistics dispatch software that modernizes how dispatchers plan logistics in the construction industry and at inland ports. The software is in production, is developed continuously, and ships in regular releases. The goal behind that: respond faster to customer requirements without releases becoming unpredictable. Lucky Data invests deliberately in technical quality and long-term product stability. Bluebird Optimization supports this development.

This case study describes the modernization of a grown core module: from heavily pointer-based C++ to modern value semantics. The rebuild rules out a whole category of bugs, cuts the size of the most bug-prone code almost in half, and has made a faster, more responsive user experience possible. For a business whose daily high-stakes operational decisions rest on this software, that combination (fewer defects, faster to change, faster for the user) shortens the path from new customer requirements to shipped features.

--

For months, development had been humming along. New features went out in regular releases. Nothing dramatic, nothing on fire. The kind of calm water that lets you plan what comes next instead of puzzling over what is broken right now.

Early March 2026 changed that. Twice within eight days. And both times it came down to the same question: who owns this data?

The First Bug

It showed up in code that was still under development: a stubborn bug with a reproducible symptom and no obvious cause. I spent a full day chasing it, and by evening had nothing but a patch, a piece of code that eliminated the symptom without explaining it. With a release on the horizon and other things still open, I decided to postpone the search for the root cause.

A full day had disappeared with nothing to show except a fix I could not fully justify. My evening was uneasy accordingly. When you cannot explain why a bug happened, you cannot really be sure it is gone.

The Second Bug

A week later, the next one. Different surface, same feel: hard to pinpoint, symptoms that did not line up cleanly with the code that should have produced them. Almost another full day, another patch, another symptom removed without understanding it. The release was imminent. The patch went in, and the release shipped.

Once it was out, I put both bugs side by side. Their resemblance was no coincidence: different triggers, the same handwriting. It came down to one thing, namely shared access to data with no clarity about who owns it.

An Uncomfortable Opportunity

The code was designed around pointers, with shared access to data spread across large parts of the codebase. Symptoms that surface far away from their cause almost always come from pointers like these.

The conclusion was uncomfortable, but above all it was an opportunity: this structure no longer fit what the software does today and what the next releases will ask of it. As long as it stayed unchanged, it would keep enabling bugs that come back under different names. Change it, and the whole category falls away.

That is exactly how I put it to Lucky Data. Not as an emergency, but as a choice between two paths: keep patching and keep this category of bugs, or modernize the core module once, deliberately, and be rid of it.

A Short Primer: Stack, Heap, and Why Pointers Are Hard to Reason About

To explain what changed, a quick detour through how programs store data:
Computer programs keep data in memory, and memory has two main regions: the stack and the heap.
Data on the stack belongs to one specific piece of code: the function that created it. Only that function can read or modify it.
Data on the heap lives on its own. Any piece of code with a pointer (essentially an address telling the program where the data lives) can read or modify it. The same piece of heap data can have many pointers aiming at it from many parts of the program.
Why does that matter? At first glance it sounds fine: if two parts of the program both change the same piece of data, surely each has a good reason. And individually, yes, each change usually does. The problem isn't individual intent. The problem is that developers can no longer reason locally.
Here's an example: Imagine code that decides whether truck 42 is available at 3pm. It reads the data: "truck 42, free", and starts assembling a dispatch order. Between the moment it reads and the moment it commits, another part of the program, also for a perfectly valid reason, marks truck 42 as under maintenance. The dispatch code has no way of knowing. The truck gets assigned anyway.
In isolation, both pieces of code are correct. The bug lives in the space between them. And debugging it means tracing every part of the program that might hold a pointer to truck 42, which in a mature codebase can be dozens or hundreds of places.
That doesn't mean pointers are bad. Anyone who has installed a browser extension, a Microsoft Office add-in, or a game mod has benefited from them: that whole category of "extend the running program with something it didn't originally know about" depends on pointer-like mechanisms. Pointers are the right choice in the right place.
For the central data store in question though: the piece that holds whatever data is currently on screen or in its temporal neighborhood: there was no such reason. The complexity of pointers was pure cost, no benefit.

Three Things Stood Out in the Grown Core Module

Shared data access with no present-day need for it

The codebase reached for pointers as the default tool, in this part of the code as well. For what the central data store actually does, that freedom is not needed. What remains is exactly the cost described above: shared access to data with no clear owner, lifetimes that had to be reconstructed piece by piece, and the space between any two accesses as a potential bug.

Mutexes as scar tissue

Mutexes are coordination mechanisms for code that runs in parallel: they prevent two threads of execution from stepping on each other's data. But currently, this part of the software runs on a single thread. Every mutex in it had been added, at some point in the past, as a patch to a bug that looked like a race condition. They were leftovers from old firefighting sessions, and they slowed the code down.

Inheritance as the only way in

The code used an abstract base class as the only way to reach the various kinds of data objects the software processes. Inheritance is a mechanism that gives different kinds of things an overarching category. It is useful when you genuinely need that unified treatment, but in C++ it pulls pointers in almost by necessity: treating different types through one shared category requires them. On top of that come the runtime costs of dynamic dispatch, and the code becomes harder to follow.

The Refactor

With growing demands on release speed, maintainability, new customer requirements and the planned streaming capability, there was a lot to be said for rebuilding now instead of patching on. Lucky Data decided to do it.

We de-risked the rebuild through timing and scope. It started directly after a release, which gave it the largest possible distance to the next deadline. I estimated two weeks full time and planned nothing else into those two weeks. The other developers kept working on features in parallel, so no development freeze was needed.

Two decisions did most of the work.

The central data store now holds its data by value

When a part of the application needs a data object, it asks by unique ID and receives a copy. The copy belongs to the caller (and is stored in the caller's stack). Nobody else can reach in and change it. When the caller is done, the copy simply disappears: no bookkeeping, no leaks, no surprises.

Composition instead of inheritance

We adopted the well-worn piece of guidance in software design, prefer composition over inheritance. Instead of the various data objects sharing a common ancestor, they now each contain a small common piece (the properties they genuinely share) and are otherwise independent types.

The overarching category that used to be the only way to reach this data is still available for code that genuinely needs to treat the various data objects uniformly. It is now implemented with a modern C++17 mechanism called the visitor pattern, which developers do not even need to know about in order to use it. But crucially, it is no longer the only door. Parts of the application that know which category of data object they are dealing with can now ask for exactly that. Less iterating over every category and filtering afterwards. That makes the software more direct and more readable.

What Changed in Practice

  • A class of bugs is gone. Not rarer: impossible. Lifetime problems, silent data corruption through shared state, symptoms that smell like concurrency bugs in non-concurrent code: all of them require shared access. That is gone now, so they are gone too.
  • Almost half the lines of code in the core data store's implementation vanished, even as the feature set grew. What remains is code a new developer can read and understand without first building a mental map of who else might be touching what.
  • Downstream code got simpler. The data provider that feeds a central calendar view lost roughly a quarter of its code. Other UI models followed the same pattern at smaller scale.
  • UI updates became more targeted. Notifications about changes are now separated by category of data object. Components that only care about one category no longer need to subscribe to updates about the others and filter them out afterwards.
  • An entire auxiliary class hierarchy was deleted. The previous design needed special classes just to carry incremental data updates through the interface. Those are gone - the easiest-to-maintain classes are those that don't exist :)
  • A planned feature arrived much earlier than expected. Streaming from the backend was on the roadmap anyway, to replace the large initial load operations. With the pointer coordination gone, the application was ready to consume data streams without any further groundwork. A substantial part of the streaming work was therefore already done as a side effect of the refactor. The backend side has since shipped, and users see their first screen of real data almost immediately.

In short: a whole category of defects is permanently off the table, the core module is lighter, faster and cheaper to maintain and extend, and a planned feature went live considerably earlier than scheduled. We even came in slightly under the two weeks of focused work we had estimated.

Rolf Ruß, CEO of Lucky Data assesses this development as follows:

"The new architecture is a game-changer, it makes our developers' lives easier and accelerates our release cadence."

From Short-Term Fixes to Sustainable Development

The real work was treating the architecture as what it is: the decision about which bugs can be written in the first place. Identifying the aspects that no longer fit today's and tomorrow's requirements, and giving them the time their replacement takes, is where the leverage was.

The alternative path is familiar enough. You keep patching. Every single patch is defensible on its own, and every single one costs no more than an afternoon. And at some point the feature you actually wanted to build costs a quarter instead of two weeks.

Lucky Data develops its platform continuously and removes structural risk early instead of carrying it along. That is what creates the basis for more reliable releases, shorter development cycles and faster delivery of customer requirements. Bluebird Optimization supports this development with the technical expertise such rebuilds take: from the diagnosis to the shipped architecture.

Today the development team at Lucky Data is back in calm water, and it uses that calm to plan what comes next.

Does any of this sound familiar?

Bugs that keep coming back in different shapes. Parts of the code everyone is a little wary of touching. A feature that is not blocked by effort, but by the structure underneath it.

Then let's talk: book 30 minutes in my calendar. We put your last few recurring bugs side by side and check whether they share the same handwriting. If they do, I will tell you what it costs to get rid of the whole category.

And if you would rather read along first, subscribe to Bluebird Briefings, my newsletter on optimization and software engineering topics like this one.

More Posts

Success Story: Inventory Optimization Under Uncertainty at Dryft
When tight deadlines met complex uncertainty in inventory optimization, trust and innovation turned challenge into opportunity: a seven-figure cost reduction while improving service levels.
8.11.2025
Faster Solutions, Sweeter Rewards: My Solver Tuning Win at the Gurobi Summit
At the Gurobi Summit in Vienna, a solver tuning challenge turned into a race for performance—and a Sachertorte. Here’s how smart parameter choices won me cake and deeper lessons in optimization.
5.11.2025

Start improving your decisions today!

Unleash the power of modern software and mathematical precision for your business.
Start your project now