Einleitung: Warum Refactoring von Legacy-Code notwendig ist
Jeder Entwickler stößt früher oder später auf Legacy-Code – veraltet, schlecht dokumentiert und oft unlesbar. In der Welt von JavaScript und Node.js ist dies besonders relevant: Projekte wachsen schnell, Technologien ändern sich und alte Codeabschnitte werden zu einem „Sumpf“, der die Entwicklung verlangsamt. Refactoring von Legacy-Code ist nicht nur eine Laune, sondern eine Notwendigkeit, um die Leistung, Sicherheit und Skalierbarkeit der Anwendung zu erhalten.
In diesem Artikel werden wir untersuchen, wie man das Refactoring von Legacy-Code in JavaScript (Node.js) sicher und effizient angeht. Sie erfahren die wichtigsten Schritte: Analyse, Aufteilung in Module, Ersetzen veralteter Konstrukte und Testen. Alle Beispiele sind in modernem JavaScript (ES6+) geschrieben und auf die Node.js-Umgebung ausgerichtet.
Schritt 1: Analyse und Verständnis von Legacy-Code
Bevor Sie Code ändern, müssen Sie ihn verstehen. Legacy-Code ist oft im „Spaghetti“-Stil geschrieben: Funktionen mit Hunderten von Zeilen, globale Variablen und vermischte Logik. Beginnen Sie mit einem Audit.
1.1. Erstellen Sie eine Abhängigkeitskarte
Verwenden Sie Tools wie madge oder dependency-cruiser, um die Verbindungen zwischen Modulen zu visualisieren. Dies hilft, zirkuläre Abhängigkeiten und „göttliche Objekte“ zu identifizieren.
// Beispiel: Installation von madgenpm install -g madgemadge --image graph.svg src/1.2. Identifizieren Sie „Risikozonen“
Suchen Sie nach Codeabschnitten, die am häufigsten geändert werden oder Fehler verursachen. Nutzen Sie die Commit-Historie in Git:
git log --oneline --follow src/legacyModule.jsAchten Sie auch auf:
- Funktionen länger als 50 Zeilen
- Globale Variablen (z.B.
varohnelet/const) - Callback-Hölle (verschachtelte asynchrone Aufrufe)
1.3. Schreiben Sie Tests für das bestehende Verhalten
Ohne Tests ist Refactoring eine Zeitbombe. Verwenden Sie jest oder mocha, um Schlüsselfunktionen abzudecken. Die Tests sollten das aktuelle Verhalten überprüfen, nicht das „ideale“.
// Beispiel eines Tests für eine Legacy-Funktionconst sum = require('./legacyModule').sum;test('sum addiert zwei Zahlen', () => { expect(sum(1, 2)).toBe(3);});Schritt 2: Schrittweise Verbesserung der Struktur
Versuchen Sie nicht, alles auf einmal umzuschreiben. Verwenden Sie die „Strangler-Fig“-Strategie – ersetzen Sie Codeteile schrittweise unter Beibehaltung der Abwärtskompatibilität.
2.1. Teilen Sie den Monolithen in Module auf
Fassen Sie logisch zusammenhängende Blöcke in separaten Dateien zusammen. Lagern Sie z.B. die Datenbankarbeit in db.js und die Anfrageverarbeitung in handlers.js aus.
// Vorher: eine Datei mit 500 Zeilenfunction handleRequest(req, res) { // 100 Zeilen Logik // 50 Zeilen Datenbankarbeit // 30 Zeilen Antwortformatierung}
// Nachher: Aufteilung in Module// db.jsexports.getUser = (id) => { /* ... */ };
// handlers.jsconst db = require('./db');exports.handleRequest = (req, res) => { const user = db.getUser(req.params.id); // Antwortformatierung};2.2. Ersetzen Sie var durch let/const
Dies ist ein einfacher, aber wichtiger Schritt. var hat einen funktionalen Gültigkeitsbereich, was oft zu Fehlern führt. Verwenden Sie let für Variablen und const für Konstanten.
// Warvar x = 10;if (true) { var x = 20; // überschreibt die äußere Variable}
// Wurdelet x = 10;if (true) { let x = 20; // lokale Variable}Schritt 3: Modernisierung von asynchronem Code
Legacy-Code in Node.js leidet oft unter der „Callback-Hölle“. Ersetzen Sie verschachtelte Callbacks durch Promises oder async/await.
3.1. Umwandlung von Callbacks in Promises
Verwenden Sie util.promisify für Funktionen, die dem Node.js-Stil folgen (erstes Argument ist der Fehler).
const fs = require('fs');const { promisi