Code Migration

Eine bestehende Codebasis mittels AI nach Rust migrieren: Geht das?

Eine bestehende Codebasis liegt in C#, C++ oder einer anderen Sprache vor, und es gibt gute Gründe, sie nach Rust zu migrieren. Bisher galt eine solche Migration als zu aufwändig, um sie nebenher zu stemmen. Wie weit bringt uns AI dabei heute? Schauen wir es uns an.

Warum sollten wir das tun?

Garbage In – Garbage Out: Zwei Fälle

Wie reibungslos die Migration läuft, entscheidet sich an der Vorlage: gute Vorlage rein, reibungslose Migration. Bei schlechter Vorlage ist ein gutes Ergebnis zwar weiterhin möglich, aber nur mit mehr Aufwand.

Wir können die bestehende Struktur übernehmen

Die Architektur ist zweckmässig und genügt damit den funktionalen Anforderungen in guter Qualität. Es gibt eine sinnvolle Testabdeckung auf allen Ebenen. So können wir direkt mit dem Migrieren beginnen.

Die bestehende Software ist schwierig zu verstehen

Es ist unklar in welchen Teilen der Codebasis welche Funktionalität ist. Mangels Tests ist nicht ganz klar, welche Funktionen die Software überhaupt erfüllt. Qualitätsanforderungen sind weder formuliert noch umgesetzt.

In diesem Fall sind wir tief im Thema Softwaremodernisierung. Bei einer Softwaremodernisierung müssen wir uns zuerst über unsere Geschäftsziele, die funktionalen Anforderungen und die Qualitätsziele Gedanken machen. Daraus können wir eine Zielarchitektur herleiten. (Weiter Informationen zu Softwaremodernisierung in unserem Blog)

bbv Techblog Rust vs. C++

Rust vs modern C++

Rust trifft auf modernes C++: Ein fairer Vergleich einiger Sprachfeatures aus technologischer Sicht. Jetzt lesen auf Tech Insights – der bbv Blog von Techies für Techies.

Vorgehen

Inkrementelles Vorgehen

Weil wir der AI nicht trauen können und weil wir immer wieder einen funktionierenden Codestand haben wollen, drängt sich ein inkrementelles Vorgehen auf.

AI Migrations Loop bbv
Diagramm des AI Migration Loop bei einer Rust-Migration

Es gibt einen fundamentalen AI Migration Loop, in dem Software mittels AI migriert wird.

Aus dem Review lernen wir und passen die Regeln an. Zum Beispiel können wir gewisse Muster als Design Pattern vorgeben oder unerwünschte Muster verbieten.

Zusätzlich gibt es einen inneren Loop, den die AI selbständig durchlaufen kann. Im Prinzip ist dies ein Red/Green/Refactor Loop bekannt von BDD und TDD. Dass die AI den inneren Loop anwenden soll, ist Teil des Regelsatzes.

Ein innerer Loop ist auch dadurch motiviert, dass die AI oft Regeln «vergisst». In einem Code Review einer anderen AI stehen diese Regeln aber im Fokus und gehen weniger im Kontext unter.

One-Shot

Bei diesem Ansatz gehen wir davon aus, dass die Ausgangscodebasis gut genug ist. Wir lassen die gesamte Codebasis auf einmal migrieren. Weil das Resultat aber fast ganz sicher ungenügend ist, verbessern wir den Code mit dem fundamentalen Loop.


Wenn wir feststellen, dass das Resultat derart schlecht ist, dass inkrementelle Verbesserungen nicht zielführend sind, kann das daran liegen, dass unsere Regeln nicht gut genug sind. In diesem Fall verbessern wir die Regeln und probieren es noch einmal. Falls dies wiederholt scheitert, haben wir es möglicherweise doch mit einer ausgewachsenen Softwaremodernisierung zu tun.

Slice by Slice

Wenn die Codebasis zu gross ist für One-Shot oder wenn wir schon gute Akzeptanztests für Features haben, können wir mit dem fundamentalen Loop Feature um Feature migrieren.

