Technische Schulden in Legacy-Java-Systemen Wie man sie vor einer Migration bewertet

Technische Schulden in Legacy-Java-Systemen Wie man sie vor einer Migration bewertet

Java-Migrationsprojekte scheitern oft aus einem bestimmten Grund: Das Team hatte zwar einen guten Plan für das Ziel, aber nur ein unklares Bild vom Ausgangszustand.

Bevor Sie einen Migrationsplan für eine bestehende Java-Anwendung erstellen, benötigen Sie ein klares Bild des aktuellen Systemzustands. Dazu müssen Sie einige grundlegende Fragen beantworten:

  • Wie hoch ist die technische Verschuldung?
  • Wo konzentriert sie sich?
  • Welche Kosten entstehen durch Nacharbeiten und Verzögerungen?

In diesem Artikel erfährt man, wie man technische Schulden in Java-Anwendungen messt und was eine Java-Migrationsbewertung genau beinhaltet. Wir erklären außerdem, warum das Codealter allein kein aussagekräftiges Indiz ist, und stellen die wichtigsten Kennzahlen für technische Schulden in der Migrationsplanung vor.

Wenn Sie wissen möchten, wie Sie ein bestehendes Java-System vor der Migration bewerten, finden Sie hier die Vorgehensweise.

Warum das Codealter wenig über die Komplexität der Migration aussagt

Der erste Gedanke bei der Prüfung eines älteren Java-Systems ist, das Erstellungsdatum des Codes zu betrachten. Eine Klassendatei, die zuletzt 2009 bearbeitet wurde, muss doch problematisch sein, oder? Nicht unbedingt.

Das Alter ist ein Indikator für das Risiko, aber kein direkter Messwert. Eine 2008 geschriebene Java-EE-5-Serviceschicht mit guter Testabdeckung und klar definierten Methodengrenzen lässt sich problemlos migrieren. Ein vor zwei Jahren geschriebener Spring-Boot-Service mit 400-zeiligen Methoden und in Datenbankabfragen eingebetteter Geschäftslogik kann deutlich schwieriger zu migrieren sein. Die tatsächlichen Migrationskosten werden durch Komplexität, Kopplung und fehlende Sicherheitsmechanismen bestimmt. Das Codealter korreliert nur lose mit diesen Faktoren, anstatt sie zu messen.

Teams, die eine Java-Altcode-Modernisierung anhand des Dateialters planen, unterschätzen strukturell komplexe Module, die modern wirken, und überschätzen ältere Module, die in Wirklichkeit gut strukturiert sind.

Kennzahlen für technische Schulden, die für die Modernisierung entscheidend sind

Vor der Migration einer Java-Anwendung können verschiedene Metriken zur Bewertung herangezogen werden. Das Team von Chudovo orientiert sich üblicherweise an den vier unten aufgeführten Kategorien, da diese die aussagekräftigsten Signale für ein klares Bild der aktuellen Situation liefern:

Zyklomatische Komplexität: Diese Metrik misst die Anzahl unabhängiger Pfade innerhalb einer Methode. Eine höhere Komplexität bedeutet mehr zu replizierende Randfälle, mehr abzudeckende Testszenarien und mehr Stellen, an denen eine Migration unbemerkt zu Verhaltensänderungen führen kann. Eine einfache Faustregel: Methoden mit einer zyklomatischen Komplexität über 15 sollten genauer betrachtet werden. Werte über 30 stellen ein Migrationsrisiko dar.

Kopplungsmetriken (afferent und efferent): Zwei Werte sind hierbei relevant. Die efferente Kopplung (Ce) gibt an, von wie vielen externen Klassen ein Modul abhängt. Ein Modul mit hoher Ce-Wertzahl führt bei jeder Änderung seiner Abhängigkeiten zu Fehlern. Die afferente Kopplung (Ca) gibt an, wie viele Klassen vom Modul abhängen. Eine hohe Ca-Wertzahl bedeutet, dass Änderungen am Modul zu Fehlern im gesamten System führen. Beide Werte müssen vor der Migration ermittelt werden. Wird dieser Schritt übersprungen, wird der Abhängigkeitsgraph erst während der Integrationstests entdeckt.

