strict im Bestand: was es kostet und was es bringt
Neun Einzelschalter, ein Ordner nach dem anderen und eine Prüfung, die im Build blockiert. So wird aus einer Zahl im Terminal ein Plan, den ein Team durchhält.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Die Zahl im Terminal sagt weniger, als alle glauben
Der übliche Ablauf: Jemand setzt strict in der tsconfig auf true, lässt tsc laufen, sieht 1.400 Fehler und schaltet es wieder aus. Danach steht im Team die Aussage im Raum, das sei nicht machbar. Die Zahl trägt diese Aussage aber nicht, weil sie kein Maß für Aufwand ist. Ein einziger falsch deklarierter Rückgabewert in einem oft genutzten Modul erzeugt Dutzende Folgemeldungen an ganz anderen Stellen, und die verschwinden alle mit einer Zeile.
Umgekehrt steckt echter Aufwand dort, wo strictNullChecks eine Annahme sichtbar macht, die im Code nie geprüft wurde. Wenn eine Funktion seit Jahren davon ausgeht, dass ein Feld gefüllt ist, ist die Meldung kein Formalismus, sondern die Frage, was passieren soll, wenn es leer ist. Diese Frage kostet Zeit, und zwar nicht beim Tippen, sondern beim Nachdenken über die Fachlichkeit. Genau das ist auch der Gewinn.
Wer sich dagegen entscheidet, zahlt an anderer Stelle. Ohne noImplicitAny wandert jeder untypisierte Parameter als stiller Freifahrtschein durch den Code und nimmt allen nachgelagerten Prüfungen die Grundlage. Ohne strictNullChecks ist die häufigste Ausnahme in der Produktion weiterhin der Zugriff auf etwas, das undefined war. Die Entscheidung lautet also nicht sauber gegen pragmatisch, sondern: jetzt in Etappen bezahlen oder später einzeln in Fehlerberichten.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Wer entscheidet was
| Thema | Wer entscheidet | Wer setzt um | Stolperfalle |
|---|---|---|---|
| Ob strict kommt und bis wann | Die Teamleitung zusammen mit den Leuten, die den Code täglich anfassen, und zwar auf Basis einer Messung statt einer Schätzung. | Wer die Messung macht: einmal einschalten, tsc mit noEmit laufen lassen, Meldungen nach Ordner und Fehlercode zählen, wieder ausschalten. | Die Gesamtzahl der Meldungen wird als Aufwand gelesen. Ein einziger falscher Rückgabetyp in einem zentralen Modul erzeugt aber Dutzende Folgemeldungen, die gemeinsam verschwinden. |
| Reihenfolge der Einzelschalter | Wer die Architektur verantwortet, weil die Reihenfolge davon abhängt, wo die Daten ins System kommen. | Das jeweilige Team im eigenen Bereich, beginnend bei noImplicitAny und noImplicitThis. | strictNullChecks wird mit eingeschaltet, weil es zur selben Option gehört. Es braucht aber eigene Zeit und einen eigenen Termin, denn es lässt sich nicht halb einführen. |
| Umgang mit den Stellen, die noch nicht gehen | Das Team gemeinsam, als Konvention im Repository festgehalten. | Wer die Datei umstellt: @ts-expect-error mit einem Satz Begründung und einer Vorgangsnummer, nie @ts-ignore. | Das Ausrufezeichen als Abkürzung. Es unterdrückt die Meldung, prüft nichts und ist im Suchlauf später kaum von berechtigten Fällen zu unterscheiden. |
| Ab wann die Prüfung blockiert | Wer den Build verantwortet, im Einvernehmen mit den Teams, deren Ordner betroffen sind. | Ein zusätzlicher Schritt in der Pipeline, der tsconfig.strict.json prüft und bei einem Fehler abbricht. | Als bloße Warnung gestartet und nie scharf geschaltet. Nach zwei Wochen liest die Ausgabe niemand mehr, und die Zahl steigt wieder. |
| Fremde Typen und generierte Dateien | Wer die Abhängigkeiten pflegt. | skipLibCheck einschalten, generierte Ordner in exclude aufnehmen, fehlende Typpakete nachziehen. | skipLibCheck nimmt auch die eigenen Deklarationsdateien aus der Prüfung. Wer selbst .d.ts pflegt, verliert genau dort die Kontrolle. |
Ob strict kommt und bis wann
- Wer entscheidet
- Die Teamleitung zusammen mit den Leuten, die den Code täglich anfassen, und zwar auf Basis einer Messung statt einer Schätzung.
- Wer setzt um
- Wer die Messung macht: einmal einschalten, tsc mit noEmit laufen lassen, Meldungen nach Ordner und Fehlercode zählen, wieder ausschalten.
- Stolperfalle
- Die Gesamtzahl der Meldungen wird als Aufwand gelesen. Ein einziger falscher Rückgabetyp in einem zentralen Modul erzeugt aber Dutzende Folgemeldungen, die gemeinsam verschwinden.
Reihenfolge der Einzelschalter
- Wer entscheidet
- Wer die Architektur verantwortet, weil die Reihenfolge davon abhängt, wo die Daten ins System kommen.
- Wer setzt um
- Das jeweilige Team im eigenen Bereich, beginnend bei noImplicitAny und noImplicitThis.
- Stolperfalle
- strictNullChecks wird mit eingeschaltet, weil es zur selben Option gehört. Es braucht aber eigene Zeit und einen eigenen Termin, denn es lässt sich nicht halb einführen.
Umgang mit den Stellen, die noch nicht gehen
- Wer entscheidet
- Das Team gemeinsam, als Konvention im Repository festgehalten.
- Wer setzt um
- Wer die Datei umstellt: @ts-expect-error mit einem Satz Begründung und einer Vorgangsnummer, nie @ts-ignore.
- Stolperfalle
- Das Ausrufezeichen als Abkürzung. Es unterdrückt die Meldung, prüft nichts und ist im Suchlauf später kaum von berechtigten Fällen zu unterscheiden.
Ab wann die Prüfung blockiert
- Wer entscheidet
- Wer den Build verantwortet, im Einvernehmen mit den Teams, deren Ordner betroffen sind.
- Wer setzt um
- Ein zusätzlicher Schritt in der Pipeline, der tsconfig.strict.json prüft und bei einem Fehler abbricht.
- Stolperfalle
- Als bloße Warnung gestartet und nie scharf geschaltet. Nach zwei Wochen liest die Ausgabe niemand mehr, und die Zahl steigt wieder.
Fremde Typen und generierte Dateien
- Wer entscheidet
- Wer die Abhängigkeiten pflegt.
- Wer setzt um
- skipLibCheck einschalten, generierte Ordner in exclude aufnehmen, fehlende Typpakete nachziehen.
- Stolperfalle
- skipLibCheck nimmt auch die eigenen Deklarationsdateien aus der Prüfung. Wer selbst .d.ts pflegt, verliert genau dort die Kontrolle.
Neun Prüfungen stecken hinter der einen Option
- 01 noImplicitAny meldet jeden Parameter, dessen Typ niemand angegeben hat.
- 02 noImplicitThis meldet ein this, dessen Typ der Compiler nicht kennt.
- 03 alwaysStrict stellt jede Datei in den strikten Modus der Sprache.
- 04 strictNullChecks trennt null und undefined von allen anderen Typen.
- 05 strictFunctionTypes prüft Parameter von Funktionstypen strenger.
- 06 strictPropertyInitialization verlangt einen Wert für jedes Klassenfeld.
- 07 strictBindCallApply prüft die Argumente von bind, call und apply.
- 08 useUnknownInCatchVariables macht den Fehler im catch-Block zu unknown.
- 09 strictBuiltinIteratorReturn kam mit TypeScript 5.6 für eingebaute Iteratoren dazu.
Was du danach entscheiden kannst, ohne zu raten
Die Einführung ist weniger eine technische als eine organisatorische Aufgabe. Der Compiler kann alles, was du brauchst, aber jemand muss festlegen, in welcher Reihenfolge verschärft wird, was mit den Ausnahmen passiert und ab wann die Prüfung wirklich blockiert.
Den Aufwand messen statt schätzen
Du schaltest strict einmal ein, lässt tsc mit noEmit laufen, gruppierst die Meldungen nach Ordner und Fehlercode und schaltest wieder aus. Erst diese Verteilung sagt dir, wo die Arbeit steckt, und nicht die Gesamtzahl.
Die Einzelschalter in der richtigen Reihenfolge ziehen
noImplicitAny und noImplicitThis kommen zuerst, weil sie die Grundlage für alles Weitere legen. strictNullChecks kommt zuletzt und bekommt eigene Zeit, weil es sich nicht sinnvoll halb einführen lässt.
Eine zweite Konfiguration statt eines Stichtags
Du legst eine tsconfig.strict.json an, die die Basis erweitert, strict einschaltet und nur die bereits bereinigten Ordner einschließt. Der eigentliche Build bleibt unverändert, die strenge Prüfung läuft zusätzlich.
Ausnahmen wählen, die von selbst verschwinden
Du nimmst @ts-expect-error statt @ts-ignore. Sobald die Stelle repariert ist, meldet der Compiler die überflüssige Ausnahme, und die Zeile fliegt raus. @ts-ignore bleibt dagegen für immer stehen.
Den Zeitpunkt für das Blockieren festlegen
Solange die strenge Prüfung nur eine Warnung ist, liest sie nach zwei Wochen niemand mehr. Du legst deshalb fest, ab wann ein Pull Request daran scheitert, und für welche Ordner das schon gilt.
Fremde und generierte Dateien aus dem Weg räumen
Mit skipLibCheck nimmst du die Deklarationsdateien aus der Prüfung, und generierte Ordner gehören in exclude. Dir ist dabei klar, dass skipLibCheck auch deine eigenen .d.ts-Dateien nicht mehr prüft.
KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Was strict überhaupt einschaltet
strict ist kein eigener Modus, sondern ein Sammelschalter für neun Einzelprüfungen: alwaysStrict, noImplicitAny, noImplicitThis, strictBindCallApply, strictBuiltinIteratorReturn, strictFunctionTypes, strictNullChecks, strictPropertyInitialization und useUnknownInCatchVariables. strictBuiltinIteratorReturn ist mit TypeScript 5.6 dazugekommen, und das ist kein Einzelfall: Unter strict können weitere Prüfungen nachrücken, ein Sprung auf eine neue Compiler-Version kann also neue Meldungen bringen. Wichtig für den Bestand ist, dass sich jede dieser Prüfungen einzeln übersteuern lässt. Du kannst also strict auf true setzen und darunter strictNullChecks vorübergehend auf false, statt alles gemeinsam abzuwarten. Damit sicherst du die einfacheren Prüfungen sofort und nimmst dir die aufwendigste getrennt vor.
Nicht in strict enthalten sind einige Optionen, die im Gespräch oft dazugezählt werden: noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, noPropertyAccessFromIndexSignature sowie die Aufräumhilfen noUnusedLocals und noUnusedParameters. Vor allem noUncheckedIndexedAccess ist eine eigene Entscheidung mit spürbaren Folgen, weil danach jeder Zugriff über einen Index oder einen dynamischen Schlüssel auch undefined liefern kann. Das ist fachlich richtig und erzeugt in Code, der viel mit Listen und Nachschlagetabellen arbeitet, noch einmal eine eigene Welle.
Warum strictNullChecks alleine steht
Ohne diese Prüfung sind null und undefined Mitglieder jedes Typs. Ein string kann also ohne Beanstandung null sein, und jede Zusicherung im Code ist damit eine Behauptung ohne Deckung. Mit der Prüfung werden beide zu eigenen Typen, die ausdrücklich in einer Vereinigung stehen müssen. Genau deshalb erzeugt sie den Großteil aller Meldungen und lässt sich nicht in kleinen Häppchen einführen: Sobald sie an ist, betrifft sie jede Zeile, die eine Variable liest.
Der Aufwand verteilt sich dabei sehr ungleich. In reinen Rechenfunktionen passiert fast nichts. Teuer wird es an den Rändern: bei Antworten aus dem Netz, bei Zugriffen aufs DOM, bei Konfigurationswerten und überall dort, wo eine Funktion optionale Parameter hat. Deshalb lohnt es sich, vor dem Einschalten genau diese Ränder zu bereinigen, also einmal ordentlich zu typisieren, was aus dem Netz kommt. Danach fällt ein erheblicher Teil der Meldungen weg, ohne dass jemand eine einzige if-Abfrage geschrieben hätte.
Beim Reparieren gibt es einen Umweg, der teuer ist und deshalb eine Absprache verdient: das Ausrufezeichen hinter einem Ausdruck, also wert!.name. Es sagt dem Compiler, dass hier sicher kein null steht, prüft aber nichts. In wenigen Fällen ist das richtig, etwa wenn ein Element garantiert im HTML des eigenen Templates steht. Als allgemeine Antwort auf Meldungen macht es die Einführung von strict wertlos, weil das Ergebnis genau die Sicherheit vortäuscht, die vorher gefehlt hat.
Der Weg über eine zweite Konfiguration
Statt eines Stichtags legst du eine tsconfig.strict.json an, die per extends auf der bestehenden Konfiguration aufsetzt, dort strict einschaltet und in include nur die Ordner aufzählt, die schon bereinigt sind. Der normale Build läuft unverändert weiter, die strenge Prüfung startet zusätzlich mit tsc -p tsconfig.strict.json --noEmit. Wächst die Liste in include, wächst der geschützte Bereich, und niemand muss auf einen großen Umbau warten.
Diese Konstruktion hat einen Haken, den man kennen sollte: Der Compiler prüft auch Dateien, die von den eingeschlossenen importiert werden, selbst wenn sie nicht in include stehen. In der Praxis bedeutet das, dass du an den Blättern des Abhängigkeitsbaums anfängst, also bei Hilfsfunktionen und Datenmodellen ohne eigene Importe. Von dort arbeitest du dich nach oben. Wer bei einer Seitenkomponente beginnt, zieht in der ersten Runde das halbe Projekt mit hinein.
Für die Steuerung im Alltag reicht ein einziges Signal: die Zahl der Meldungen aus dem strengen Lauf, festgehalten je Woche. Sie darf nur fallen. Sobald sie steigt, ist entweder ein Ordner zu früh in include gewandert oder jemand hat in einem geschützten Bereich neue Schulden angelegt. Diese eine Zahl ersetzt jede Diskussion darüber, ob es vorangeht.
Wann sich der Aufwand für ein Team nicht lohnt
Wenn eine Anwendung nur noch Fehlerbehebungen bekommt und in absehbarer Zeit abgelöst wird, ist die Verschärfung ein Umbau ohne Empfänger. Dasselbe gilt für Code, der ohnehin generiert wird, etwa Klienten aus einer Schnittstellenbeschreibung: Dort gehört die Strenge in den Generator und nicht in die erzeugten Dateien.
Umgekehrt gibt es drei Lagen, in denen sich der Aufwand fast immer rechnet. Erstens, wenn regelmäßig neue Leute dazukommen, denn strenge Typen sind die einzige Dokumentation, die nicht veraltet. Zweitens, wenn dieselbe Datenstruktur durch viele Module fließt, weil dort ein einzelnes any die Prüfung auf dem ganzen Weg abschaltet. Und drittens, wenn du ohnehin eine größere Umbaumaßnahme vor dir hast: Ein Refactoring unter strict ist eine andere Tätigkeit als eines ohne, weil der Compiler dir die betroffenen Stellen zeigt, statt sie dir zu überlassen.
Dazu passende Kurse
Wer im laufenden Projekt strengere Prüfungen nachträglich einführen will, findet den passenden Stoff in den TypeScript-Kursen.
Weil eine strenge Prüfung nur wirkt, wenn sie in der Pipeline blockiert, passen dazu Kurse rund um automatisierte Prüfungen im Build .
Wie sicher bist du beim Thema wirklich?
Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.
Wo du genau das übst
Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
Häufige Fragen
Kann ich strict einschalten und einzelne Prüfungen wieder abschalten?
Wie lange dauert das in einem mittelgroßen Projekt?
Ist @ts-expect-error nicht genauso schlimm wie @ts-ignore?
Bringt strict etwas, wenn wir viel mit Daten aus dem Netz arbeiten?
Deine Ansprechpartner
Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.
Yves Hoppe
Weiterbildung & Beratung
Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.
Norbert Jansen
Beratung & Inhouse
Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.
Strenge Typen einführen, ohne das Projekt anzuhalten
In den TypeScript-Kursen bei cmt arbeitest du an Code mit Altlasten und siehst, an welchen Stellen die Verschärfung wirklich Arbeit macht und an welchen sie sich von selbst erledigt.