Modernisierungsansatz

Zuerst müssen wir die Zielarchitektur festlegen und mindestens in Grundzügen implementieren oder dokumentieren, damit sich die AI daran halten kann. Gewisse Features können aus der bestehenden Codebasis übernommen werden (sonst wäre es keine Migration sondern eine Neuentwicklung). AI hilft, um diese Features im bestehenden Code zu lokalisieren, isolieren und extrahieren. So kann Slice by Slice mit dem fundamentalen Loop migriert werden.

Übrigens:
Festlegen der Zielarchitektur ist in der Regel eine entscheidende Aufgabe, in die sowohl Business-Stakeholders als auch Entwickler:innen involviert sein sollten.

Header Blogserie Rust Integration ins Team.

Erfolgreiche Einführung von Rust in Ihrem Team

Wie schaffen Sie es, Rust in Ihr bestehendes Team einzuführen? Essenziell dafür sind motivierte Mitarbeitende, eine klare Roadmap und die Weitergabe von Wissen.

Nützliche Taktiken

Meine Testabdeckung ist ungenügend

Falls die Abdeckung mit formellen Tests nicht genügend ist, können wir uns mit Approval Tests behelfen.
Dies hilft, um den inneren Loop zu schliessen. So hat die AI einen konkreten Anhaltspunkt dafür, ob die Software wie gewünscht funktioniert.

Gewusst?
Approval Testing (auch Golden Master oder Snapshot Testing) vergleicht die tatsächliche Ausgabe eines Codes (z.B. ein serialisiertes Struct oder println-Output) gegen eine zuvor genehmigte Referenzdatei. Anders als beim klassischen Unit-Test schreibt man den Erwartungswert nicht selbst, sondern genehmigt die vom Werkzeug erzeugte Ausgabe. Stark, um das aktuelle Verhalten von Legacy-Code vor Refactorings abzusichern. Nachteil: Jede beabsichtigte Änderung lässt den Test brechen und erfordert ein erneutes Genehmigen.

Rust: insta
C++: ApprovalTests.cpp
C#: ApprovalTests.Net oder Verify

Ich kann ein Muster nicht 1:1 von der Ausgangssprache zu Rust übersetzen

Gewisse Muster sind von Sprache zu Sprache verschieden. Beispiele:

Zur Info:
Rust unterstützt OO-Konzepte, kennt aber keine klassische Vererbung. Eine typische OOP-Hierarchie (Basisklasse mit virtuellen Methoden, Subklassen) wird in Rust über Traits plus Komposition gelöst. Polymorphie entweder statisch über Generics (fn f<T: Trait>) oder dynamisch über Trait-Objekte (dyn Trait).

Diese Muster naiv zu migrieren mag zwar manchmal funktionieren, ist aber nicht idiomatisch und spielt fast sicher nicht die Stärken von Rust aus. In solchen Fällen lohnt es sich deshalb ein Konzept zu entwickeln, wie ein Muster oder ein Datenmodell in Rust geeignet umgesetzt wird. Um schnell verschiedene Varianten auszuprobieren, hilft AI offensichtlich auch. Das so etablierte Muster muss der AI als Beispielcode oder als Regel zugänglich gemacht werden, damit die AI den Code korrekt migrieren kann.

Wie gehe ich mit dem Borrow Checker um?

Der Borrow Checker ist gefürchtet von Novizen und geschätzt von Könnern. Wenn das Compilieren fehlschlägt mit einem Borrow Checker Error, handelt es sich in den meisten Fällen um eine Designschwäche.

Die AI «löst» dieses Problem oft mittels Arc<Mutex<T>> damit der Borrow Checker nicht anschlägt, anstatt das zugrundeliegende Designproblem zu lösen. Manchmal müssen Objekte aber tatsächlich shared werden. Hier kommen wiederum die Software-Ingenieure zum Zug, um zu entscheiden, ob dies ein legitimer Fall ist, um ein Objekt zu sharen. Eine Faustregel könnte sein, dass Arc<Mutex<T>> und Konsorten schlicht verboten werden und die AI zwingend Rücksprache mit Entwickler:innen halten soll. Eine häufige idiomatische Lösung ist, dass Objekte mittels Nachrichten kommunizieren und nicht mittels sharen von Objekten.