Abhängigkeitsbeziehungen und Kopplungsmuster lassen sich mithilfe von Architekturanalyse-Tools wie ArchUnit, Sonargraph, Structure101, jQAssistant oder JDK-nativen Tools wie jdeps analysieren. Zum Beispiel:

jdeps --recursive --summary my-legacy-app.jar

Das Flag –summary liefert eine Übersicht auf Modulebene. Wird es weggelassen, erhalten Sie eine vollständige Ausgabe der Abhängigkeiten auf Klassenebene. Diese ist deutlich detaillierter, aber bei großen Projekten auch schwieriger auszuwerten. Die Ausgabe hilft dabei, die Abhängigkeitsbeziehungen zwischen Modulen und externen Bibliotheken zu analysieren und eng gekoppelte Komponenten zu identifizieren, die den Migrationsprozess einer Legacy-Anwendung erschweren könnten.

Testabdeckung und Testqualität: Viele Teams verlassen sich auf den prozentualen Abdeckungsgrad. Das ist irreführend. Ein Legacy-Java-System mit 70 % Codeabdeckung kann Tests haben, die bedingungslos erfolgreich sind, weil die Tests Assertions enthalten, die zwar die Existenz eines Feldes, aber nicht dessen Wert überprüfen, oder weil sie lediglich bestätigen, dass keine Ausnahme ausgelöst wurde. Anstatt sich ausschließlich auf die Abdeckung zu verlassen, liefert das Ausführen von Mutationstests mit PIT ein aussagekräftigeres Bild. Wenn der Mutations-Score deutlich unter die Codeabdeckung fällt, erfassen die Tests keine relevanten Fehler.

Status der Abhängigkeiten: Eine veraltete Abhängigkeit blockiert das Zielprojekt. Wenn das System mit Java 8 läuft und Bibliotheken verwendet, die zuletzt 2016 aktualisiert wurden, treten wahrscheinlich Kompatibilitätsprobleme auf, wenn Java 17 oder Java 21 als Zielplattformen verwendet werden. Die Integrität der Abhängigkeiten sollte unter zwei Gesichtspunkten geprüft werden: Aktualität der Versionen und Sicherheitsrisiken. Verwenden Sie Tools wie das Maven Versions-Plugin oder das Gradle Versions-Plugin, um veraltete Bibliotheken zu finden, und OWASP Dependency-Check, um bekannte Schwachstellen und gefährliche transitive Abhängigkeiten aufzudecken.

Wie eine Java-Migrationsbewertung in der Praxis aussieht

So geht das Team von Chudovo typischerweise bei einem Java-Anwendungsaudit und der Analyse technischer Schulden vor. Diese Basislinie dient als Ausgangspunkt für die Schätzung des Aufwands für die Migration bestehender Java-Systeme pro Modul.

Im ersten Schritt wird eine statische Codeanalyse mit SonarQube durchgeführt, um die Basiswerte für die gesamte Codebasis zu ermitteln. Der Befehl sollte wie folgt aussehen:

# Run SonarQube analysis on a Maven project

mvn sonar:sonar \

 -Dsonar.projectKey=my-legacy-app \

 -Dsonar.host.url=http://localhost:9000 \

 -Dsonar.token=your-token

Nach Abschluss des Scans liefert der Bericht eine Aufschlüsselung von Code-Smells, Bugs, Sicherheitslücken, Duplikationen und SonarQubes eigener Schätzung der „technischen Schulden“ in Stunden. Diese letzte Zahl ist keine Schätzung des Migrationsaufwands. Sie misst, wie lange die Behebung der Probleme im aktuellen Code dauern würde – eine völlig andere Fragestellung. Sie können sie zum Vergleich von Modulen untereinander verwenden, nicht für die Budgetplanung.

