TypeScript-Code-Reviews: Worauf erfahrene Teams achten
<T>
type
TypeScript · Code Review · Type Safety · Team-Praxis
TypeScript-Code-Reviews: Worauf erfahrene Teams achten
Red Flags erkennen, ohne die Velocity zu bremsen

Ein TypeScript-Review, das nur auf Syntax achtet, übersieht die eigentlichen Risiken: any-Creep, unnötige as-Assertions, unlesbare Generic-Konstruktionen und Non-Null Assertions, die echte Fehlerquellen maskieren. Dieser Artikel zeigt die wichtigsten Red Flags erfahrener Reviewer, eine praktische Checkliste und einen Weg, Typsicherheit und Review-Tempo im Gleichgewicht zu halten.

11 Min. Lesezeit any-Creep · as-Assertions · Generics ESLint · tsc --noEmit · Pull Requests

1. Warum TypeScript-Reviews mehr sind als Syntax-Checks

Ein TypeScript-Code-Review, das sich auf Formatierung, Namenskonventionen und offensichtliche Bugs beschränkt, verpasst den eigentlichen Wert des Typsystems: TypeScript verspricht, ganze Fehlerklassen bereits zur Compile-Zeit auszuschließen, aber dieses Versprechen gilt nur, wenn der Code das Typsystem auch tatsächlich nutzt, statt es zu umgehen. Erfahrene Reviewer prüfen deshalb nicht nur, ob der Code kompiliert, sondern ob die Typen die reale Domäne korrekt abbilden und ob an kritischen Stellen bewusst oder unbewusst Typsicherheit geopfert wurde.

Der Unterschied zwischen einem oberflächlichen und einem gründlichen Review zeigt sich meist an denselben wiederkehrenden Mustern: any an Stellen, die eigentlich präzise typisierbar wären, as-Assertions, die den Compiler zum Schweigen bringen statt ein echtes Problem zu lösen, und Non-Null Assertions, die eine tatsächlich mögliche Null-Situation einfach wegdefinieren. Diese Muster sind nicht immer falsch, aber sie verdienen im Review eine bewusste Nachfrage, keine automatische Zustimmung.

2. any-Creep: Der schleichende Verlust der Typsicherheit

any-Creep beschreibt, wie sich any unbemerkt durch eine Codebasis ausbreitet: Eine Funktion bekommt einen any-Parameter, weil ein externer Typ gerade nicht verfügbar ist, der Rückgabewert einer anderen Funktion wird dadurch ebenfalls implizit any, und jeder nachfolgende Aufruf verliert stillschweigend seine Typprüfung. Das Tückische daran: Der Compiler meldet keinen einzigen Fehler, weil any mit jedem anderen Typ kompatibel ist. Der Codeausschnitt sieht fertig aus, verhält sich aber wie reines JavaScript ohne jede Absicherung.

Im Review lohnt sich die gezielte Suche nach any in Funktionssignaturen, in Rückgabetypen und in Type-Assertion-Ketten wie (value as any) as SpecificType, einem klaren Signal, dass der eigentliche Typfehler umgangen statt gelöst wurde. unknown ist in fast jedem Fall, in dem der konkrete Typ zur Compile-Zeit nicht feststeht, die sicherere Alternative, weil es den Aufrufer zu einer expliziten Typprüfung zwingt, bevor der Wert verwendet werden darf. Ein aktiviertes noImplicitAny in der tsconfig.json verhindert zumindest die unbeabsichtigten Fälle.


// RED FLAG: any spreads silently through the call chain
function parseApiResponse(raw: any) {
  return raw.data.items; // no error, even if "data" does not exist
}

function loadProducts(json: string) {
  const parsed = JSON.parse(json); // implicitly "any"
  return parseApiResponse(parsed); // type safety already lost here
}

// BETTER: unknown forces an explicit check before use
interface ApiResponse {
  data: { items: Product[] };
}

function isApiResponse(value: unknown): value is ApiResponse {
  return (
    typeof value === "object" &&
    value !== null &&
    "data" in value
  );
}

