Bei Ramp hatte sich ein Monorepo mit Machine-Learning-Workflows von einem praktischen gemeinsamen Raum zu dem entwickelt, was das Team „ein bisschen einen Sumpf“ nannte: eine requirements.txt mit nicht gepinnten transitiven Abhängigkeiten, die zwischen Maschinen drifteten, inkonsistente Formatierung, keine automatisierten Tests und Typannotationen, die entweder fehlten oder subtil falsch waren. Die Ingenieure wurden vorsichtig, den Code anzufassen, aus Angst, etwas kaputtzumachen, und die Geschwindigkeit, das ursprüngliche Ziel, war auf ein Kriechen verlangsamt.
Statt das Repository neu zu schreiben, führte das Team vier Werkzeuge direkt vor Ort ein: poetry für gesperrte, reproduzierbare Abhängigkeiten; ruff für Formatierung und Linting; pytest zum Testen von hochwirksamen gemeinsamen Utilities, statt vollständige Abdeckung anzustreben; und mypy für schrittweise Typüberprüfung. Der Trick, der die Einführung in einer laufenden Codebasis ermöglichte, war nicht, zuerst alles zu beheben. Sie ließen einen Auto-Formatter einmal über das gesamte Repo in einem einzigen Commit laufen und fügten dann den Hash dieses Commits zu .git-blame-ignore-revs hinzu, damit er die Blame-Historie nicht verunreinigte.
Für Linting und Typisierung behoben sie nicht Hunderte bereits bestehender Fehler, bevor sie CI aktivierten, sondern generierten maschinell „# noqa“- und „# type: ignore“-Kommentare für jeden bestehenden Verstoß und aktivierten dann harte Durchsetzung für alles Neue. Die Regel „RUF100“ von Ruff und „warn_unused_ignores“ von mypy markierten jeden Unterdrückungskommentar, der veraltet war, sobald der zugrunde liegende Code tatsächlich behoben wurde, sodass die Ignorierliste nur schrumpfen und niemals still verrotten konnte.
Das Ergebnis war eine Codebasis, die per Anordnung nie „sauber“ war, aber ab dem ersten Tag für die Zukunft durchgesetzt wurde, wobei die Schulden schrumpften, als Entwickler auf natürliche Weise alte Dateien anfassten.
Alte Verstöße bestandsgeschützt, nur neuen Code durchgesetzt
Eine vollständige Neuschreibung oder eine Big-Bang-Bereinigung würde Wochen dauern, bevor irgendeine Durchsetzung beginnen könnte, also setzte das Team für die Zukunft durch statt rückwirkend.
Das maschinelle Generieren der anfänglichen Unterdrückungskommentare verwandelte eine unbegrenzte manuelle Bereinigung in einen einmaligen, automatisierbaren Schritt.
Das Markieren ungenutzter Unterdrückungskommentare bedeutete, dass Schulden nur abnehmen konnten, da das Beheben eines alten Verstoßes einen nun sinnlosen Ignorierkommentar aufdeckte, den die CI abfangen würde.
Das Zusammenfassen der Neuformatierung in einen Commit und das Ausschließen aus git blame bewahrte die Möglichkeit zu sehen, wer eine Zeile zuletzt aus einem echten Grund angefasst hatte.
Man braucht keine saubere Codebasis, bevor man Standards darauf durchsetzt. Man schweigt jeden bestehenden Verstoß einmal still, setzt streng nur für neuen Code durch, und der Rückstand schrumpft, da Leute ohnehin alte Dateien anfassen.
Das Team berichtete, dass die Codebasis einen Zustand erreichte, in dem die Qualität für die Zukunft aufrechterhalten wurde: poetrys Lock-Datei sorgte für konsistente Umgebungen auf Entwicklermaschinen und in der CI, ruff fing echte Fehler wie doppelte Dictionary-Schlüssel vor dem Merge ab, und mypy fing Signaturänderungs-Regressionen an Aufrufstellen ab, die Unit-Tests nicht abdeckten. Der Beitrag rahmt das Ziel als wiederhergestelltes Vertrauen, weitreichende Änderungen vorzunehmen, nicht als das Tooling selbst, und merkt an, dass das Team noch dabei war zu prüfen, mehr dieser Last von Mitwirkenden auf das Tooling zu verlagern.
DEN BELEGEN FOLGEN
Die Quellen
- Increasing velocity by modernizing a Python codebase Ramp Engineering · builders.ramp.com ·