Einleitung: Warum die Refaktorisierung von Legacy-Code auf TypeScript notwendig ist
Die Arbeit mit vererbtem (Legacy-)Code ist eine der häufigsten und anspruchsvollsten Aufgaben für Entwickler. Oft ist solcher Code in reinem JavaScript ohne strenge Typisierung geschrieben, enthält verworrene Abhängigkeiten und es fehlt eine modulare Struktur. Der Wechsel zu TypeScript fügt nicht nur Typen hinzu – es ist eine grundlegende Verbesserung der Codequalität, die die Anzahl der Fehler reduziert, die Refaktorisierung vereinfacht und das Onboarding neuer Entwickler beschleunigt.
In diesem Artikel werden wir den schrittweisen Prozess der Refaktorisierung von Legacy-Code auf TypeScript durchgehen. Sie erfahren, wie Sie Typen sicher einführen, monolithische Funktionen aufteilen und Änderungen testen.
Schritt 1. Analyse und Vorbereitung des Legacy-Codes
Bevor Sie Code schreiben, müssen Sie die aktuelle Architektur verstehen. Ohne dies wird die Refaktorisierung zum Chaos.
Erstellen Sie eine Abhängigkeitskarte
Beginnen Sie mit der Analyse von Dateien und Modulen. Ermitteln Sie:
- Welche Funktionen "rein" (pure) sind und welche Seiteneffekte haben.
- Wo sich globale Variablen und Zustände befinden.
- Welche Codeabschnitte dupliziert werden.
Schreiben Sie Tests für kritische Bereiche
Refaktorisierung ohne Tests ist russisches Roulette. Schreiben Sie zumindest Modultests für die Schlüsselfunktionen. Beispiel eines Tests in TypeScript:
// Ursprünglicher Legacy-Code (JavaScript)function calculatePrice(items) { let total = 0; for (let i = 0; i < items.length; i++) { total += items[i].price * items[i].quantity; } return total;}
// Test in TypeScript (Jest)describe('calculatePrice', () => { it('should calculate total price correctly', () => { const items = [ { price: 10, quantity: 2 }, { price: 5, quantity: 3 } ]; expect(calculatePrice(items)).toBe(35); });});Wenn die Tests bestanden werden, kann die Refaktorisierung beginnen.
Schritt 2. Schrittweise Einführung der strengen Typisierung
Versuchen Sie nicht, alles auf einmal mit Typen zu versehen. Verwenden Sie die "Bottom-up"-Strategie: Beginnen Sie mit den einfachsten und isoliertesten Funktionen.
Typisierung einfacher Funktionen
Fügen Sie Typen für Parameter und Rückgabewerte hinzu. Beispiel:
// Vorher (JavaScript)function greet(name) { return "Hello, " + name;}
// Nachher (TypeScript)function greet(name: string): string { return `Hello, ${name}`;}Arbeit mit Objekten und Interfaces
Erstellen Sie für komplexe Datenstrukturen Interfaces. Das macht den Code selbstdokumentierend:
// Interface für ein Bestellelement definiereninterface OrderItem { id: number; name: string; price: number; quantity: number;}
// Funktion mit typisiertem Parameterfunction getTotalPrice(items: OrderItem[]): number { return items.reduce((sum, item) => sum + item.price * item.quantity, 0);}Verwenden Sie `any` nur als temporäre Lösung
Der Typ any ist der Feind der strengen Typisierung. Wenn Sie den genauen Typ nicht kennen, verwenden Sie unknown und prüfen Sie ihn zur Laufzeit:
function safeParse(data: unknown): string { if (typeof data === 'string') { return data.trim(); } return '';}Schritt 3. Aufteilung monolithischer Funktionen und Module
Legacy-Code enthält oft "göttliche Funktionen" (god functions), die alles auf einmal erledigen. Teilen Sie sie in kleine, wiederverwendbare Module auf.
Auslagerung reiner Logik
Beispiel: Eine Funktion, die eine E-Mail validiert und eine Anfrage sendet. Teilen wir sie auf:
// Vorher (monolithisch)function processUser(email) { if (!email || !email.includes('@')) { throw new Error('Invalid email'); } // ... Anfrage senden console.log('Sending request...');}
// Nachher (Trennung der Verantwortlichkeiten)function validateEmail(email: string): boolean { return email
```