Kommunizieren mittels Nachrichten?
Der Slogan «Do not communicate by sharing memory; instead, share memory by communicating.» der auch in Go umgesetzt wird ist im «Rust Buch» im Kapitel Message Passing in Rust konkret beschrieben.

Was prüft der Borrow Checker?
Die Borrowing-Regeln sind:
1. Ein Objekt kann beliebig oft immutable ausgeliehen (immutable borrow) werden.
2. Ein Objekt kann nur einmal gleichzeitig als mutable ausgeliehen werden, es gibt also genau diese eine mutable Reference.
3. Nachdem der mutable borrow seinen Scope verlässt, können die immutable borrows wieder verwendet werden oder es kann wieder ein mutable borrow erstellt werden.

Es braucht etwas Gewöhnung daran, dass der Scope einer Reference mit ihrer letzten Verwendung endet und nicht mit dem tatsächlichen Scope-Block.


Fazit

Gute Software ist mit dem geeigneten Vorgehen leicht nach Rust zu migrieren. Vor dem Aufkommen von AI hätte man wohl in den meisten Fällen gesagt, dass eine Migration zu aufwändig ist. Heute können wir eine Migration leicht nebenher bewerkstelligen. Ganz auf die AI verlassen kann man sich aber nicht. Es braucht immer noch erfahrene Entwickler:innen, die die Geschäftsziele verstehen und diese in eine geeignete Architektur und geeignete Designs umsetzen können.

FAQ zu Rust Migration mittels KI

Nein, aber die AI beschleunigt die Migration erheblich, ersetzt aber das menschliche Review nicht. Der generierte Code wird in einem inkrementellen Loop von erfahrenen Entwickler:innen geprüft und die Regeln werden laufend nachgeschärft. Eine vollautomatische Migration ohne Kontrolle führt zu nicht-idiomatischem und oft fehlerhaftem Rust-Code.

Nicht ohne Review. Der erste Output ist fast nie ausreichend. Erst durch den Red/Green/Refactor-Loop, eine sinnvolle Testabdeckung und das Festhalten von Architekturvorgaben in den Regeln entsteht produktionsreifer Code. Typische Schwachstellen wie unnötiges Arc<Mutex<T>> deuten auf Designprobleme hin, die ein Mensch beurteilen muss.

Ja. Vor AI galt eine solche Migration meist als zu aufwändig. Mit AI geht sie heute entscheidend schneller und ist damit machbar, sofern die bestehende Software eine zweckmässige Architektur und eine sinnvolle Testabdeckung hat. Ist die Codebasis schwer verständlich und ungetestet, handelt es sich eher um eine Softwaremodernisierung als um eine reine Migration.

Dann steht zuerst die Modernisierung an: Geschäftsziele, funktionale Anforderungen und Qualitätsziele klären, daraus eine Zielarchitektur ableiten. Fehlt eine formale Testabdeckung, helfen Approval Tests (Golden Master), um das bestehende Verhalten abzusichern, bevor migriert wird. AI hilft dann beim Lokalisieren, Isolieren und Extrahieren einzelner Features für eine Slice-by-Slice-Migration.

Portrait Oliver With
Der Experte

Oliver With

Oliver With ist Spezialist für Embedded Software. Als Senior-Entwickler ist er überzeugt, dass eng zusammenarbeitende Teams komplexe Probleme am besten lösen. Kreativität in der Lösungsfindung vereint er mit Qualität in der Entwicklung, um erfolgreiche Produkte zu schaffen. Er ist Rust-Enthusiast, weil es mit Rust erstmals eine Sprache gibt, die Sicherheit, Performance, Akzeptanz in der Industrie und Ergonomie für Entwickler kombiniert.
Senior Software-Ingenieur Embedded

Unser Wissen im Abo

Attention!

Sorry, so far we got only content in English for this section.