Sobald Sie diese Basislinie haben, können Subsysteme nach Risiko eingestuft werden. Ein Modul mit der Bewertung D für Wartbarkeit, 40 % Duplikation und ohne Unit-Tests stellt ein anderes Problem dar als ein Modul mit der Bewertung B und 65 % Testabdeckung. Ersteres erfordert eine Neuentwicklung. Letzteres kann wahrscheinlich durch inkrementelles Java-Code-Refactoring migriert werden.

Erstellung einer Scorecard zur Bewertung der Migrationsbereitschaft

Der Hauptzweck einer auf Java-Codequalitätsmetriken basierenden Checkliste zur Migrationsbereitschaft besteht darin, Rohdaten in ein für Architekturteams und Entwicklungsleiter nutzbares Format für die Planung umzuwandeln.

Die folgende Tabelle veranschaulicht, wie eine Checkliste zur Migrationsbereitschaft für Java-Anwendungen die Informationen nach Modulen zusammenfasst:

Modul/Service Zyklomatische Komplexität (Ø) Testabdeckung Alter der Abhängigkeiten Kopplungsgrad Bereitschaftsgrad
AuthService 8 72% < 2 Jahre Niedrig Hoch
BillingEngine 34 18% 5+ Jahre Hoch Niedrig
ReportingModule 12 55% 3 Jahre Mittel Mittel
UserProfileAPI 7 80% < 1 Jahre Niedrig Hoch
LegacyETLJob 48 4% 8+ Jahre Sehr Hoch Kritisch

Wichtig hierbei: Der „Bereitschaftsgrad“ ist ein abgeleiteter, kein Rohwert. Das bedeutet, Sie müssen die Schwellenwerte anhand der Risikotoleranz Ihres Unternehmens festlegen.

Die eigentliche Funktion der Scorecard besteht darin, implizite Annahmen sichtbar zu machen. Betrachten Sie sie als Grundlage Ihrer Java-Modernisierungsstrategie: Bevor Sie die Arbeitsschritte planen können, benötigen Sie ein gemeinsames Verständnis des Systems. Teams ohne dieses Verständnis neigen dazu, die Migration intuitiv zu planen, wodurch die Kopplung systematisch vernachlässigt und die kosmetische Codequalität überbewertet wird.

Häufige Migrationshindernisse in Java-Modernisierungsprojekten für Unternehmen

Bei der Bewertung älterer Java-Anwendungen im Hinblick auf eine Modernisierung reicht eine statische Analyse allein nicht aus. Eine manuelle Überprüfung ist weiterhin unerlässlich und muss in den Migrationsplan einbezogen werden.

Im Folgenden werden einige der häufigsten Herausforderungen aufgeführt, mit denen Entwicklungsteams bei der Modernisierung solcher Legacy-Anwendungen konfrontiert sind:

Implizite Verträge in der XML-Konfiguration

Vor der Einführung von Spring Boot wurde kritisches Anwendungsverhalten häufig in XML-Dateien definiert:

  • applicationContext.xml
  • web.xml
  • hibernate.cfg.xml

Diese Dateien fließen nicht in Komplexitätskennzahlen ein. Ein Entwickler, der mit dem System nicht vertraut ist, erkennt möglicherweise nicht, dass ein bestimmter Bean-Scope oder eine Transaktionsgrenze wesentliche Geschäftslogik steuert. In solchen Fällen gehen diese impliziten Konfigurationen während der Migration leicht verloren.

Abhängigkeit von bestimmten Framework-Versionen

Java-EE-5/6-Anwendungen, die auf EJB 2.x oder Struts 1.x basieren, lassen sich häufig nur schwer schrittweise migrieren, da die Einschränkungen dieser Frameworks die Reihenfolge der Migration beeinflussen. In vielen Fällen müssen große Teile der Web- oder Persistenzschicht gemeinsam ersetzt werden. Dies sollte bereits in der Bewertungsphase erkannt werden.

