Reverse Engineering Legacy-Systeme Legacy-Modernisierung

Reverse Engineering Legacy-Systeme: Case Studies aus der Praxis

Carola Schulte
Carola Schulte 1. Mai 2025 20 min Lesezeit

„Der einzige Entwickler, der das System kannte, hat vor zwei Jahren gekündigt. Die Dokumentation ist von 2016. Können Sie sich das mal anschauen?" – So beginnen die meisten meiner Rescue-Projekte. Reverse Engineering ist keine Magie. Es ist systematische Detektivarbeit mit den richtigen Werkzeugen und Fragen.

Kurz gesagt: Reverse Engineering von Legacy-Systemen bedeutet, aus Code, Datenbank, Logs und Laufzeitverhalten ein Verständnis der Architektur zu rekonstruieren – ohne sich auf Dokumentation verlassen zu können.

Dieser Artikel zeigt drei anonymisierte Case Studies aus meiner Praxis – mit konkreten Methoden, Tools und Learnings.

Warum Reverse Engineering?

Sie brauchen Reverse Engineering, wenn:

  • Dokumentation fehlt, veraltet oder falsch ist
  • Die ursprünglichen Entwickler nicht mehr verfügbar sind
  • Sie ein System übernehmen (M&A, Vendor-Wechsel)
  • Ein Audit oder eine Migration ansteht
  • Unerklärliche Bugs oder Performance-Probleme auftreten

Das Ziel ist nicht, jede Codezeile zu verstehen. Das Ziel ist, genug zu verstehen, um fundierte Entscheidungen zu treffen.

Reality-Check: Reverse Engineering ist teuer. Planen Sie 2-4 Wochen für ein mittelgroßes System, bevor Sie belastbare Aussagen treffen können. Wer Ihnen nach zwei Tagen eine vollständige Analyse verspricht, hat entweder ein sehr kleines System oder unterschätzt die Komplexität.

Die Methodik

Mein Vorgehen folgt immer dem gleichen Muster – von außen nach innen:

Phase 1: Black-Box-Analyse

Bevor ich eine Zeile Code lese, beobachte ich das System von außen:

  • Traffic-Analyse: Welche Endpunkte werden aufgerufen? Mit welchen Parametern?
  • Datenbank-Queries: Welche Tabellen werden wie oft gelesen/geschrieben?
  • Logs: Was loggt das System? Welche Fehler treten auf?
  • Netzwerk: Mit welchen externen Systemen kommuniziert es?
# Traffic mitschneiden (wenn möglich)
tcpdump -i any -w capture.pcap port 443

# Datenbank-Queries loggen (PostgreSQL)
ALTER SYSTEM SET log_statement = 'all';

# Slow Queries finden
SELECT * FROM pg_stat_statements ORDER BY total_time DESC LIMIT 20;

Phase 2: Statische Code-Analyse

Jetzt geht es in den Code – aber strukturiert:

  • Entry Points: Wo kommt Traffic rein? (Controller, Handler, Main)
  • Dependency Graph: Welche Module hängen voneinander ab?
  • Datenmodell: Welche Entitäten gibt es? Wie hängen sie zusammen?
  • Konfiguration: Welche Umgebungsvariablen, Feature Flags, Secrets?
# Dependency-Graph visualisieren (PHP)
deptrac analyze --formatter=graphviz

# Alle Routen extrahieren (Laravel)
php artisan route:list --json

# Code-Metriken (allgemein)
cloc --by-file --csv src/

# Dead Code finden
phpstan analyse --level=max src/

Phase 3: Datenbank-Archäologie

Die Datenbank erzählt oft mehr als der Code:

# Schema extrahieren
pg_dump --schema-only -f schema.sql

# Tabellen-Größen und Alter
SELECT
  relname as table,
  n_live_tup as rows,
  pg_size_pretty(pg_total_relation_size(relid)) as size
FROM pg_stat_user_tables
ORDER BY n_live_tup DESC;

# Foreign Keys finden (oft fehlen sie!)
SELECT
  tc.table_name, kcu.column_name,
  ccu.table_name AS foreign_table
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
  ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
  ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY';
Praxis-Tipp: Fehlende Foreign Keys sind ein Warnsignal. Sie deuten auf Konsistenz-Probleme hin, die im Code gelöst werden (oder nicht). Suchen Sie nach Tabellen mit *_id-Spalten ohne FK-Constraint.

Phase 4: Laufzeit-Analyse

Manche Dinge sieht man nur zur Laufzeit:

  • Profiling: Wo verbringt die Anwendung ihre Zeit?
  • Memory: Welche Objekte werden erzeugt?
  • Call Graphs: Welche Funktionen rufen welche auf?
# PHP Profiling mit XHProf
xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY);
// ... Code ausführen ...
$data = xhprof_disable();

# Java Flight Recorder
java -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=app.jfr

# Python cProfile
python -m cProfile -o output.prof app.py

Case Study 1: Der PHP-Monolith ohne Tests

