Refactoring ohne Risiken: So extrahieren Sie Module unter Beibehaltung des Verhaltens
Beim Verschieben eines Moduls aus einem Monolithen in einen separaten Service ist das Hauptziel, das Verhalten exakt so zu erhalten, inklusive bekannter Bugs und undokumentierter Eigenarten. Versuche, den Code in dieser Phase zu »aufräumen«, führen oft zu Regressionen. Wir zeigen Ihnen, wie Sie die Fallstricke mit einer Strategie minimaler Änderungen umgehen.
Warum der »langweilige« Ansatz Sie vor Regressionen schützt
Produktionssysteme haben einen impliziten Vertrag, der durch jahrelangen Betrieb geformt wurde: Client-Integrationen, Fehlerbehandlung, Timing-Verzögerungen und Anpassungen an externe APIs. Dieser Vertrag ist nicht dokumentiert – er steckt in Logs, Commits und der Erfahrung des Teams. Refactoring darf das Verhalten nicht verändern, auch wenn es Bugs enthält. Beispiel: Wenn der alte Code einen leeren String statt null zurückgibt, bricht eine Änderung Clients, die sich bereits an diese Logik angepasst haben. Grundprinzip: Extraction ändert die Form, erhält aber die Semantik. Jede Abweichung vom ursprünglichen Verhalten macht aus dem Refactoring einen Release mit unvorhersehbaren Folgen.
Eine Art von Änderung nach der anderen: Grenzen vor interner Struktur
Zerlegen Sie Aufgaben in Etappen:
- Etappe A: Extrahieren Sie das Modul unter Beibehaltung des Vertrags (HTTP, Queue, Bus) und führen Sie eine Feature Flag ein.
- Etappe B: Aktualisieren Sie den Stack, Libraries oder den Code-Stil.
- Etappe C: Beheben Sie Bugs.
- Etappe D: Optimieren Sie die Performance.
Das Mischen von Etappen in einer einzigen PR führt garantiert zu unlesbaren Diffs, Rollback-Ärger und Streit darüber, »ist das ein Bug oder eine neue Feature?«. In der ersten Etappe wrappen Sie den alten Code: neue Service-Grenze, alte Logik darin. Beispiel beim Extrahieren eines Routing-Moduls: Behalten Sie die originale route()-Funktion bei, wrappen Sie sie aber in einen HTTP-Endpoint. So bleibt das Verhalten identisch, auch wenn der Code »hässlich« wirkt.
Feature Flag: Ihr Not-Aus-Schalter
Um alten und neuen Code parallel im Produktionsbetrieb laufen zu lassen, brauchen Sie einen Schalter, der über Config oder Env-Variablen gesteuert wird. Das ermöglicht:
- Sofortigen Rollback ohne Neudeploy bei einem Incident.
- Stufenweisen Rollout auf einen Teil des Traffics (Canary Release).
- Langfristige Beibehaltung der alten Implementierung zur Verhaltensvergleich.
Entscheidend: Der Schalter muss ohne Neustart des Services funktionieren. Sonst wird aus einem Incident um Mitternacht ein Notfallanruf statt eines simplen Config-Tweaks. Implementieren Sie die Flag auf Business-Logic-Ebene, nicht in der Infrastruktur – um komplizierte Abhängigkeiten von Orchestratoren zu vermeiden.
Verhalten kopieren, nicht umschreiben
Richtlinie für sichere Extraction: Dateien »as is« kopieren, nur das ändern, was für den Build nötig ist. Praxisbeispiel – Routing von einem Actor zu einer reinen Funktion verschieben:
object UpdateRouting {
def route(update: Update): Either[RoutingError, RouteTarget] =
if (update.message.exists(_.chat.isGroupOrSupergroup)) Right(GroupFlow)
else if (update.callbackQuery.isDefined) Right(CallbackFlow)
else Right(PrivateFlow)
}
Beim Mapping von Modellen auch feine Details erhalten. Wenn der alte Code ein fehlendes channelId zu einem leeren String konvertiert, tun Sie dasselbe:
def toBusMessage(in: PlatformInMessage): BusIncoming =
BusIncoming(
correlationId = newCorrelationId(),
channelId = in.channelId.getOrElse("")
)
Jede semantische Änderung (z. B. leeren String durch null ersetzen) bricht den Vertrag mit anderen Services. Ziel: Bit-für-Bit-identisches Verhalten, auch wenn der Code veraltet wirkt.
Was Sie bei der Extraction unbedingt vermeiden sollten
Solange die Feature Flag aktiv ist, halten Sie sich fern von:
- Neuen Libraries. Jede bringt transitive Dependencies und Attack Surface mit. Stack-Updates sind eine separate Aufgabe.
- Code-Stil-Änderungen. Das Mischen von altem Domain-Code mit neuen Wrappers ist der temporäre Preis für Sicherheit.
- Behebung entdeckter Bugs. Loggen Sie ein Ticket und kümmern Sie sich danach. Sonst wird jeder Incident dem Refactoring angelastet.
- »Solange-wir-in-der-Datei-sind«-Optimierungen. Profiling und Speedups sind ein separater Zyklus.
Diese Einschränkungen mögen übertrieben wirken, aber sechs Monate später im git blame sehen Sie eine klare Historie: »Modul extrahiert, Bus angebunden«, nicht »alles umgeschrieben«.
Tests: Den Vertrag absichern, nicht KPIs
Unit-Tests für reine Funktionen (Routing, Mapping) sollten das Verhalten vor der Extraction erfassen. Ein grüner Test nach der Extraction garantiert unveränderte Logik. Für Integration-Szenarien (Netzwerk, Queues, DB) nutzen Sie E2E-Tests, behalten aber ihre Grenzen im Blick:
- Mocks externer Systeme können fehlerhafte Annahmen aus dem Code verewigen.
- Echte APIs haben oft subtile Eigenarten (z. B. ein
/api/-Prefix), die in Tests fehlen.
Immer einen manuellen Lauf in einer produktionsnahen Staging-Umgebung einplanen. Automation ersetzt keine Checks unter fast-realem Betrieb.
Code-Wachstum ist kein Bug, sondern eine Prozessetappe
Nach der ersten Etappe schwillt das Code-Volumen oft an: Adapter, Feature Flags, Monolith-Duplikate. Das ist normal – Sie zahlen für sichere Rollbacks und stufenweisen Rollout. Aufräumen und Schrumpfen kommt in Etappe zwei, sobald die Grenze stabil ist, alter Code weg und Tests die Korrektheit bestätigen. Nicht vorab optimieren – erst sicherstellen, dass es funktioniert.
Wichtige Erkenntnisse
- Ziel der Extraction – Verhalten »as is« übertragen, keine umfassende Verbesserung.
- Feature Flag ist Pflicht für sicheren Rollback ohne Deploy.
- Tests sichern den Vertrag vor der Extraction, nicht danach.
- Parallele Änderungen verboten zu Stack, Bugfixes und Optimierungen.
- Code darf wachsen – das gehört zur Strategie, kein Fehler.
Refactoring von Legacy-Code ist kein Ort für Experimente. Je langweiliger der Prozess, desto unwahrscheinlicher der Anruf um 3 Uhr morgens wegen Produktionsausfall. Folgen Sie diesen Regeln, um eine riskante Extraction in einen vorhersehbaren Vorgang zu verwandeln.
— Editorial Team
Noch keine Kommentare.