Letzten Herbst saß ich bei einem Maschinenbauer in Süddeutschland. Drei .NET-Entwickler, eine gewachsene WinForms-Anwendung aus dem Jahr 2014, und ein neuer Teamlead, der „endlich mal Qualität rein bringen" wollte. Einer seiner ersten Schritte: nullable reference types aktivieren. Er hatte einen Blogartikel gelesen, ein paar YouTube-Videos geschaut, und dann <Nullable>enable</Nullable> in die .csproj geschrieben.
Zwei Wochen später rief er mich an. 4.700 Warnungen. Zwei Entwickler, die innerlich gekündigt hatten. Und ein Geschäftsführer, der fragte, warum das Team seit Wochen nichts liefert. Das war kein technisches Problem. Das war ein Planungsproblem — und ein klassisches Beispiel dafür, was passiert, wenn man Enterprise-Migrationslogik in einen Mittelstandskontext kippt ohne Anpassung.
Was Maarten Balliauw sagt — und er hat Recht
Maarten ist einer der wenigen .NET-Blogger, die ich regelmäßig lese, weil er keine Tutorials schreibt, sondern echte Erfahrung teilt. In seinem Talk zu Nullable Reference Types macht er klar, was viele unterschätzen: Das Feature ist kein Schalter, den man umlegt. Es ist eine Migration. Wer einfach ? und ! durch den Code streut, löst nichts — er versteckt Warnungen. Maarten zeigt konkrete Techniken, wie man schrittweise vorgeht, welche Annotationen wirklich helfen, und warum das Ausrufezeichen (!, der sogenannte null-forgiving operator) in den meisten Fällen die falsche Antwort ist. Der Titel seines Talks — „dammit is not the answer" — ist ein direkter Verweis darauf, dass ! (englisch umgangssprachlich auch „dammit operator" genannt) keine Lösung ist, sondern Kapitulation vor dem Problem. Das ist technisch präzise und richtig.
Was in dem Talk nicht vorkommt: Wie das in einem Team mit drei bis zwölf Entwicklern, ohne dedizierten Architekten, ohne Testautomatisierung und mit einem Releasedruck von zwei Wochen wirklich umsetzbar ist.
Wie das im Mittelstand wirklich landet
Im Mittelstand gibt es keine Migrations-Task-Force. Es gibt zwei Entwickler, die auch den Support machen, und einen dritten, der gerade im Urlaub ist. Der Unterschied zu dem, worüber Maarten spricht, ist nicht technischer Natur — er hat bei der Technik Recht. Der Unterschied ist Bandbreite.
Was sich lohnt, direkt und ohne Diskussion:
Neue Projekte und neue Module mit <Nullable>enable</Nullable> zu starten kostet nichts und zahlt sich sofort aus. Null-Referenz-Exceptions sind bei meinen Mandanten nach wie vor die häufigste Ursache für Support-Tickets in .NET-Anwendungen. Das ist keine Theorie, das ist aus Ticket-Auswertungen von vier verschiedenen KMU in den letzten 18 Monaten. Wer heute ein neues Feature schreibt, schreibt es nullable-aware — Punkt. Kein Aufwand, kein Risiko.
Was sich nicht lohnt, zumindest nicht so:
Eine bestehende Codebasis mit 150.000 Zeilen aus dem Bestand auf einmal enablen. Ich habe das in drei Mandaten begleitet. Zweimal war das Ergebnis: Entwickler frustriert, Migration nach drei Wochen eingefroren, Code in einem Zwischenzustand, der schlechter lesbar ist als vorher. Der dritte hat es durchgezogen — mit sechs Monaten Kalenderzeit und einem dedizierten Refactoring-Sprint pro Quartal. Das ist realistisch. Aber das muss man so kommunizieren, nicht als „wir machen das mal nebenbei".
Was in Maarens Talk fehlt:
Die organisatorische Seite. Wer entscheidet, welche Module zuerst dran sind? Wie geht ihr mit dem unvermeidlichen Moment um, wo ein Entwickler ! schreibt, weil der Sprint-Druck zu hoch ist? Wann ist eine Warnung eine Warnung und wann ist sie ein Bug? Im Enterprise-Kontext gibt es Architektur-Boards und Coding-Guidelines-Dokumente. Im Mittelstand entscheidet das der Entwickler im Moment. Das ist keine Kritik an Maarten — sein Talk ist ein Tech-Talk, kein Beratungs-Workshop. Aber du musst das wissen, wenn du seine Empfehlungen in deinem Team umsetzt.
Meine konkrete Empfehlung
Wenn du heute in einer bestehenden .NET-Codebasis bist und nullable reference types einführen willst, mach folgendes:
Schritt 1 — Aktiviere das Feature nicht global. Aktiviere es per Datei, mit #nullable enable am Dateikopf. Fang mit den Klassen an, die die meisten Null-Exceptions verursachen. Die findest du in deinen Logs. Wenn du keine Logs hast, ist das das eigentliche Problem, und das löst du zuerst.
Schritt 2 — Setz eine Teamregel: ! ist erlaubt, aber muss kommentiert sein. Kein Kommentar, kein Merge. Nicht weil der Kommentar das Problem löst, sondern weil er erzwingt, dass der Entwickler versteht, warum er ! schreibt. „Hier kann theoretisch null reinkommen, aber der Aufrufer garantiert das nie" ist eine valide Aussage. „Ich weiß es nicht" ist es nicht.
Schritt 3 — Plant die Migration als eigenes Backlog-Item, nicht als Anhang an Feature-Tickets. Ich schätze für eine typische Mittelstands-Codebasis (50.000 bis 200.000 Zeilen, keine Tests): drei bis acht Personentage pro 10.000 Zeilen, wenn ihr es sauber macht. Das ist kein Sprint, das ist ein Quartalsprojekt. Verkauft es auch so intern.
Schritt 4 — Hört auf, wenn es anfängt, Schmerzen zu machen. Ernsthaft. Eine 70%-nullable-Codebasis, die jeder versteht, ist besser als eine 100%-nullable-Codebasis, die niemand anfassen will. Perfekt ist der Feind von fertig — gerade im Mittelstand.
Eine Zahl noch, weil ich die immer mitgebe: In dem Maschinenbauer-Projekt aus meiner Einleitung haben wir nach der Neuplanung zunächst die fünf Module aktiviert, die für 80% der Null-Exceptions verantwortlich waren. Das waren 12.000 von 80.000 Zeilen. Aufwand: vier Tage. Ergebnis: 60% weniger Null-Exceptions in der Produktion im Folgequartal. Den Rest der Codebasis haben wir bis heute nicht angefasst. Das war die richtige Entscheidung.
Der vollständige Original-Beitrag von Maarten Balliauw: Talk - Bringing C# nullability into existing code
Pflichtlektüre, wenn du das technische Handwerk verstehen willst. Was du dann noch brauchst, ist ein Plan für dein Team — und der sieht im Mittelstand anders aus als auf einer Konferenz in Belgien.
Wenn du gerade vor einer ähnlichen Entscheidung stehst und nicht weißt, wo du anfangen sollst — oder dein Teamlead gerade 4.700 Warnungen anstarrt: → 30 Min Klartext-Sparring
— Bernhard