In der Welt der Softwareentwicklung streben viele Entwickler nach Perfektion: sauberer Code, fehlerfreie Funktionen und optimale Architektur. Doch was wäre, wenn genau das Gegenteil – das bewusste Schreiben von schlechtem oder 'schrecklichem' Code – der eigentliche Schlüssel zur Meisterschaft im Programmieren wäre? Diese paradoxe Idee wird immer relevanter, besonders wenn es um das Lernen, Testen und Verbessern von Software geht. Kaum jemand versteht das besser als die Entwickler-Community um Rust, eine moderne Programmiersprache, die für Sicherheit und Effizienz steht. Der Kern dieses Ansatzes liegt in einer berühmten Erkenntnis von Kent Beck, einem Pionier des Extreme Programming: Es gibt eine Kunst darin, gerade so viel zu entwerfen, dass man Feedback erhält, und dann genau genug zu verbessern, um noch mehr Feedback zu bekommen. Dieser iterative Prozess fördert Anpassung und Qualitätssteigerung ohne übermäßigen Perfektionismus von Anfang an.
Aber warum schlechtem Code überhaupt Raum geben? Der erste und wichtigste Grund liegt in der Korrektheit. Code ist nur dann wirklich gut, wenn er das Richtige tut. Wenn er jedoch Fehler macht und die falsche Funktionalität liefert, sind alle anderen Eigenschaften wie Lesbarkeit oder Stil bedeutungslos. Das Akzeptieren, dass fehlerhafter Code ein essenzieller Schritt auf dem Weg zu funktionsfähiger Software ist, ermöglicht es Entwicklern, realistisch und pragmatisch an Projekte heranzugehen. Ein idealer Weg, um das Verhalten von Funktionen zu überprüfen, sind Tests.
Tests können bestätigen, ob eine Funktion korrekt agiert oder nicht. Interessanterweise sind großartige Tests jene, die nicht nur sicherstellen, dass der Code funktioniert, sondern auch erkennen, wenn er versagt. Um solche Tests zu schreiben, ist es sogar notwendig, absichtlich falsche Versionen der Funktion zu implementieren – das sogenannte Schreiben von 'schlechtem Code'. Im Rust-Ökosystem illustriert man dieses Prinzip gerne anhand eines einfachen Beispiels: einer Funktion, die die Anzahl der Zeilen in einem Eingabestream zählt. Die Testumgebung nutzt dabei eine sogenannte Cursor-Struktur, die wie eine simulierte Eingabequelle fungiert.
Ein kleiner Test legt fest, dass eine Eingabe mit zwei Textzeilen auch genau zwei als Ergebnis liefern muss. Um den Test zu validieren, beginnt man zunächst mit einer minimalen Implementierung, die immer null zurückgibt – eindeutig falsch. Dieser absichtliche Fehler führt dazu, dass der Test fehlschlägt, was wiederum bestätigt, dass der Test die korrekte Prüfungsfunktion erfüllt. Zur Herausforderung wird es, die richtige Signatur der Funktion festzulegen. Während im Test ein Cursor übergeben wird, soll die Funktion im echten Einsatz tatsächlichen Standard-Input verarbeiten.
Das Problem: Lösungsvorschläge wie ‚Cursor‘ oder ‚StdinLock‘ als Parameter-Typen scheiden aus, denn die Funktion soll mit beiden Arten von Eingaben arbeiten können. Hier kommen Traits ins Spiel – eine leistungsfähige Rust-Funktionalität, die es ermöglicht, eine Schnittstelle zu definieren, die verschiedene Typen erfüllen müssen. In diesem Fall ist der Trait BufRead entscheidend, da sowohl Cursor als auch StdinLock dieses Interface implementieren. Die endgültige Funktionssignatur lautet also: eine Funktion namens count_lines, die eine Eingabe vom Typ impl BufRead annimmt und eine Größe (usize) als Anzahl der Zeilen zurückgibt. Die anfängliche Implementierung ignoriert das Eingabeobjekt und gibt 0 zurück, um den Test scheitern zu lassen und damit dessen Wirksamkeit zu bestätigen.
Erst wenn die Entwickler diese Hürde überwinden und den Körper auf korrekte Funktionsweise anpassen, besteht die Funktion den Test. Die einfache, aber wirkungsvolle Lösung verwendet Rusts eigene Werkzeuge, etwa die Methode lines(), die Iteratoren über Zeilen eines Buffers erzeugt, und den Iteratorzähler count(). Ein Einzeiler genügt, um den Anforderungen gerecht zu werden: input.lines().count().
Dieses Beispiel demonstriert, dass komplexe Funktionen nicht zwangsläufig kompliziert sein müssen, wenn man die richtigen Sprachmittel kennt. Indem Entwickler zuerst schlechten Code schreiben, der absichtlich scheitert, und danach iterativ verbessern, fördern sie nicht nur die Codequalität sondern verbessern gleichzeitig ihr Verständnis für das Verhalten und die Anforderungen ihrer Funktionen. Mehr noch, das Schreiben von Tests, die auch Fehler erkennen, anstatt nur erfolgreiche Pfade zu validieren, macht Anwendungen robuster und weniger fehleranfällig in realen Einsatzszenarien. Ein weiterer Vorteil, der besonders in Rust eine Rolle spielt, ist die modulare Organisation von Testcode. Tests lassen sich in eigene Module verpacken, die nur bei Testausführungen kompiliert werden.
Dieses Vorgehen beschleunigt kompilierungszweige und schont Ressourcen, wenn tatsächliche Anwendungen gebaut oder ausgeführt werden. Mit Attributen wie #[cfg(test)] können Entwickler sicherstellen, dass Testmodule nur dann zur Anwendung kommen, wenn dazu explizit angefordert wird. Das bewusste Schreiben von schlechtem Code als Zwischenschritt ist also keineswegs ein Zeichen von Schwäche oder Unprofessionalität. Es ist vielmehr ein Ausdruck von pragmatischem Denken und methodischem Vorgehen. Diese Technik ermöglicht eine bessere Fehlererkennung, vereinfacht das Finden von Bugs und sorgt dafür, dass Tests wirklich robust sind.
Dadurch entsteht Code, der nicht nur funktioniert, sondern auch nachvollziehbar und wartbar ist. Diese Erkenntnisse sind branchenübergreifend anwendbar und nicht nur auf Rust begrenzt. Wer sich mit diesem Paradigma beschäftigt, versteht, dass der Weg zur Softwarequalität nicht über perfekten Code führt, sondern über iterative Verbesserungsschleifen, geschaffen durch Feedback und ehrliches Testen. Im Endeffekt macht genau dieser Prozess Entwickler zu besseren Programmierern und bringt nachhaltige Ergebnisse in der Softwareentwicklung. Abschließend zeigt das Beispiel des count_lines in Rust, wie mit einfachen, aber fundierten Mitteln schon nach kurzer Zeit funktionaler, zuverlässiger Code entsteht.
Das strategische Zulassen von schlechtem Code unterstützt den Lernprozess und ebnet den Weg zu stabilen Anwendungen. Dies ist eine wertvolle Perspektive für alle Entwickler, die mehr als nur Funktionalität anstreben – sie wollen Software schaffen, die wirklich funktioniert.