function parseApiResponseSafe(raw: unknown): Product[] {
  if (!isApiResponse(raw)) {
    throw new Error("Unexpected API response shape");
  }
  return raw.data.items; // fully typed from here on
}

3. Unnötige Type Assertions mit as

Eine as-Assertion sagt dem Compiler: "Vertrau mir, ich weiß es besser." Das ist manchmal berechtigt, etwa beim Verarbeiten von DOM-APIs, die TypeScript nicht präzise genug typisieren kann, aber in vielen Pull Requests wird as stattdessen benutzt, um einen echten Typfehler zum Schweigen zu bringen, den man eigentlich lösen sollte. Der Reviewer-Reflex sollte bei jeder Assertion lauten: Wäre das auch ohne as lösbar, etwa durch eine korrekte Typdefinition, einen Type Guard oder eine kleine Refaktorierung der Datenstruktur?

Besonders riskant sind Assertions, die einen Typ verengen, ohne dass eine Laufzeitprüfung stattfindet, etwa const user = data as User ohne vorherige Validierung. Wenn data zur Laufzeit nicht der Form von User entspricht, produziert der Code keinen Compile-Fehler, sondern einen Laufzeitfehler an einer völlig anderen Stelle, oft erst mehrere Funktionsaufrufe später. Ein Review-Kommentar wie "Woher wissen wir, dass das zur Laufzeit stimmt?" deckt diese Fälle zuverlässig auf und führt meist zu einem robusteren Type Guard statt einer Assertion.


// RED FLAG: assertion without runtime validation
function getUserFromCache(key: string): User {
  const raw = cache.get(key);
  return raw as User; // compiles fine, may explode later at runtime
}

// BETTER: validate before narrowing, throw with context if invalid
function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "email" in value
  );
}

function getUserFromCacheSafe(key: string): User {
  const raw = cache.get(key);
  if (!isUser(raw)) {
    throw new Error(`Cache entry for "${key}" is not a valid User`);
  }
  return raw; // narrowed by the type guard, no assertion needed
}

4. Generic-Gymnastik: Wenn Typen die Lesbarkeit zerstören

Generics sind ein mächtiges Werkzeug, aber ein Review-Red-Flag entsteht, sobald ein Type-Parameter-Konstrukt mehr Zeit zum Verstehen braucht als die eigentliche Funktionslogik. Verschachtelte konditionale Typen, mehrere ineinander verschachtelte infer-Klauseln oder Generics mit fünf oder mehr Type-Parametern sind typische Anzeichen dafür, dass die Abstraktion den praktischen Nutzen überholt hat. Der Test, den erfahrene Reviewer anwenden: Kann ein neues Teammitglied die Signatur in unter einer Minute verstehen, ohne den Implementierungscode zu lesen?

Oft lässt sich dieselbe Typsicherheit mit einer einfacheren Struktur erreichen, etwa durch Aufteilen einer übermäßig generischen Funktion in zwei spezifischere Varianten, durch benannte Hilfstypen statt Inline-Konstrukten, oder durch den Verzicht auf einen Type-Parameter, der ohnehin nur einen einzigen konkreten Typ im gesamten Projekt annimmt. Komplexe Generics gehören, wenn sie wirklich nötig sind, in gut dokumentierte, zentrale Utility-Typen, nicht verstreut in einzelne Feature-Module, wo sie bei jedem Review neu entschlüsselt werden müssen.


// RED FLAG: deeply nested conditional types, hard to read at a glance
type DeepPartialExtract<T, K> = T extends object
  ? K extends keyof T
    ? { [P in K]: T[P] extends Array<infer U> ? DeepPartialExtract<U, keyof U>[] : T[P] }
    : never
  : never;

// BETTER: split into named, documented steps
type ArrayElement<T> = T extends Array<infer U> ? U : never;

/** Extracts a single top-level property while keeping array element types intact. */
type ExtractField<T, K extends keyof T> = {
  [P in K]: T[P] extends unknown[] ? ArrayElement<T[P]>[] : T[P];
};

// Usage stays simple and self-explanatory for reviewers and newcomers
type ProductSummary = ExtractField<Product, "id" | "variants">;

