Blog Post
Wenn Software-Migrationen ein Unternehmen gefährden: Ein Erfahrungsbericht aus der Praxis
Software-Migrationen gehören zu den anspruchsvollsten Projekten in der IT und erfordern eine klare Strategie, umfassende Dokumentation und langfristige Planung, die das Tagesgeschäft und zukünftige Erweiterungen gleichermaßen berücksichtigt. Doch was passiert, wenn eine Migration aufgrund schlechter Entscheidungen, unstrukturierter Prozesse und fehlender Unterstützung ins Chaos gerät? In diesem Artikel teile ich einen Erfahrungsbericht, der zeigt, wie chaotische Bedingungen und falsche Prioritäten ein Projekt gefährden und das Unternehmen in eine kritische Lage bringen können.
Die Ausgangssituation: Migration zu React Native
Das Unternehmen entschloss sich, seine bestehende App von nativen Android- und iOS-Plattformen auf eine zentrale React-Native-Plattform zu migrieren. Die optimale Vorgehensweise für eine solche Migration wäre ein Feature-Freeze für die alten Apps (keine neuen Features, jedoch weiterhin Bug-Fixing), während parallel die neue React-Native-Anwendung aufgebaut wird. So hätte das Unternehmen eine klare Trennung zwischen Alt- und Neuentwicklung beibehalten können.
Die Fehlentscheidung: Hybride Parallelentwicklung
Statt auf diese bewährte Strategie zu setzen, entschied man sich, React Native parallel innerhalb der bestehenden nativen Android- und iOS-Projekte aufzusetzen. Normalerweise wird eine React-Native-Anwendung über das React-Native CLI oder Expo eingerichtet. Das CLI bietet die Möglichkeit zu tiefgreifenden nativen Anpassungen und erstellt dabei Android- und iOS-Ordner, um die nativen Codeanteile aufzunehmen.
In diesem Projekt entschied man sich jedoch, die von CLI generierten Android- und iOS-Ordner zu entfernen. Stattdessen spiegelten separate Repositories die nativen Android- und iOS-Anteile wider, sodass es letztlich drei Repositories gab: eins für das React-Native-Projekt und je eins für die nativen Android- und iOS-Komponenten.
Diese Aufteilung sorgte für erhebliche Komplexität, da die React-Native-Entwicklung zwar theoretisch zentralisiert war, praktisch jedoch auf die getrennten Repositories für Android und iOS angewiesen blieb. Der Plan war, später von React-Native CLI auf Expo umzusteigen, sobald die nativen Komponenten vollständig migriert waren. Da dies jedoch nicht der Fall war, musste weiterhin mit CLI gearbeitet werden, obwohl der spätere Umstieg auf Expo durch die unterschiedlichen Initialisierungsmethoden für bestimmte SDKs zusätzliche Anpassungen erfordern würde. Diese doppelte Verflechtung zwischen CLI und Expo führte zu einem komplexen System, das schon im Ansatz äußerst problematisch war.
Ich erwähnte außerdem, dass Expo für ein Projekt dieser Größe und Marktrelevanz die falsche Wahl sei, da eine Expo-App im Vergleich zu einer Standard-CLI-App deutlich größer ist. Die App-Größe beeinflusst die Platzierung in den Stores, da Algorithmen von Google und Apple die Größe in ihre Bewertung einbeziehen. Dieser gut gemeinte Rat stieß jedoch auf Unverständnis, und man teilte mir mit, ich würde vermutlich die Expo Go App meinen. Zudem wurde behauptet, dass der Expo-Workflow keineswegs zu einer größeren App als bei einer Standard-CLI-Anwendung führe. Diese Aussage ist jedoch faktisch falsch. Bei einer monatlichen User-Basis von mehreren Millionen aktiven Kunden könnte dies zu Umsatzeinbußen in Millionenhöhe pro Monat führen, da die Platzierung in den Store-Rankings entscheidend ist.
Die Realität im Team: Dezentrale und spezialisierte Entwicklerstrukturen
Die Herausforderungen in diesem Projekt wurden durch die Teamstruktur verschärft: Über 30 Entwickler arbeiteten an der Anwendung, jedoch hatte jeder nur spezifische, spezialisierte Aufgaben. Niemand im Team besaß das Gesamtverständnis, das für eine effektive Migration notwendig gewesen wäre. Es gab keinen Entwickler, der umfassend mit Android, iOS, React Native (CLI und Expo) sowie dem verwendeten SDK vertraut war. Stattdessen war das Wissen fragmentiert, und über die Jahre war so niemand in der Lage, die Migration effektiv voranzutreiben und das Team wurde im Zeitraum der Migration nahezu vollständig ersetzt, ohne dass zuvor Dokumentationen angelegt wurden.
Das Unternehmen hatte großes Glück, überhaupt jemanden mit meiner Erfahrung zu finden, der die notwendigen Kenntnisse in Android, iOS, React Native (CLI und Expo) sowie Wissen über das verwendete SDK mitbrachte. Doch anstatt diese Expertise zu nutzen und meiner strukturierten Herangehensweise zu vertrauen, begegnete man mir mit Misstrauen und massiven Forderungen nach schnellen Ergebnissen. Der technische Projektleiter war primär darauf fokussiert, den Stakeholdern schnelle Resultate zu liefern, statt ihnen die tatsächliche Komplexität des Projekts zu verdeutlichen und eine realistische Lösung zu priorisieren. Anstatt das Problem systematisch anzugehen, setzte er auf Druck, ohne die technischen Herausforderungen auch nur ansatzweise zu verstehen. Eine solche Haltung, gepaart mit Misstrauen gegenüber einem Experten, führte letztlich zu einer katastrophalen Situation.
Technische Komplikationen und kreative Notlösungen
Ohne die nativen Android- und iOS-Ordner im React-Native-Repository ließ sich die Anwendung nicht eigenständig starten. Um die Anwendung dennoch zum Laufen zu bringen, wurde beim Starten der Anwendung im React-Native-Repository zunächst ein kompletter Emulator mit der aktuellsten Version für Android (oder iOS) sowie einem CI/CD-automatisierten Bundle geladen. Erst durch diesen Prozess war es möglich, über den Metro-Bundler darauf zuzugreifen und den aktuellsten Code des React-Native-Repositories in die Anwendung zu übernehmen. Das bedeutete, dass die Entwickler immer gezwungen waren, einen vollständigen nativen Emulator zu laden und den Metro-Bundler für jede Änderung zu nutzen, um den Code zu aktualisieren. Dieser höchst ineffiziente und umständliche Prozess erschwerte die Entwicklung enorm und führte zu einem stark fragmentierten und zeitraubenden Workflow.
Diese komplizierte Struktur legte die Grundlage für die tiefgreifenden strukturellen Probleme des Projekts.
Fünf Jahre Migration ohne Konsistenz
Im Verlauf von fünf Jahren wurden Features stückweise nach React Native übertragen, jedoch ohne die nativen Bestandteile schrittweise abzubauen. Die nativen Codes erstreckten sich über mehr als 20 Module, die teilweise über Bridges mit React Native verbunden waren und parallel gepflegt werden mussten. Innerhalb der React-Native-Umgebung entstand eine weitere Fragmentierung durch die Mischung von Class-Based Components und Functional Components.
Bestimmte Teile der App waren mit veralteten Class-Based Components umgesetzt, die längst überholt sind. Zunächst wurde mit JavaScript begonnen und später wurde TypeScript eingeführt (JavaScript blieb bestehen), jedoch ohne strikte Typprüfungen, was allein in React Native zu über 500 Dateien mit Fehlern führte. Regeln zur Codequalität wurden nicht einheitlich durchgesetzt, und es fehlte an einer konsistenten Linter-Konfiguration, sodass veraltete und inkonsistente Muster dauerhaft bestehen blieben. Functional Components und Hooks wurden teilweise integriert, was eine Herausforderung darstellte, da Hooks in Class-Based Components nicht genutzt werden können. Statt diese alten Komponenten komplett zu ersetzen, wurde zusätzlicher Code geschrieben, um die Funktionsweise der Hooks zu simulieren. Diese Vermischung führte zu einer unübersichtlichen und schwer wartbaren Codebasis im React-Native-Projekt.
Eine weitere Herausforderung war die zusätzliche Integration von Web-Funktionalitäten innerhalb einzelner React-Native-Module, die dazu führte, dass in der Entwicklungsumgebung Events sowohl für die App als auch für das Web gleichzeitig ausgelöst wurden. Dies geschah aufgrund einer fehlerhaften Umsetzung, die die Trennung der Plattformen nicht berücksichtigte und so zu unvorhergesehenen Wechselwirkungen zwischen den App- und Web-Komponenten führte.
Die Herausforderung durch Third-Party-Libraries, Bridges und native SDK-Migrationen
Die App verwendete mehrere Third-Party-Libraries, die essenziell für den Betrieb und die Analyse der Anwendung waren. Die Libraries waren teils in den nativen Android- und iOS-Projekten und teils in React Native integriert, was dazu führte, dass die Bibliotheken parallel initialisiert und gepflegt werden mussten. Dies schuf eine enge Versionsbindung zwischen den nativen SDKs und den React-Native-Bibliotheken.
Besonders problematisch war die Integration von Push-Benachrichtigungen über Firebase, die ursprünglich in den nativen Android- und iOS-Projekten implementiert war und eine zentrale Funktionalität des zu migrierenden SDKs darstellte. Ziel war es, die Funktionen des SDKs schrittweise nach React Native zu überführen, was jedoch sowohl native als auch React-Native-spezifische Anpassungen erforderte. Da das SDK bereits teilweise nativ und teilweise in React Native implementiert war, mussten bestimmte Komponenten der Push-Benachrichtigungen weiterhin nativ gepflegt werden. Diese hybride Struktur führte zu einer starken Verflechtung und machte es nahezu unmöglich, die Push-Funktionalitäten vollständig und konsistent auf React Native, innerhalb der geforderten Zeit, zu migrieren.
Zusätzlich erschwerten Bridges die Situation. Einige SDK-Funktionen waren bereits auf React Native migriert, andere blieben weiterhin nativ und wurden über die Bridges angesprochen. Diese Konstellation erforderte, dass noch nicht migrierte SDK-Teile durch die Bridges auf die nativen Anteile zugreifen konnten, was die Abhängigkeiten zwischen React Native und den nativen Komponenten maximierte. Ein SDK-Update wurde dadurch enorm kompliziert und setzte umfassende Anpassungen in React Native sowie in den nativen Android- und iOS-Projekten voraus – ein nahezu unmöglicher und extrem zeitaufwändiger Prozess.
Mein Ansatz als Berater und das fehlende Vertrauen im Team
Als Berater wurde ich beauftragt, das SDK von der nativen Implementierung in React Native zu migrieren, ohne zu wissen, wie stark das System fragmentiert und verstrickt war. Um diese Komplexität zu entwirren und eine tragfähige Lösung zu schaffen, erstellte ich einen klaren Plan:
- Analyse der nativen Codeanteile und ihrer Funktionalität: Ziel war es, die SDK-Funktionalitäten im nativen Code zu verstehen und bestehende Probleme sowie Abhängigkeiten zu identifizieren. Hierbei sollten die nativen Anteile und ihre Verbindungen zu React Native detailliert analysiert werden.
- Erstellung einer Event-Liste: Da keine Dokumentation existierte, war es erforderlich, eine vollständige Liste aller Events, ihrer Attribute und ihrer Einsatzorte zu erstellen, um einen umfassenden Überblick über die Ereignisstruktur zu erhalten.
- Bestandsaufnahme in React Native: Der nächste Schritt war eine Bestandsaufnahme der SDK-Funktionalitäten, die bereits in React Native umgesetzt waren. Es sollte geprüft werden, welche Abhängigkeiten durch die vorhandenen Bridges weiterhin auf die nativen Anteile verwiesen und welche davon in React Native integriert werden könnten.
- Implementierung und Anpassungen: Basierend auf den gewonnenen Erkenntnissen sollten die erforderlichen Migrations- und Anpassungsschritte geplant und umgesetzt werden, um die nativen SDK-Komponenten vollständig in React Native zu integrieren.
- Tests und Dokumentation: Abschließend sollten umfassende Tests durchgeführt und eine vollständige Dokumentation in Confluence erstellt werden, um alle Änderungen und Migrationsprozesse festzuhalten und zukünftigen Teams den Einstieg zu erleichtern.
Misstrauen, Rechtfertigungsdruck und Infragestellen meines Ansatzes
Bereits nach nur acht Tagen wurde ich vom technischen Projektleiter gedrängt, einen präzisen Fertigstellungstermin (ETA) anzugeben. Obwohl ich deutlich erklärte, dass eine fundierte Schätzung erst nach der Analyse möglich sei (ich hatte gerade Punkt 2 abgeschlossen), stieß dies auf taube Ohren. Zusätzlich wurde ich aufgefordert, die noch unfertige Dokumentation vorzulegen – ein klares Zeichen für das Misstrauen gegenüber meiner Arbeit. Trotz meines transparenten Vorgehens musste ich mich immer wieder rechtfertigen und Vorschau-Dokumente bereitstellen, obwohl ich die vollständige Dokumentation erst nach Abschluss der Analyse einstellen wollte.
Hinzu kam, dass unerfahrene Entwickler Kritik an meinem strukturierten Ansatz äußerten und argumentierten, dass die Analysephase und Dokumentation überflüssig seien. Mein systematischer Plan wurde als „übertrieben“ und „unnötig“ bezeichnet, obwohl die komplexen Abhängigkeiten und die fehlende Dokumentation genau dieses strukturierte Vorgehen erforderten. Die Leitung hatte nicht nur kein Vertrauen in den Plan, sondern stellte meine Erfahrung immer wieder in Frage und ließ sich durch den „Tatendrang“ des technischen Projektleiters antreiben, der die Probleme ignorierte und den Druck auf mich erhöhte, um den Stakeholdern vermeintliche Fortschritte zu präsentieren. Anstatt die tatsächliche Sachlage zu erklären, die technischen Herausforderungen offenzulegen und realistische Lösungen zu priorisieren, legte er Wert darauf, sofortige Resultate vorweisen zu können, ohne das notwendige technische Verständnis. Eine solche Haltung, gepaart mit Misstrauen gegenüber einem Experten, machte das Projekt nur noch schwieriger und führte letztlich zu einer Katastrophe.
Der Punkt des Ausstiegs
Bereits am zehnten Tag stand ich erneut massiv unter Druck, als die Projektleitung feststellte, dass bestimmte API-Levels unter Android mit der alten SDK-Version nicht mehr unterstützt wurden (ein Punkt, welcher seit August 2022 hätte bekannt sein müssen). Dies führte dazu, dass Anwender nicht mehr erreicht werden konnten, was ein dringendes Problem darstellte.
Daraufhin wurde ich angehalten, eine sofortige Migration durchzuführen – und dies in einem extrem knappen Zeitrahmen von nur drei Tagen. Dabei sollte ich notfalls auch native Codeanteile sowie "unwichtige" Funktionalitäten des SDK entfernen oder anpassen, um eine kurzfristige Lösung herbeizuführen. Eine solche Vorgehensweise hätte jedoch die Verflechtungen des SDK und die vielen Abhängigkeiten ignoriert. Die Anweisung, schnellstmöglich Anpassungen vorzunehmen, bedeutete zudem, dass ich ohne ausreichende Kenntnis der Auswirkungen Teile des Codes entfernen müsste. Dies hätte die Gefahr geborgen, dass die Anwendung an unvorhergesehenen Stellen bricht oder wichtige Funktionen verloren gehen.
Die Projektleitung ignorierte in ihrer Forderung nach schnellen Ergebnissen die komplexe Struktur der App und den notwendigen Aufwand, um eine nachhaltige Migration sicherzustellen. Die unrealistischen Forderungen und die Erwartung, kurzfristige Ergebnisse zu liefern, ohne die technischen Konsequenzen und Gegebenheiten zu bedenken, führten letztlich dazu, dass ich mich entschloss, das Projekt nach knapp über zwei Wochen zu verlassen.
Zudem ist ein weiterer Aspekt zu erwähnen: Kulturelle Unterschiede in den Arbeitsweisen könnten ebenfalls dazu beigetragen haben, dass mein strukturiertes Vorgehen auf Ablehnung stieß. Alle am Projekt beteiligten Personen lebten im Ausland oder waren erst seit kurzer Zeit in Deutschland, was möglicherweise zu einer anderen Auffassung hinsichtlich Projektplanung und -umsetzung führte.
Lektionen gelernt: Vermeidung von Migrations-Desastern
Dieser Erfahrungsbericht zeigt, wie essenziell eine klare und durchdachte Migrationsstrategie ist und wie destruktiv Misstrauen, fehlendes Verständnis und unrealistische Erwartungen für ein solch komplexes Projekt sein können.
Wichtige Erkenntnisse:
- Eindeutige Strategie und Trennung der Codebasis: Ein Feature-Freeze und die parallele, unabhängige Entwicklung der neuen Anwendung hätten das Projekt geordnet und stabilisiert.
- Einheitliche Architektur und Vermeidung technologischer Vermischungen: Veraltete Code-Patterns und eine inkonsistente Verwendung von Komponenten sollten vermieden werden.
- Vertrauen und konstruktive Kritik: Berater und Entwickler benötigen Rückhalt und die Freiheit, strukturierte Lösungen zu entwickeln.
- Realistische Zeitpläne: Zeitliche Vorgaben sollten auf fundierten Analysen basieren und nicht auf überzogenen Erwartungen.
- Fachliche Zusammenarbeit: Kritik sollte konstruktiv sein und auf fachlicher Grundlage erfolgen.
Fazit
Software-Migrationen sind immer herausfordernd und risikoreich. Dieses Projekt hat gezeigt, dass eine klare Strategie, Vertrauen und offene Kommunikation die Grundpfeiler für eine erfolgreiche Migration sind. Ohne diese grundlegenden Prinzipien wird eine Migration schnell zum Desaster – mit ernsthaften Konsequenzen für das Unternehmen und die beteiligten Personen.