Legacy-Modernisierung
Reverse Engineering Legacy-Systeme: Case Studies aus der Praxis
„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.
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.
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';
*_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
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)
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)
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
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
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."
Legacy-System verstehen?
Ich analysiere Ihr System systematisch und liefere eine Architektur-Landkarte mit Risiken, Quick Wins und strategischen Empfehlungen.
Analyse anfragen