5. Non-Null Assertions und maskierte Null-Checks

Das Ausrufezeichen bei value!.property sagt dem Compiler, dass value zur Laufzeit garantiert nicht null oder undefined ist. In vielen Codebasen wird diese Non-Null Assertion jedoch dort eingesetzt, wo diese Garantie tatsächlich nicht besteht, meist weil eine Fehlermeldung an dieser Stelle unbequem wäre. Das Ergebnis ist ein klassischer stiller Laufzeitfehler: Cannot read properties of undefined, an einer Stelle, an der TypeScript den Entwickler eigentlich gewarnt hätte, wäre die Assertion nicht dagewesen.

Ein besonders häufiges Muster in React- oder Alpine.js-nahem Code: document.getElementById("cart")!.classList.add("open"), obwohl das Element je nach Seitenzustand durchaus fehlen kann. Reviewer sollten bei jeder Non-Null Assertion nachfragen, ob der Fall tatsächlich unmöglich ist oder nur unwahrscheinlich, und im letzteren Fall auf einen expliziten Guard mit sinnvoller Fehlermeldung bestehen. Optional Chaining ?. kombiniert mit dem Nullish-Coalescing-Operator ?? löst die meisten dieser Fälle ohne eine einzige Assertion.


// RED FLAG: non-null assertion masks a genuinely possible null case
function openCart() {
  document.getElementById("cart")!.classList.add("open"); // silent crash risk
}

function getFirstVariant(product: Product) {
  return product.variants![0].sku; // assumes variants is never empty
}

// BETTER: explicit guard with a meaningful error, or safe fallback
function openCartSafe() {
  const cartElement = document.getElementById("cart");
  if (!cartElement) {
    console.warn("Cart element not found, skipping open()");
    return;
  }
  cartElement.classList.add("open");
}

function getFirstVariantSafe(product: Product): string | undefined {
  return product.variants?.[0]?.sku ?? undefined;
}

6. Die praktische Review-Checkliste für TypeScript-PRs

Eine schriftliche Checkliste macht Reviews konsistenter, weil sie unabhängig von der Tagesform des Reviewers dieselben kritischen Punkte abdeckt. Sinnvoll ist eine kurze Liste, die direkt in die PR-Vorlage oder ins Team-Wiki gehört: Enthält der Diff neue any-Vorkommen ohne Kommentar, der sie begründet? Sind neue as-Assertions durch einen Kommentar oder einen vorgelagerten Type Guard abgesichert? Überschreitet ein Generic-Konstrukt zwei Type-Parameter, ohne dass ein Kommentar den Zweck erklärt? Gibt es Non-Null Assertions an Stellen, die nicht offensichtlich unmöglich sind?

Ergänzend gehören API-Grenzen auf die Liste: Werden externe Daten, etwa aus einem Fetch-Aufruf oder einer Formular-Eingabe, vor der Verwendung tatsächlich validiert, oder wird ihnen blind ein Typ zugewiesen? Und schließlich die Konsistenzfrage: Folgt der neue Code denselben Typ-Patterns wie der Rest der Codebasis, etwa bei der Verwendung von satisfies für Konfigurationsobjekte oder bei der Struktur von Error-Types? Eine solche Checkliste sollte kurz bleiben, maximal sechs bis acht Punkte, sonst wird sie im Alltag ignoriert.

7. Automatisierung: ESLint, tsc --noEmit und CI-Gates

Viele der genannten Red Flags lassen sich automatisiert erkennen, bevor ein Mensch überhaupt in den Diff schaut. @typescript-eslint/no-explicit-any markiert neue any-Vorkommen, @typescript-eslint/no-non-null-assertion erzwingt eine bewusste Ausnahme-Regel für jedes !, und @typescript-eslint/consistent-type-assertions schränkt ein, wo as überhaupt erlaubt ist. Diese Regeln im CI als Gate zu erzwingen, statt sie nur als Editor-Warnung anzuzeigen, verhindert, dass Red Flags überhaupt erst zur Review-Diskussion werden.