Kontext

System: E-Commerce-Plattform, 12 Jahre alt, 400.000 LOC PHP
Problem: Performance-Einbrüche, unerklärliche Bugs nach jedem Deployment
Herausforderung: Keine Tests, keine Dokumentation, Original-Team weg

Vorgehen

Tag 1-2: Black-Box-Analyse

Ich habe eine Woche Produktions-Logs analysiert. Ergebnis: 80% der Requests gingen an 12 Endpunkte. Der Rest war Long Tail. Das gab mir den Fokus.

Tag 3-5: Entry Points tracen

Für jeden der 12 Hot-Path-Endpunkte habe ich den Code-Flow dokumentiert. Dabei fiel auf: Ein einziger Controller hatte 4.000 Zeilen mit 47 privaten Methoden. Das war der Kern des Problems.

Tag 6-8: Datenbank-Analyse

Die orders-Tabelle hatte 15 Millionen Rows und keinen Index auf customer_id. Jede Kundenansicht triggerte einen Full Table Scan.

Findings

  • 3 kritische Performance-Bottlenecks (fehlende Indizes)
  • 17 SQL-Injection-Vulnerabilities (String-Konkatenation)
  • Ein God Object, das 60% der Business-Logik enthielt
  • Zirkuläre Abhängigkeiten zwischen 4 Kernmodulen
Ergebnis: Mit drei Indizes und einer Query-Optimierung sank die Antwortzeit von 8s auf 200ms. Die Security-Findings wurden priorisiert ins Backlog aufgenommen. Der God Object-Refactor wurde als 6-Monats-Projekt geplant.

Case Study 2: Die Java-Enterprise-Blackbox

Kontext

System: Versicherungs-Backend, Java EE, 8 Jahre alt
Problem: Übernahme nach Vendor-Insolvenz, kein Quellcode-Zugang initial
Herausforderung: Nur JARs, Datenbank-Zugang und Laufzeitumgebung

Vorgehen

Schritt 1: Decompiling

# JAR entpacken und decompilieren
jar -xf application.jar
fernflower -dgs=true classes/ src/

# Oder mit JD-GUI für schnellen Überblick
java -jar jd-gui.jar application.jar

Schritt 2: Datenbank als Wahrheit

Die Datenbank war die zuverlässigste Quelle. 180 Tabellen, aber nur 30 davon hatten mehr als 1000 Rows. Der Rest war Konfiguration oder historischer Ballast.

Schritt 3: Integration Points mappen

Über Netzwerk-Analyse und Konfigurations-Dateien identifiziert: 7 externe Systeme (SAP, zwei Legacy-Mainframes, vier REST-APIs).

Findings

  • Das System war eigentlich drei Systeme in einem Deployment
  • Ein kritischer Batch-Job lief täglich um 3 Uhr – undokumentiert
  • Die SAP-Integration nutzte ein deprecated RFC-Protokoll
  • 40% des Codes war tot (nie aufgerufen)
Learning: Bei übernommenen Systemen ist die Datenbank oft die einzige Wahrheit. Schema, Daten-Verteilung und Zugriffspatterns erzählen die Geschichte, die im Code verloren ging.

Case Study 3: Das Node.js-Microservice-Chaos

Kontext

System: FinTech-Plattform, 23 Node.js-Services, 3 Jahre alt
Problem: Niemand überblickt die Abhängigkeiten, Änderungen brechen random andere Services
Herausforderung: Keine zentrale Dokumentation, jedes Team hat „sein" Service dokumentiert

Vorgehen

Schritt 1: Service-Katalog erstellen

