
von Tim Varelmann
Diese Fallstudie wird mit Genehmigung von Lucky Data geteilt.
Lucky Data ist ein deutsches IT-Unternehmen, das IT-Dienstleistungen anbietet. Darüber hinaus entwickelt Lucky Data Logistik-Dispositionssoftware, die modernisiert, wie Disponenten die Logistik in der Bauindustrie und in Binnenhäfen planen. Die Software ist im Einsatz, wird laufend weiterentwickelt und in regelmäßigen Releases ausgeliefert. Der Anspruch dabei: schneller auf Kundenanforderungen reagieren, ohne dass Releases unberechenbar werden. Lucky Data investiert bewusst in technische Qualität und langfristige Produktstabilität. Bluebird Optimization unterstützt diese Entwicklung.
Diese Fallstudie beschreibt die Modernisierung eines gewachsenen Kernmoduls: von stark pointer-basiertem C++ hin zu modernen Value Semantics. Der Umbau schließt eine ganze Kategorie von Bugs aus, halbiert den Umfang des fehleranfälligsten Codes nahezu und hat eine schnellere, reaktivere Nutzererfahrung möglich gemacht. Für ein Unternehmen, auf dessen Software täglich wichtige operative Entscheidungen beruhen, verkürzt diese Kombination (weniger Fehler, leichter änderbar, schneller für den Nutzer) den Weg von neuen Kundenanforderungen zu ausgelieferten Features.
--
Über Monate hinweg lief die Entwicklung rund. Neue Features gingen in regelmäßigen Releases raus. Nichts Dramatisches, nichts brannte an. Ruhiges Fahrwasser, welches erlaubt, zu planen, was als Nächstes kommt, statt sich den Kopf zu zerbrechen über das, was gerade kaputt ist.
Anfang März 2026 änderte sich das. Zweimal innerhalb von acht Tagen. Und beide Male ging es im Kern um dieselbe Frage: Wem gehören diese Daten eigentlich?
Er tauchte in Code auf, der sich noch in Entwicklung befand: ein hartnäckiger Bug mit reproduzierbarem Symptom und ohne offensichtliche Ursache. Ich habe einen ganzen Tag damit verbracht, ihn zu suchen, und hatte am Abend nichts als einen Patch: ein Stück Code, das das Symptom eliminierte, ohne es zu erklären. Mit einem Release am Horizont und anderen noch offenen Aufgaben entschied ich, die Ursachensuche aufzuschieben.
Ein ganzer Tag war weg, ohne etwas Vorzeigbares außer einem Fix, den ich nicht vollständig rechtfertigen konnte. Entsprechend unruhig war ich am Abend. Wenn man nicht erklären kann, warum ein Bug auftrat, kann man auch nicht wirklich sicher sein, dass er weg ist.
Eine Woche später, der nächste Bug. Andere Oberfläche, gleiches Gefühl: schwer einzugrenzen, Symptome, die nicht sauber zu dem Code passten, der sie eigentlich hätte produzieren sollen. Wieder fast ein ganzer Tag, wieder ein Patch, wieder Symptome entfernt ohne sie zu verstehen. Der Release stand unmittelbar bevor. Der Patch ging rein, und der Release ging raus.
Nach dem Release habe ich beide Bugs nebeneinandergelegt. Ihre Ähnlichkeit war kein Zufall: andere Auslöser, aber dieselbe Handschrift. Es ging um eines: geteilter Zugriff auf Daten, ohne dass klar ist, wem sie gehören.
Der Code war rund um Pointer designt, mit geteiltem Zugriff auf Daten, der sich über große Teile des Codes zog. Symptome, die weit entfernt von ihrer Ursache auftauchen, kommen fast immer von solchen Pointern.
Die Schlussfolgerung war unbequem, aber sie war vor allem eine Chance: Diese Struktur passte nicht mehr zu dem, was die Software heute tut und was in den nächsten Releases von ihr verlangt wird. Solange sie unverändert blieb, würde sie weiter Bugs ermöglichen, die unter anderen Namen wiederkommen. Änderte man sie, fiel diese ganze Kategorie weg.
Genau so habe ich es Lucky Data vorgelegt. Nicht als Notfall, sondern als Wahl zwischen zwei Wegen: weiter patchen und diese Bug-Kategorie behalten, oder das Kernmodul einmal gezielt modernisieren und sie loswerden.
Um zu erklären, was sich geändert hat, ein kurzer Abstecher dazu, wie Programme Daten speichern:
Computerprogramme halten Daten im Speicher, und der Speicher hat zwei Hauptbereiche: den Stack und den Heap.
Daten auf dem Stack gehören einem bestimmten Stück Code - der Funktion, die sie erzeugt hat. Nur diese Funktion kann sie lesen oder verändern.
Daten auf dem Heap leben für sich. Jedes Stück Code mit einem Pointer (im Wesentlichen eine Adresse, die dem Programm sagt, wo die Daten liegen) kann sie lesen oder verändern. Dasselbe Stück Heap-Daten kann viele Pointer haben, die von vielen Stellen im Programm aus darauf zeigen.
Was daran ist nun problematisch? Auf den ersten Blick klingt das doch in Ordnung: Wenn zwei Teile eines Programms dasselbe Datum verändern, wird jeder seinen guten Grund dafür haben. Und individuell betrachtet stimmt das meistens auch. Das Problem ist nicht die einzelne Änderung. Das Problem ist, dass Entwickler nicht mehr lokal denken können.
Ein Beispiel: Man stelle sich Code vor, der entscheidet, ob LKW 42 um 15 Uhr verfügbar ist. Er liest die Daten: "LKW 42, frei", und beginnt mit dem Zusammenstellen eines Dispositionsauftrags. Zwischen dem Moment des Lesens und dem Moment der echten Disposition markiert ein anderer Teil des Programms, ebenfalls aus einem völlig validen Grund, LKW 42 als in Wartung. Der dispositionierende Code hat keine Möglichkeit, davon zu erfahren. Der LKW wird trotzdem zugewiesen.
Für sich betrachtet sind beide Codeteile korrekt. Der Bug lebt im Raum zwischen ihnen. Und Debugging heißt, jeden Teil des Programms zu verfolgen, der möglicherweise einen Pointer auf LKW 42 hält. In einer ausgewachsenen Codebasis können das Dutzende oder Hunderte von Stellen sein.
Das heißt nicht, dass Pointer schlecht sind. Jeder, der schon einmal eine Browser-Extension, ein Microsoft-Office-Add-in oder ein Spielemod installiert hat, hat von ihnen profitiert: Die ganze Kategorie "ein laufendes Programm um etwas erweitern, das es ursprünglich nicht kannte" beruht auf pointer-artigen Mechanismen. Pointer sind an der richtigen Stelle die richtige Wahl.
Für den zentralen Datenspeicher, um den es hier geht, gab es aber keinen solchen Grund. Die Komplexität der Pointer war reiner Kostenfaktor, ohne Nutzen.
Die Codebasis griff zu Pointern als Standardwerkzeug, auch in diesem Teil des Codes. Für das, was der zentrale Datenspeicher tatsächlich leistet, braucht es diese Freiheit nicht. Übrig bleibt genau der oben beschriebene Kostenfaktor: geteilter Datenzugriff ohne klaren Besitzer, Lebenszyklen, die Stück für Stück rekonstruiert werden mussten, und der Raum zwischen je zwei Zugriffen als potenzieller Bug.
Mutexes sind Koordinationsmechanismen für parallel laufenden Code: Sie verhindern, dass zwei Ausführungs-Threads sich gegenseitig in die Daten pfuschen. Aber zurzeit läuft dieser Teil der Software auf einem einzigen Thread. Jeder Mutex darin war irgendwann in der Vergangenheit als Patch zu einem Bug hinzugefügt worden, der wie eine Race Condition aussah. Sie waren Überbleibsel alter Feuerwehreinsätze und bremsten den Code aus.
Der Code nutzte eine abstrakte Basisklasse als einzigen Zugang zu den unterschiedlichen Datenobjekten, die in der Software verarbeitet werden. Vererbung ist ein Mechanismus, der unterschiedlichen Dingen eine übergeordnete Kategorie gibt. Sie ist nützlich, wenn man diese einheitliche Behandlung wirklich braucht, aber in C++ zieht sie Pointer praktisch zwangsläufig nach sich: Verschiedene Typen über eine gemeinsame Kategorie zu behandeln erfordert Pointer. Dazu kommen Laufzeitkosten durch die dynamische Bindung, und der Code wird schwerer nachvollziehbar.
Mit steigenden Anforderungen an Release-Geschwindigkeit, Wartbarkeit, neue Kundenwünsche und die geplante Streaming-Fähigkeit sprach viel dafür, jetzt umzubauen statt weiter zu patchen. Lucky Data hat sich dafür entschieden.
Abgesichert haben wir den Umbau über Zeitpunkt und Zuschnitt. Er startete direkt nach einem Release, also mit dem größtmöglichen Abstand zum nächsten Termin. Ich habe zwei Wochen Vollzeit veranschlagt und in diesen zwei Wochen nichts anderes geplant. Die übrigen Entwickler haben parallel weiter an Features gearbeitet, ein Entwicklungsstopp war nicht nötig.
Zwei Entscheidungen haben den größten Teil der Arbeit gemacht.
Wenn ein Teil der Anwendung ein Datenobjekt braucht, fragt er über eine eindeutige ID an und bekommt eine Kopie. Die Kopie gehört dem Aufrufer (und liegt auf dessen Stack). Niemand sonst kann hineingreifen und etwas ändern. Wenn der Aufrufer fertig ist, verschwindet die Kopie einfach: keine Buchführung, keine Lecks, keine Überraschungen.
Wir haben uns für den Grundsatz prefer composition over inheritance entschieden: Statt dass die verschiedenen Datenobjekte einen gemeinsamen Vorfahren teilen, enthalten sie jetzt jeweils ein kleines gemeinsames Stück (die Eigenschaften, die sie tatsächlich teilen) und sind ansonsten unabhängige Typen.
Die übergeordnete Kategorie, die früher der einzige Zugang zu diesen Daten war, steht weiterhin zur Verfügung für Code, der die verschiedenen Datenobjekte wirklich einheitlich behandeln muss: sie ist jetzt mit einem modernen C++17-Mechanismus namens Visitor-Pattern umgesetzt, von dem Entwickler nicht einmal wissen müssen, um ihn zu nutzen. Entscheidend aber: sie ist nicht mehr die einzige Tür. Teile der Anwendung, die wissen, mit welcher Kategorie von Datenobjekten sie zu tun haben, können jetzt gezielt danach fragen. Weniger "über alle Kategorien iterieren und anschließend filtern". Das macht die Software geradlinig und lesbarer.
Zusammengefasst: Eine ganze Fehlerkategorie ist dauerhaft erledigt, das Kernmodul ist leichter, schneller und günstiger wart- und weiterentwickelbar, und ein geplantes Feature ist deutlich früher live gegangen als vorgesehen. Die veranschlagten zwei Wochen fokussierter Arbeit haben wir sogar leicht unterschritten.
Rolf Ruß, Geschäftsführer von Lucky Data, bewertet diese Entwicklung wie folgt:
"Die neue Architektur ist der Hammer, sie macht unseren Entwicklern das Leben leichter. Somit gehen weitere Releases schneller durch."
Die eigentliche Arbeit bestand darin, die Architektur als das zu behandeln, was sie ist: die Entscheidung darüber, welche Bugs überhaupt programmierbar sind. Die Aspekte zu identifizieren, die nicht mehr zu den heutigen und zukünftigen Anforderungen passen, und ihnen die Zeit zu geben, die ihr Austausch braucht, war hier der Schlüssel.
Der Alternativpfad ist ja bekannt: Man patcht weiter. Jeder einzelne Patch ist für sich vertretbar, jeder einzelne kostet nur einen Nachmittag. Und irgendwann kostet das Feature, das man eigentlich bauen wollte, ein Quartal statt zwei Wochen.
Lucky Data entwickelt seine Plattform kontinuierlich weiter und beseitigt strukturelle Risiken früh, statt sie mitzuschleppen. Das schafft die Grundlage für zuverlässigere Releases, kürzere Entwicklungszyklen und eine schnellere Umsetzung von Kundenanforderungen. Bluebird Optimization unterstützt diese Weiterentwicklung mit der technischen Expertise für solche Umbauten: von der Diagnose bis zur umgesetzten Architektur.
Heute ist das Entwicklerteam bei Lucky Data wieder in ruhigem Fahrwasser und plant in dieser Ruhe, was als Nächstes kommt.
Bugs, die unter verschiedenen Gestalten immer wiederkommen. Stellen im Code, vor denen alle ein bisschen Respekt haben. Ein Feature, das nicht am Aufwand hängt, sondern an der Struktur darunter.
Dann lass uns sprechen: Buch dir 30 Minuten in meinem Kalender. Wir legen deine letzten wiederkehrenden Bugs nebeneinander und prüfen, ob sie dieselbe Handschrift haben. Wenn ja, sage ich dir, was es kostet, diese Kategorie loszuwerden.
Und wenn du erstmal mitlesen willst, abonniere Bluebird Briefings, meinen Newsletter zu Optimierungs- und Softwareentwicklungs-Themen wie diesem.
Als enge Deadlines auf komplexe Unsicherheit in einem wegweisenden Lageroptimierungsprojekt trafen, wurde aus der Herausforderung durch Vertrauen und Innovation eine Chance. Das Ergebnis: eine siebenstellige Reduzierung der Lagerkosten bei gleichzeitiger Verbesserung der Materialverfügbarkeit.

Beim Gurobi Summit in Wien wurde eine Solver-Tuning-Challenge zu einem Wettlauf um Performance – und um eine Sachertorte. Wie gezieltes Parametertuning, mir Kuchen und wertvolle Optimierungserkenntnisse brachte.