Monolithische Datenbankarchitektur

In vielen Legacy-Systemen bildet die Datenbank den zentralen Integrationspunkt. Anwendungskomponenten greifen auf gemeinsame Tabellen zu und nutzen Trigger sowie Stored Procedures zur Umsetzung von Geschäftsregeln. Eine Codeanalyse allein macht diese Zusammenhänge nicht sichtbar. Um die tatsächlichen Datenflüsse zu verstehen und beurteilen zu können, ob eine Zerlegung des Systems sinnvoll ist, sind Schemaanalysen und die Nachverfolgung von Datenbankabfragen erforderlich.

Einsatz automatisierter Tools zur Bewertung technischer Schulden bei der Java-Modernisierung

Es gibt kein einzelnes Werkzeug, das alle Anforderungen abdeckt. Mit einer Kombination aus vier Tools lassen sich jedoch die meisten Kennzahlen ermitteln, die für eine zuverlässige Bewertung technischer Schulden in Enterprise-Java-Systemen erforderlich sind.

Für die statische Codeanalyse, die Bewertung der Wartbarkeit und die Erkennung von Codeduplizierung eignet sich SonarQube. Zur Analyse von Abhängigkeiten, Kopplungsmustern und den Architekturbeziehungen zwischen Modulen können ArchUnit, Sonargraph, Structure101, jQAssistant oder das JDK-eigene Tool jdeps verwendet werden. Für Mutation Testing zur Bewertung der Testqualität kommt PIT (Pitest) zum Einsatz. Darüber hinaus hilft OWASP Dependency-Check dabei, anfällige Abhängigkeiten sowie bekannte Sicherheitslücken zu identifizieren.

Für die Einrichtung dieser Tools in einem typischen Maven- oder Gradle-Projekt sollte etwa ein Arbeitstag eingeplant werden. Die Analyse einer einzelnen Anwendung dauert anschließend nur wenige Stunden. Mit den Ergebnissen lässt sich die Scorecard zur Bewertung der Migrationsbereitschaft erstellen und der Migrationsaufwand für einzelne Module fundiert abschätzen. Für Teams, die Legacy-Anwendungen modernisieren, bilden diese Ergebnisse außerdem eine wichtige Grundlage für den Business Case der Modernisierung.

Es gibt jedoch einen wichtigen Vorbehalt: In bestimmten Situationen erkennen diese Werkzeuge Probleme, die in der Praxis kaum relevant sind, während sie andere, wesentlich kritischere Aspekte übersehen. So markiert SonarQube beispielsweise eine Methode mit 200 Codezeilen, erkennt jedoch nicht, dass diese einen Abstimmungsalgorithmus implementiert, auf den das Finanzteam angewiesen ist. Solcher Kontext ergibt sich aus der Erfahrung der Entwickler und der sogenannten Code Archaeology. Dazu gehören Tätigkeiten wie die Analyse der Git-Blame-Historie, die Auswertung der Commit-Historie oder Gespräche mit den Entwicklern, die das System bislang betreut haben. Eine erfolgreiche Bewertung technischer Schulden basiert daher immer auf einer Kombination aus automatisierten Werkzeugen und menschlicher Expertise.

Übersetzung der Bewertung in eine Roadmap zur Softwaremodernisierung

Sobald Sie die Scorecard zur Hand haben, wird die Roadmap-Struktur nachvollziehbar. Der Ausgangspunkt sind immer die High-Ready-Module, da sie die Integrationsoberfläche verringern, das Frührisiko senken und dem Team die Möglichkeit geben, Migrationsmuster zu etablieren, bevor es auf schwierigere Probleme stößt.