# Alle Services und ihre package.json sammeln
for dir in services/*/; do
  echo "=== $dir ==="
  cat "$dir/package.json" | jq '{name, dependencies}'
done > service-catalog.json

Schritt 2: Runtime-Kommunikation tracen

Über Service-Mesh-Logs (sie hatten Istio, nutzten es aber nicht für Observability) konnte ich die tatsächlichen Aufrufbeziehungen extrahieren.

# Aus Istio-Logs Service-Kommunikation extrahieren
kubectl logs -n istio-system -l app=istio-proxy | \
  grep -E "upstream_cluster.*outbound" | \
  awk '{print $NF}' | sort | uniq -c | sort -rn

Schritt 3: Shared Dependencies identifizieren

# Welche npm-Packages werden von mehreren Services genutzt?
find . -name "package-lock.json" -exec jq -r '.dependencies | keys[]' {} \; | \
  sort | uniq -c | sort -rn | head -20

Findings

  • 5 Services waren eigentlich ein verteilter Monolith (synchrone Ketten)
  • 3 Services wurden von niemandem aufgerufen (Zombies)
  • Eine interne npm-Library wurde von 18 Services genutzt – ohne Versionierung
  • Die „Microservices" teilten sich eine Datenbank (4 Services auf dieselben Tabellen)
Die harte Wahrheit: Das war kein Microservice-System. Es war ein verteilter Monolith mit allen Nachteilen beider Welten. Die Empfehlung: Erst konsolidieren, dann sauber schneiden – nicht andersherum.

Werkzeugkasten

Kategorie Tools Zweck
Code-Analyse SonarQube, PHPStan, ESLint Statische Analyse, Code Smells
Visualisierung Deptrac, Madge, Structure101 Dependency Graphs
Decompiling JD-GUI, Fernflower, dnSpy Java/.NET ohne Quellcode
Datenbank SchemaSpy, DBeaver, pgAdmin Schema-Analyse, ER-Diagramme
Netzwerk Wireshark, tcpdump, mitmproxy Traffic-Analyse
Profiling XHProf, Blackfire, async-profiler Performance-Analyse
Tracing Jaeger, Zipkin, OpenTelemetry Distributed Tracing

Was Sie vermeiden sollten

1. Zu früh in Details versinken

Der erste Instinkt ist, jede Funktion zu verstehen. Widerstehen Sie. Verstehen Sie erst die Struktur, dann die Hot Paths, dann Details nach Bedarf.

2. Der Dokumentation blind vertrauen

Dokumentation lügt. Nicht absichtlich, aber sie veraltet. Verifizieren Sie jede Aussage gegen Code oder Laufzeitverhalten.

3. Alleine arbeiten

Auch wenn das Original-Team weg ist – irgendjemand kennt das System. Support-Mitarbeiter wissen, welche Workarounds Kunden nutzen. Operations weiß, welche Cronjobs heimlich laufen. Fragen Sie.

4. Keine Hypothesen dokumentieren

Schreiben Sie auf, was Sie vermuten – und wie Sie es verifizieren wollen. Sonst laufen Sie im Kreis.

Was Sie liefern sollten

Am Ende eines Reverse-Engineering-Projekts erwarten Stakeholder konkrete Artefakte:

  • Architektur-Diagramm: Komponenten, Abhängigkeiten, externe Systeme
  • Datenmodell: ER-Diagramm der wichtigsten Entitäten
  • Hot Paths: Die 5-10 kritischsten Flows dokumentiert
  • Risk Assessment: Security, Performance, Wartbarkeit
  • Empfehlungen: Priorisierte Liste von Quick Wins und strategischen Maßnahmen
Format-Tipp: Architektur-Diagramme gehören in den Code (C4 mit PlantUML, Mermaid). Nicht in PowerPoint, wo sie verrotten.

Fazit

Reverse Engineering ist keine Kunst – es ist Handwerk. Mit den richtigen Methoden und Tools können Sie jedes System verstehen. Die Frage ist nur: Wie viel Zeit haben Sie, und wie tief müssen Sie gehen?

Die wichtigsten Prinzipien:

  • Von außen nach innen: Black Box vor White Box
  • Daten vor Code: Die Datenbank lügt seltener
  • Fokus auf Hot Paths: 80/20-Regel gilt auch hier
  • Dokumentieren während Sie arbeiten: Nicht erst am Ende
  • Hypothesen explizit machen: Vermutungen aufschreiben und testen
Nächster Schritt: Wenn Sie vor einem Legacy-System stehen, das niemand mehr versteht: Fangen Sie mit den Logs an. Eine Woche Produktions-Logs erzählt Ihnen mehr als jede veraltete Dokumentation. In meinen Rescue-Projekten ist das der erste Schritt – und oft der aufschlussreichste. Das Ergebnis ist eine Landkarte des Systems, die fundierte Entscheidungen ermöglicht.

Häufige Fragen

Wie lange dauert Reverse Engineering?

Für ein erstes belastbares Bild: 2-4 Wochen bei einem mittelgroßen System. Für vollständige Dokumentation: Monate. Die Kunst ist, zu wissen, wann „gut genug" erreicht ist.

Brauche ich Zugang zum Quellcode?

Hilft enorm, ist aber nicht zwingend. Mit Datenbank-Zugang, Logs und Netzwerk-Analyse kommen Sie weit. Bei Java/.NET können Decompiler helfen.

Was mache ich ohne Testumgebung?

Vorsichtig sein. Read-only-Analyse auf Produktion ist möglich (Logs, DB-Queries mit EXPLAIN). Aktives Testen ohne Staging ist riskant – dokumentieren Sie das Risiko für Stakeholder.

Wie überzeuge ich Management von der Notwendigkeit?

Risiko-Sprache: „Wir wissen nicht, was wir nicht wissen. Ohne Analyse treffen wir Entscheidungen blind." Kosten-Sprache: „2 Wochen Analyse jetzt oder 6 Monate falsche Richtung später."


Carola Schulte

Carola Schulte

Software-Architektin mit 25+ Jahren Erfahrung in Rescue-Projekten, Legacy-Modernisierung und Technical Due Diligence.

Beratung anfragen

Legacy-System verstehen?

Ich analysiere Ihr System systematisch und liefere eine Architektur-Landkarte mit Risiken, Quick Wins und strategischen Empfehlungen.

Analyse anfragen