Ein tsc --noEmit-Schritt in der Pipeline stellt sicher, dass kein PR gemergt wird, der neue Typfehler einführt, unabhängig davon, ob ein menschlicher Reviewer sie bemerkt hätte. Für Regeln, die sich nicht sauber automatisieren lassen, etwa die Lesbarkeit komplexer Generics, bleibt der menschliche Review unersetzlich, aber die Automatisierung reduziert die Menge an mechanischer Prüfung, die ein Mensch überhaupt noch leisten muss, spürbar.


# package.json script section: fail fast, cheapest checks first
# "typecheck": "tsc --noEmit",
# "lint": "eslint . --max-warnings=0",

# CI pipeline step: block merge on type errors or lint violations
npm run typecheck && npm run lint

# Local pre-commit hook via lint-staged, catches issues before push
npx lint-staged --config '{
  "*.ts,*.tsx": ["eslint --fix", "tsc-files --noEmit"]
}'

8. Review-Kommunikation: Kritik am Code, nicht an der Person

Die technisch korrekteste Checkliste nützt wenig, wenn Review-Kommentare als persönlicher Angriff ankommen und Entwickler beginnen, Assertions und any einzusetzen, um Diskussionen zu vermeiden, statt um ein echtes Problem zu lösen. Formulierungen wie "Diese Assertion könnte hier zur Laufzeit fehlschlagen, wollen wir stattdessen einen Guard einbauen?" statt "Das ist falsch" öffnen eine Diskussion, statt sie zu schließen, und führen erfahrungsgemäß zu besseren, nicht nur konformeren Lösungen.

Gleichzeitig sollte ein Reviewer nicht jede stilistische Präferenz zur Blockade machen. Ein Red Flag wie unvalidiertes any an einer API-Grenze rechtfertigt ein "Request Changes", eine etwas unübliche, aber korrekte Generic-Formulierung eher einen optionalen Kommentar. Diese Abstufung, konsequent angewendet, ist der eigentliche Unterschied zwischen einem Review-Prozess, der Qualität sichert, und einem, der nur Reibung erzeugt.

Mironsoft

TypeScript-Reviews, Type-Safety-Audits und CI-Gates für Frontend-Teams

TypeScript-Reviews auf ein neues Niveau heben?

Wir analysieren eure bestehende Codebasis auf any-Creep, riskante Assertions und komplexe Generics, richten passende ESLint-Regeln und CI-Gates ein und schulen euer Team im praktischen TypeScript-Review.

Type-Safety-Audit

any-Creep, unsichere Assertions und Lücken systematisch aufspüren

ESLint- und CI-Setup

Regeln und Gates passend zum Team-Tempo konfigurieren

Review-Coaching

Checklisten und Kommunikationsmuster fürs Entwicklerteam

9. Red Flags im direkten Vergleich

Die folgende Übersicht fasst die wichtigsten TypeScript-Review-Red-Flags zusammen: das problematische Muster, warum es riskant ist, und die bevorzugte Alternative, auf die ein Review hinwirken sollte.

Red Flag Risiko Bevorzugte Alternative
any-Parameter Typprüfung entfällt für die gesamte Aufrufkette unknown mit Type Guard
as ohne Validierung Laufzeitfehler statt Compile-Fehler Type Guard vor der Verengung
Verschachtelte konditionale Typen Signatur unter einer Minute nicht verständlich Benannte, dokumentierte Hilfstypen
Non-Null Assertion (!) "Cannot read properties of undefined" Optional Chaining plus Guard
Unvalidierte API-Antwort Falsche Annahme über externe Datenform Schema-Validierung an der Grenze

Kein einzelnes Muster in dieser Tabelle ist per se verboten, jedes hat legitime Ausnahmefälle. Entscheidend im Review ist nicht das automatische Ablehnen, sondern die bewusste Nachfrage: Ist diese Stelle wirklich eine Ausnahme, oder wurde hier eine Abkürzung genommen, die später zu einem echten Fehler führt?

10. Zusammenfassung