„Kritische“ Module erfordern ein gesondertes Gespräch. Normalerweise verlagert sich die Diskussion in diesem Fall von der Frage, wie man sie migriert, hin zur Frage, ob es überhaupt sinnvoll ist, sie zu migrieren, oder ob ein gezielter Neuaufbau sinnvoller ist. Beispielsweise ist ein älterer ETL-Job mit 4 % Testabdeckung und einer durchschnittlichen zyklomatischen Komplexität von 48, der unter Java 6 läuft, kein Standard-Migrationskandidat. Es handelt sich um einen Neuaufbaukandidaten, der einen Migrationswrapper benötigt, um während des Übergangs am Leben zu bleiben.

Das folgende Beispiel zeigt, wie eine hochkomplexe Methode tatsächlich aussieht:

// Example of a high-complexity method that flags as a migration risk

public BigDecimal calculateBill(Order order, Customer customer) {

   if (order == null || customer == null) return BigDecimal.ZERO;

   if (customer.isInternal()) {

       if (order.hasDiscount()) {

           // nested branch logic continues...

       }

   }

   // 40+ more lines of nested conditionals

}

 

Diese Methode erreicht einen Wert von über 30 bei der zyklomatischen Komplexität. Das bedeutet nicht, dass sie fehlerhaft ist, birgt aber ein erhebliches Migrationsrisiko, da kein automatisiertes Tool aufzeigen kann, welche verschachtelten Zweige tatsächlich geschützt sind.

Eine weitere Faustregel besagt, dass die Kopplung die Reihenfolge bestimmt. Wenn das Modul „BillingEngine“ eine hohe afferente Kopplung aufweist, wird es erst migriert, nachdem die davon abhängigen Module entkoppelt wurden. Denn die Migration eines Moduls mit hoher afferenter Kopplung bedeutet, dass die Grundlage refaktoriert wird, während die darüber liegende Struktur noch vorhanden ist. So verzögern sich Migrationen oft über Monate.

Das Team von Chudovo folgt bei Java-Modernisierungsprojekten üblicherweise dem folgenden Workflow:

Workflow bei Java-Modernisierungsprojekten

Dieser Workflow ist natürlich nicht in Stein gemeißelt; er muss an die konkrete Situation und die verfügbaren Ressourcen der Organisation angepasst werden. Er bietet aber eine gute Orientierung für Teams, die eine solche Migration planen.

Fazit

Der erste Schritt für jeden erfolgreichen Migrationsplan ist die Beantwortung der Frage, wie sich technische Schulden in bestehenden Java-Anwendungen messen lassen. Teams, die diese formale Bewertung überspringen, tappen im Dunkeln. Der Code wirkt veraltet, das Team ist bereit für die Migration, und jemand schätzt den Aufwand anhand der Codezeilenanzahl oder intuitiv. Sechs Monate später steht die schwierigste Arbeit noch bevor, und im Budget wurde fälschlicherweise angenommen, die vermeintlich einfachen Aufgaben seien die schwierigsten.

Eine gezielte Modernisierung von Java-Anwendungen, die sich auf die Messung technischer Schulden vor der Migration konzentriert, verschafft dem Entwicklerteam ein gemeinsames Verständnis des tatsächlichen Systemzustands. Zudem werden Migrationshindernisse aufgedeckt, bevor sie zu geplanten Fehlern führen. Dies ist der Kern der Java-Schuldenanalyse im Migrationskontext. Sie liefert eine Bewertungsmatrix, die fundierte Entscheidungen zur Reihenfolge der Maßnahmen ermöglicht, anstatt optimistische Schätzungen zu verwenden. Eine solche Bewertungsmatrix ist nicht nur ein weiterer Bericht; sie ist eine Landkarte, die das Entwicklungsteam tatsächlich auf seinem Weg nutzt.

Diese Landkarte (ob intern erstellt oder mit Unterstützung von Modernisierungsdienstleistern) ist zwei bis vier Wochen wert, bevor man sich auf einen Zeitplan für die Modernisierung bestehender Java-Anwendungen festlegt.