Die versteckten Kosten von TypeScript as-Casts — und wie man sie systematisch eliminiert

Die Type Assertion, die harmlos aussieht (und es nicht ist)
const user = response.data as User — eine Zeile, die in praktisch jeder TypeScript-Codebasis existiert. Sie kompiliert sauber. Sie erzeugt keinen Runtime-Overhead. Und sie ist exakt der Punkt, an dem das Typsystem aufhört, seinen Job zu machen.
Was diese Zeile tut: Sie weist den TypeScript-Compiler an, den tatsächlichen Typ von response.data zu ignorieren und stattdessen blind zu akzeptieren, dass es sich um ein User-Objekt handelt. Keine Validierung. Keine Prüfung. Ein reines Compile-Time-Versprechen ohne jede Runtime-Absicherung. Wenn die API morgen ein Feld umbenennt, ein Pflichtfeld hinzufügt oder die Struktur ändert, kompiliert der Code weiterhin fehlerfrei — und bricht erst in Produktion.
Das Perfide daran: Der Compiler behandelt den Cast als Grundwahrheit. Der falsche Typ propagiert sich über Hunderte Zeilen downstream — in Komponenten, Utility-Funktionen, State Management. Jede nachfolgende Typprüfung basiert auf der Lüge der ersten Assertion. Wenn der Bug schließlich auftritt, liegt der as-Cast vier Abstraktionsschichten weiter oben, und der Blast Radius ist enorm.
Zürich, August 2022. Ein Bankenprojekt, eine umfangreiche Migration einer bestehenden Codebasis auf TypeScript. Nicht alle Entwickler im Team waren mit TypeScript vertraut — einige waren schlicht überfordert von der plötzlichen Anforderung, alles sauber zu typisieren. Also taten wir, was Teams unter Druck tun: Wir brachten den Compiler zum Schweigen. as hier, as dort — Hauptsache, die roten Linien verschwinden.
Wochen später informierte das Backend-Team über eine Typänderung an einer zentralen Schnittstelle. Wir passten die Typen im Frontend an, die Linting-Pipeline lief grün, die Tests bestanden. Deployment nach Produktion. Und dann: etwas stimmte nicht. Das Frontend erwartete die Daten in exakt dem neuen Format — aber an einer Stelle im Code hatte jemand den Response-Typ lokal neu definiert und die API-Antwort per as auf diese lokale Definition gecastet. Der Compiler hatte keine Chance zu warnen, weil der Cast ihm explizit gesagt hatte: Alles in Ordnung hier, geh weiter.
Das eigentliche Problem war trivial — eine Handvoll Zeilen, die angepasst werden mussten. Aber es zu finden war alles andere als trivial. Mehrere Stunden Debugging, bis wir den einen Cast identifiziert hatten, der die Typänderung unsichtbar gemacht hatte. Hotfix, Deployment, Aufatmen. Eine schmerzhafte Lektion, die sich ins Team eingebrannt hat: as ist kein Shortcut. Es ist eine Zeitbombe mit unbekanntem Timer.
Warum as proliferiert: Der Weg des geringsten Widerstands
as proliferiert: Der Weg des geringsten WiderstandsKein Entwickler schreibt as-Casts aus Böswilligkeit. Die Ursachen sind strukturell, nicht individuell.
Die Migrationslast
Jede JavaScript-zu-TypeScript-Migration erzeugt Casts. Teams unter Zeitdruck setzen as als Übergangsmaßnahme ein — „wir fixen das später". Später kommt nie. Eine Codebasis mit 500.000 Zeilen, die 2021 migriert wurde, trägt heute typischerweise noch Hunderte dieser Migrations-Casts, die nie überarbeitet wurden.
TypeScripts eigene Stdlib-Lücken
Hier muss man ehrlich sein: TypeScript zwingt Entwickler an bestimmten Stellen zum Cast. JSON.parse() gibt any zurück. fetch().json() ist untypisiert. localStorage.getItem() liefert string | null ohne Schema-Validierung. An der absoluten Systemgrenze muss irgendwann ein Typ zugewiesen werden. Das Problem ist nicht der Cast an der Grenze selbst — sondern der Cast, der sich unkontrolliert weiter propagiert, ohne dass jemals eine Runtime-Validierung stattfindet.
AI-generierter Code als struktureller Beschleuniger
GitHub Copilot, Cursor und Claude erzeugen as-Casts mit hoher Frequenz, wenn sie den korrekten Typ nicht inferieren können. Der fundamentale Unterschied zum menschlichen Entwickler: Ein Mensch fragt einen Kollegen oder gräbt sich in die Typdefinition. Ein LLM castet und macht weiter. Es unterdrückt seine eigene Unsicherheit, indem es den Compiler zum Schweigen bringt.
In Codebases, die AI-generierte Pull Requests im großen Maßstab akzeptieren, wird as damit zum Vektor für KI-eingeführte stille Fehler. Das ist qualitativ neues Terrain.
as ist das neue any
as ist das neue anyDie Community hat any weitgehend geächtet. ESLint-Regeln wie @typescript-eslint/no-explicit-any fangen es zuverlässig. Also sind die Flüchtlinge weitergezogen — zu as. Linter ignorieren es größtenteils. Es sieht typsicher aus. Es fühlt sich typsicher an. Es ist nur keine Typsicherheit.
State-Management-Bibliotheken wie Zustand, Jotai und TanStack Query haben ihre Generics massiv verbessert. Teams schreiben kein any mehr — aber sie casten die Ergebnisse mit as. Das Symptom hat sich verschoben, die Krankheit ist dieselbe.
Nach dem Zürcher Vorfall haben wir im gleichen Projekt ein any-Audit durchgeführt. Das Ergebnis war auf den ersten Blick beeindruckend: weniger als zwanzig any-Vorkommen in der gesamten Codebasis. Das Team war stolz. Dann haben wir nach as gesucht. Über dreihundert Instanzen. Der Großteil davon an API-Boundaries und in State-Management-Logik — exakt die Stellen, an denen Typsicherheit am meisten zählt. Die any-Eliminierung hatte die Entwickler lediglich dazu gebracht, ihre Unsicherheit eleganter zu verpacken. Statt any zu schreiben, schrieben sie as ConcreteType — visuell sauberer, funktional identisch unsicher.
Die vier Explosionszonen: Wo as den größten Schaden anrichtet
as den größten Schaden anrichtet1. API-Response-Deserialisierung
const transaction = response.data as Transaction — die klassische Vertrauensgrenze. Das Frontend-Team nimmt an, dass die API den Vertrag einhält. Dann fügt das Backend-Team ein neues Pflichtfeld hinzu. Der TypeScript-Compiler kann nicht warnen, weil der as-Cast ihm explizit gesagt hat, dass alles in Ordnung ist.
Im DACH-Raum trifft dieses Muster auf eine besondere Realität: Regulierte Branchen — Banken (FINMA), Versicherungen (BaFin), Gesundheitswesen — haben komplexe API-Landschaften mit externen Schnittstellen, deren Schemas sich durch regulatorische Anforderungen ändern. Ein as-Cast an einer solchen Schnittstelle macht den Compiler exakt in dem Moment nutzlos, in dem er am dringendsten gebraucht wird.
2. JSON.parse() und Datenbank-Deserialisierung
JSON.parse() und Datenbank-Deserialisierungconst config = JSON.parse(row.config) as TenantConfig — ein Muster, das in jeder Multi-Tenant-SaaS-Anwendung vorkommt. Wenn TenantConfig ein neues Pflichtfeld bekommt, haben existierende Datenbankzeilen dieses Feld nicht. Das Ergebnis: undefined-Zugriffe in Produktion. Eine Zod-Schema-Validierung mit .default() hätte die Schema-Migration automatisch behandelt. Der as-Cast machte die Schema-Evolution für das Typsystem unsichtbar.
3. Test-Mocks
Ein subtiler, aber besonders tückischer Fall. Test-Dateien, die as verwenden, um Mock-Daten zu konstruieren, erzeugen Mocks, die vom realen Typ abweichen, sobald sich die Codebasis weiterentwickelt. Die Tests laufen weiterhin grün — der Mock passt noch zur alten Struktur. Die Runtime bricht — die neue API-Struktur passt nicht. as in Tests ist genau der Ort, an dem diese stille Divergenz am längsten überlebt.
4. postMessage / Worker-Grenzen
postMessage / Worker-GrenzenWeb Worker und postMessage-Kommunikation sind untypisiert by design. event.data as WorkerPayload ist der Reflex. Aber Worker-Boundaries sind besonders gefährlich, weil sie oft über Modul- und Team-Grenzen hinweg verlaufen. Das Sender-Team ändert das Payload-Format; das Empfänger-Team merkt nichts, weil der Cast schweigt.
Im selben Bankenprojekt trat Monate nach dem ersten Vorfall ein verwandtes Problem in den Test-Mocks auf. Ein Kollege hatte die Mock-Daten für einen zentralen Service per as TransactionResponse typisiert. Als das Backend-Team ein neues Statusfeld einführte, kompilierten alle Tests weiterhin sauber — der Mock kannte das Feld schlicht nicht, und der Cast unterdrückte jede Warnung. Die Integrationstests liefen grün. Erst als ein QA-Tester in der Staging-Umgebung eine spezifische Transaktionsansicht öffnete, fiel auf, dass das Frontend den neuen Status nicht darstellte. Drei Wochen hatte der Bug unbemerkt im Code gelebt. Der Fix war wieder trivial — aber die Erkenntnis war es nicht: Unsere Tests hatten aktiv dabei geholfen, den Fehler zu verstecken.
Audit First: Die eigene as-Schuld messen
as-Schuld messenBevor man anfängt zu eliminieren, muss man den Bestand kennen. Die Diagnose ist überraschend einfach.
Schnelle Bestandsaufnahme
1# Alle as-Casts zählen (ohne as const)2grep -r " as " --include="*.ts" --include="*.tsx" src/ | grep -v "as const" | wc -l3
4# Die gefährlichsten Varianten: double assertion5grep -rn "as unknown as\|as any as" --include="*.ts" --include="*.tsx" src/6
7# Heatmap nach Verzeichnis8grep -r " as " --include="*.ts" --include="*.tsx" src/ | grep -v "as const" \9 | cut -d'/' -f1-3 | sort | uniq -c | sort -rn | head -20Die Heatmap ist das aufschlussreichste Werkzeug. Wo konzentrieren sich die Casts? Die Antwort ist vorhersagbar: in den ältesten Teilen der Codebasis, in den am häufigsten geänderten Modulen und dort, wo die Abstraktionen am schwächsten sind. Eine as-Heatmap ist ein Proxy für architekturelle Schulden.
ESLint als systematisches Werkzeug
1// .eslintrc.js — Die Regel, die die meisten Teams nicht kennen2{3 rules: {4 '@typescript-eslint/consistent-type-assertions': ['error', {5 assertionStyle: 'never'6 }]7 }8}Der entscheidende Punkt: @typescript-eslint/no-explicit-any ignoriert jeden as-Cast. Die meisten Teams führen die any-Regel aus und halten sich für sicher, während sie Hunderte von as-Casts akkumulieren. consistent-type-assertions mit assertionStyle: 'never' ist die Regel, die tatsächlich greift.
Für den Übergang: Die Regel zunächst als Warning einführen und nur für neue Dateien als Error schalten. So entsteht keine Lawine an Fehlern, aber neuer Code ist sofort sauber.
Das as unknown as T-Pattern als Alarmzeichen
as unknown as T-Pattern als AlarmzeichenDie Double Assertion — x as unknown as T — ist die nukleare Option. Sie durchbricht die Typverfolgung vollständig. 80 Instanzen dieses Patterns in einer 200.000-Zeilen-Codebasis bedeuten 80 Stellen, an denen der Typgraph vollständig unterbrochen ist. Jedes Refactoring, das T ändert, korrumpiert alle Downstream-Consumer — still, ohne Compiler-Warnung.
Als wir nach dem Zürcher Produktionsvorfall beschlossen, den gesamten Cast-Bestand systematisch aufzuarbeiten, lief zuerst das grep-Skript. Das Ergebnis: über dreihundert as-Casts, davon knapp zwanzig Double Assertions der Form as unknown as T. Die Heatmap zeigte ein klares Bild: Die höchste Dichte lag in den API-Boundary-Modulen — exakt dort, wo die TypeScript-Migration unter dem größten Zeitdruck stattgefunden hatte. Der zweitgrößte Cluster war der State-Management-Layer, wo Zustand-Stores ihre API-Responses in lokale Typen casteten. Das Team war zunächst defensiv — „das hat doch funktioniert". Bis wir die Cast-Dichte pro Modul neben die Bug-Tickets der letzten sechs Monate legten. Die Korrelation war nicht subtil. Sie war offensichtlich.
Das Replacement-Toolkit (nach ROI geordnet)
1. satisfies für Definition-Site-Narrowing
satisfies für Definition-Site-NarrowingSeit TypeScript 4.9 verfügbar, löst satisfies einen der häufigsten legitimen as-Anwendungsfälle: einen Literaltyp auf einen breiteren Typ erweitern, ohne die Literal-Inferenz zu verlieren.
1// Vorher: as-Cast widened den Typ2const routes = {3 home: '/home',4 about: '/about',5} as Record<string, string>; // verliert Literal-Typen6
7// Nachher: satisfies prüft UND erhält Literal-Typen8const routes = {9 home: '/home',10 about: '/about',11} satisfies Record<string, string>; // Typ bleibt { home: "/home", about: "/about" }Wenn eine Codebasis noch as SomeEnum-Patterns aus der Zeit vor 4.9 enthält, ist das die niedrighängendste Frucht. Rein mechanische Ersetzung, null Risiko, sofortiger Gewinn.
2. Zod / Valibot / ArkType an Vertrauensgrenzen
Schema-Validierungsbibliotheken generieren TypeScript-Typen aus runtime-validierten Schemas. Das Muster ist strikt überlegen:
1// Vorher: Vertrauensvorschuss2const user = response.data as User;3
4// Nachher: Validierung generiert den Typ5import { z } from 'zod';6
7const UserSchema = z.object({8 id: z.string().uuid(),9 email: z.string().email(),10 role: z.enum(['admin', 'user', 'viewer']),11});12
13type User = z.infer<typeof UserSchema>;14
15const user = UserSchema.parse(response.data);16// → Typ ist User, UND die Daten sind tatsächlich validiertGleiche Ergonomie, null Runtime-Risiko. Der Schema-Ansatz hat einen zusätzlichen Vorteil: Wenn sich die API ändert, schlägt die Validierung sofort und laut fehl — nicht still und drei Abstraktionsschichten weiter unten.
Für Bundle-Size-sensitive Projekte: Valibot ist deutlich kleiner als Zod und verfolgt einen tree-shakeable Ansatz.
3. Discriminated Unions statt Cast-after-Check
1// Vorher: manuelles Narrowing mit Cast2if (response.ok) {3 const data = response.data as User; // ← Cast4} else {5 const error = response.data as ErrorResponse; // ← Cast6}7
8// Nachher: Discriminated Union eliminiert beide Casts9type ApiResponse<T> =10 | { ok: true; data: T }11 | { ok: false; error: string };12
13function handleResponse(res: ApiResponse<User>) {14 if (res.ok) {15 res.data; // ← Typ ist User, kein Cast nötig16 } else {17 res.error; // ← Typ ist string, kein Cast nötig18 }19}Die meisten as-Casts in realen Codebases sind kein Narrowing-Problem — sie sind ein Design-Problem. Eine saubere Discriminated Union eliminiert eine ganze Klasse von Casts auf Architekturebene.
4. User-Defined Type Guards für wiederverwendbares Narrowing
1// Zumindest benannt, findbar, auditierbar2function isUser(value: unknown): value is User {3 return (4 typeof value === 'object' &&5 value !== null &&6 'id' in value &&7 'email' in value8 );9}Ein Type Guard führt auch keine Runtime-Validierung auf Zod-Niveau durch. Aber der Unterschied zu as ist entscheidend: Er ist benannt, findbar und auditierbar. Man kann einen Type Guard an einer Stelle aktualisieren; man kann nicht 40 verstreute as User-Casts ohne grep aktualisieren.
5. as const — der falsche Feind
as const — der falsche Feindas const ist kategorisch verschieden von as SomeType. Es ist eine Narrowing-Direktive, keine Assertion. Es verengt auf Literal-Typen und erzeugt keine Unsicherheit. Eine Linting-Regel, die pauschal as verbietet, fängt das Falsche. Die Unterscheidung muss in jedem Tooling-Ansatz explizit sein.
Systematische Elimination: Ein Migrations-Playbook
Schritt 1: Die „No New as"-Policy
as"-PolicyAb sofort darf kein neuer as-Cast in die Codebasis gelangen. Technisch umgesetzt:
1// eslint.config.js — Neue Dateien: strict2// Bestehende Dateien: warning (eslint-disable für Legacy erlaubt)3{4 rules: {5 '@typescript-eslint/consistent-type-assertions': ['error', {6 assertionStyle: 'never'7 }]8 }9}In CI als Gate: PR-Checks schlagen fehl, wenn neue as-Casts hinzugefügt werden. Bestehende werden per eslint-disable-Kommentar mit Ticket-Referenz markiert — so ist jeder Legacy-Cast sichtbar und trackbar.
Schritt 2: Codemods für die einfachen Gewinne
Drei Kategorien lassen sich meist automatisiert ersetzen:
as const-Kandidaten: Literal-Objekte, die fälschlicherweise mitas SomeTypegewidened werdensatisfies-Kandidaten:as-Casts an Definitionsstellen, bei denen der Wert den Zieltyp tatsächlich erfüllt- Redundante Casts:
x as string, wobeixbereitsstringist (häufiger als erwartet)
jscodeshift oder ast-grep sind die Werkzeuge der Wahl für automatisierte Transformationen dieser Art.
Schritt 3: Inkrementelles Strict-Flag-Rollout
Die empfohlene Reihenfolge für strikte Compiler-Flags:
strict: true(falls noch nicht aktiv — Basis)noUncheckedIndexedAccess: true— decktas-Casts auf, die Array/Object-Index-Zugriffe ohne Boundary-Check maskierenexactOptionalPropertyTypes: true— unterscheidetundefinedvon „Eigenschaft fehlt", was vieleas-Casts an Konfigurationsobjekten aufdeckt
Jedes Flag wird Dutzende as-Casts als die Probleme sichtbar machen, die sie sind. Deshalb die as-Reduktion vor dem Flag-Rollout starten — sonst trifft man auf Hunderte Compiler-Fehler gleichzeitig.
Schritt 4: Boundary-Module als Investitionsziel
Die höchste Rendite liefert die Eliminierung von as-Casts an Vertrauensgrenzen — dort, wo externe Daten in das Typsystem eintreten. Diese Module mit Zod/Valibot-Schemas auszustatten eliminiert nicht nur die Casts, sondern liefert als Nebeneffekt Runtime-Validierung, bessere Fehlermeldungen und automatische Schema-Dokumentation.
Die „No New as"-Policy, die wir nach dem Zürcher Vorfall einführten, stieß anfangs auf Widerstand. Zwei Argumente kamen sofort: „Das verlangsamt uns" und „Manchmal geht es nicht anders". Beides war teilweise berechtigt. Also haben wir einen Kompromiss gewählt: Die ESLint-Regel als Error für alle neuen Dateien, als Warning für bestehende. Jeder Legacy-Cast bekam einen eslint-disable-Kommentar mit Jira-Ticket. Innerhalb von drei Monaten sank der Cast-Bestand um rund vierzig Prozent — nicht durch heroische Refactoring-Sprints, sondern durch natürliche Code-Evolution: Jedes Mal, wenn jemand eine Datei anfasste, musste der Cast entweder gerechtfertigt oder eliminiert werden. Nach sechs Monaten war die Debatte vorbei. Die Produktionsvorfälle durch Typ-Mismatches waren auf null gesunken, und selbst die anfänglichen Skeptiker verteidigten die Regel in Onboarding-Sessions für neue Teammitglieder.
Was legitimer as-Einsatz aussieht (und warum es fast keinen gibt)
as-Einsatz aussieht (und warum es fast keinen gibt)Es gibt genau eine Situation, in der ein as-Cast vertretbar ist: an der absoluten Systemgrenze, unmittelbar gefolgt von einer Runtime-Validierung, in einem dedizierten Boundary-Modul.
1// Vertretbar: Cast am absoluten Rand, sofort validiert2function parseApiResponse<T>(raw: unknown, schema: z.ZodSchema<T>): T {3 return schema.parse(raw); // Zod validiert; as wäre hier nicht mal nötig4}5
6// Grenzfall: DOM-APIs, bei denen der Compiler nicht weiß, was querySelector liefert7const canvas = document.querySelector('#canvas') as HTMLCanvasElement | null;8// → Vertretbar, wenn der Selektor bekannt ist und null gehandhabt wirdDie wirklich legitimen Fälle — nach exhaustivem Narrowing, in DOM-Kontexten, bei TypeScript-Compiler-Limitierungen — machen in der Praxis einen einstelligen Prozentsatz aller as-Casts aus. Die anderen 90%+ sind Abkürzungen.
TypeScripts Typsystem ist absichtlich unsauber in dieser Hinsicht — das ist dokumentiert und war eine explizite Designentscheidung des TS-Teams, optimiert auf Ergonomie statt formale Korrektheit. Wer das nicht weiß, behandelt Compiler-Akzeptanz als Korrektheitsbeweis. "hello" as unknown as number kompiliert fehlerfrei. Das ist kein Bug, es ist das Design.
Der AI-Codegen-Faktor: Wenn das LLM castet statt nachdenkt
AI-generierter Code hat ein strukturelles as-Problem, das sich qualitativ von menschlich geschriebenem Code unterscheidet.
Ein menschlicher Entwickler, der den Typ nicht kennt, hat mehrere Optionen: Dokumentation lesen, einen Kollegen fragen, die Typdefinition inspizieren. Ein LLM hat eine Option: den wahrscheinlichsten Token generieren. Und der wahrscheinlichste Token nach einem Typ-Mismatch ist as.
Gegenmaßnahmen:
- CI-Gate:
consistent-type-assertions: 'never'fängt AI-generierte Casts genauso wie menschliche. Die Maschine lernt schnell, wenn der PR-Check fehlschlägt. - Cursor/Copilot Rules: Projektweite
.cursorrulesoder.github/copilot-instructions.mdmit expliziter Anweisung, keineas-Casts zu generieren, sondern stattdessen Zod-Schemas oder Type Guards einzusetzen. - PR-Templates: Eine Checkbox „Enthält dieser PR
as-Casts? Wenn ja, Begründung für jeden einzelnen." Klingt bürokratisch, ist aber effektiv — besonders wenn die Antwort von einem AI-Tool kommt, das den Cast selbst erzeugt hat. - Review-Fokus: In Code Reviews AI-generierter PRs explizit nach
assuchen. Es ist der zuverlässigste Indikator dafür, dass das LLM den Typ nicht verstanden hat.
Als wir Monate nach der Cast-Bereinigung Copilot im Projekt aktivierten, dauerte es keine Woche, bis die CI-Pipeline die ersten as-Casts in AI-generierten Vorschlägen abfing. Das Pattern war konsistent: Immer wenn Copilot einen komplexeren generischen Typ nicht auflösen konnte, griff es zu as. In einem einzigen Sprint zählten wir elf Copilot-vorgeschlagene Casts, die von Entwicklern ungeprüft übernommen worden wären — hätte die Linting-Regel sie nicht blockiert. Die Ironie war nicht zu übersehen: Wir hatten Monate investiert, um menschliche Casts zu eliminieren, und das AI-Tool versuchte, sie schneller wieder einzuführen, als wir sie entfernen konnten. Die .cursorrules-Datei mit expliziter „no as"-Anweisung reduzierte die Trefferquote merklich — aber eliminierte sie nicht vollständig. Das CI-Gate bleibt die letzte Verteidigungslinie.
Type Safety Theater — und wie man es auditiert
Es gibt Enterprise-Codebases mit strict: true, null any-Vorkommen und 400 as-Casts. Diese Codebases haben den Anschein von Typsicherheit, ohne die Substanz. Sie haben Typsicherheit performt, ohne sie zu erreichen.
Das ist schlimmer als eine JavaScript-Codebasis mit guter Runtime-Validierung. Denn die Ingenieure glauben, dass sie sicher sind. Sie vertrauen dem Compiler. Und der Compiler vertraut den Casts. Niemand validiert tatsächlich.
Im DACH-Kontext trifft dieses Problem auf eine regulatorische Realität: Der EU Cyber Resilience Act (CRA, in Kraft seit 2024, Compliance-Deadline 2027) und die BSI-Richtlinien drängen Unternehmen zu nachweisbaren Software-Qualitätsaudits. Schweizer Finanzsektor-Teams im FINMA-Umfeld werden zunehmend aufgefordert, Typsicherheitsgarantien zu dokumentieren. as-Casts sind für diese Audits unsichtbar — sie sehen aus wie typsicherer Code, weil der Compiler keine Fehler meldet.
Ein pragmatischer Audit-Ansatz
- Quantifizieren:
as-Count pro Modul. Absolut und normalisiert (pro 1.000 LoC). - Kategorisieren: Boundary-Casts (API, JSON, Worker) vs. interne Casts. Boundary-Casts sind Priorität 1.
- Korrelieren:
as-Dichte gegen Bug-Tickets pro Modul plotten. Die Korrelation ist meist erschreckend deutlich. - Tracken:
as-Count als Metrik in CI. Nicht als Gate (zu disruptiv), sondern als Trend — er darf nur sinken, nie steigen.
Wer noUncheckedIndexedAccess oder exactOptionalPropertyTypes aktivieren möchte, sollte vorher den as-Audit durchführen. Andernfalls aktiviert man das Flag, sieht 300 Compiler-Fehler, und stellt fest, dass die Hälfte davon as-Casts sind, die genau die Fälle maskiert haben, die das Flag aufdecken sollte.
Bei einem späteren Engagement für eine Schweizer Versicherung — ein anderes Team, eine andere Codebasis, aber ein erschreckend ähnliches Bild — präsentierte das Engineering-Management stolz ihre TypeScript-Metriken: strict: true, null any, 95% Testabdeckung. Beim Audit zählten wir über vierhundert as-Casts. Die Reaktion des Lead Engineers war bezeichnend: „Aber die Casts sind doch typsicher, die haben einen konkreten Typ." Genau das ist das Missverständnis, das as so gefährlich macht. Es sieht aus wie Typsicherheit. Es fühlt sich an wie Typsicherheit. Es ist keine. Der Unterschied wird erst sichtbar, wenn man die Cast-Dichte neben die Produktionsvorfälle legt — und dann wird er sehr sichtbar.
Die zentrale These
as-Casts sind eine Steuer auf zukünftige Ingenieure, die still bezahlt wird, bis ein Produktionsvorfall ein Audit erzwingt. Sie sind kein individuelles Qualitätsproblem, sondern das Ergebnis struktureller Anreize: Compiler-Lücken, Migrationsdruck, AI-Codegen, fehlende Linting-Defaults. Die Lösung ist ebenso strukturell: Schema-Validierung an Grenzen, satisfies an Definitionsstellen, Discriminated Unions im Domain-Modell, und eine CI-Pipeline, die keinen neuen Cast durchlässt.