Gute TypeScript-Code-Reviews gehen über Formatierung und offensichtliche Bugs hinaus und prüfen gezielt, ob das Typsystem tatsächlich genutzt oder nur umgangen wird. any-Creep, unnötige as-Assertions, überkomplexe Generics und Non-Null Assertions an unsicheren Stellen sind die wiederkehrenden Muster, die erfahrene Reviewer zuverlässig erkennen und gezielt hinterfragen, statt sie automatisch durchzuwinken.

Eine kurze, schriftliche Checkliste sorgt für Konsistenz zwischen verschiedenen Reviewern, während ESLint-Regeln und ein tsc --noEmit-Gate in der CI viele Fälle bereits automatisch abfangen, bevor ein Mensch überhaupt reviewen muss. Der entscheidende Erfolgsfaktor bleibt aber die Kommunikation: Reviews, die Probleme benennen statt Personen zu kritisieren, führen zu robusterem Code, ohne die Team-Velocity durch endlose Diskussionen über jede stilistische Kleinigkeit auszubremsen.

TypeScript-Code-Reviews - Das Wichtigste auf einen Blick

any-Creep stoppen

unknown statt any an API-Grenzen, noImplicitAny in der tsconfig aktivieren.

Assertions hinterfragen

Jedes as und jedes ! verdient die Nachfrage, ob eine Laufzeitprüfung fehlt.

Generics vereinfachen

Signaturen müssen in unter einer Minute verständlich sein, sonst aufteilen.

Automatisieren, wo möglich

ESLint-Regeln und tsc --noEmit als CI-Gate entlasten den menschlichen Review.

11. FAQ: TypeScript-Code-Reviews

1Was ist any-Creep und warum ist es gefährlich?
any breitet sich unbemerkt durch die Codebasis aus, weil Funktionen mit any-Parametern implizit auch ihre Rückgabetypen zu any machen, ohne dass der Compiler einen Fehler meldet.
2Wann ist eine Type Assertion mit as gerechtfertigt?
Wenn TypeScript einen Typ nicht präzise genug herleiten kann, die Korrektheit aber anderweitig sichergestellt ist. Bei externen, unvalidierten Daten ist ein Type Guard vorzuziehen.
3Wie erkenne ich zu komplexe Generics im Review?
Test: Signatur in unter einer Minute verstehbar, ohne die Implementierung zu lesen? Verschachtelte infer-Klauseln oder viele Type-Parameter deuten auf zu viel Abstraktion hin.
4Warum sind Non-Null Assertions riskant?
Sie unterdrücken eine echte Compiler-Warnung ohne Laufzeitgarantie. Trifft die Annahme nicht zu, entsteht ein Laufzeitfehler, oft weit von der eigentlichen Ursache entfernt.
5Was gehört auf eine gute TypeScript-Review-Checkliste?
Neue any-Vorkommen, unbegründete as-Assertions, riskante Non-Null Assertions, unvalidierte externe Daten und Konsistenz mit bestehenden Typ-Patterns. Kurz halten, maximal sechs bis acht Punkte.
6Wie automatisiere ich die Erkennung von any und Assertions?
ESLint-Regeln wie no-explicit-any, no-non-null-assertion und consistent-type-assertions aus @typescript-eslint, als CI-Gate konfiguriert.
7Wie viel Zeit sollte ein TypeScript-Review pro PR kosten?
Keine feste Zahl, aber automatisierte CI-Checks sollten den mechanischen Anteil übernehmen, damit sich der Mensch auf Domänenlogik und komplexe Typkonstruktionen konzentrieren kann.
8Wie vermeide ich, dass Reviews zur reinen Bremse werden?
Klar zwischen blockierenden Red Flags und optionalen Stilfragen unterscheiden. Nur echte Risiken rechtfertigen ein Request Changes.
9Ist unknown immer besser als any?
Für die meisten Fälle ja, da unknown eine explizite Prüfung erzwingt. In seltenen Fällen mit bewusst dynamischem Code kann any mit erklärendem Kommentar sinnvoller sein.
10Wie geht man im Review-Kommentar konstruktiv mit Red Flags um?
Fragen statt Behauptungen formulieren und eine konkrete Alternative wie einen Type Guard vorschlagen. Das öffnet eine Diskussion, statt als persönliche Kritik zu wirken.