Start / Seminare / Modern TypeScript 7

Modul

Architektur, Qualität und Tests

9 Kapitel in diesem Modul-Video · Laufzeit

Vertonung mit synthetischer Stimme · Inhalte redaktionell verantwortet · © 2026 HECKER CONSULTING, alle Rechte vorbehalten

Transkript

Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.

Architektur, Qualität und Tests

0:00 Dieses Modul schließt den zweiten Tag ab und bringt das Gelernte in einen größeren Zusammenhang: Wie fügen sich Typen, Grenzen und Tests zu einem Projekt zusammen, das man in zwei Jahren noch ändern kann? Und es enthält den härtesten Migrationsblocker des ganzen Seminars — ein Werkzeugthema, an dem viele Umstellungspläne im Moment tatsächlich hängen.

0:19 Der Leitsatz für heute steht auf der nächsten Folie, und er erklärt, warum Typprüfung anders behandelt werden muss als Tests: Ein fehlender Test bricht sichtbar. Eine fehlende Typprüfung bricht gar nicht.

Architektur, Qualität und Tests

0:31 Denken Sie kurz über diesen Satz nach, denn er ist der Grund für die zweite Hälfte dieses Moduls. Wenn Sie einen Test löschen, wird die Lücke früher oder später sichtbar — irgendwann bricht etwas, das der Test gefangen hätte. Wenn Sie die Typprüfung aus der Pipeline nehmen, passiert erst mal gar nichts. Alles ist grün, alles ist schneller. Und deshalb verschwindet sie so leicht.

0:53 Wir gehen von der Architektur über Tests bis zu den Toren in der Pipeline — und dazwischen liegt das Werkzeugthema, das Ihre Migrationsplanung bestimmt.

TypeScript als Architekturwerkzeug

1:02 Beginnen wir mit einer Perspektive, die vielen neu ist: Typen als Architekturwerkzeug. Es geht um Grenzen, die im Code gelten statt nur im Wiki. Denn der Unterschied zwischen einer Absprache und einer Grenze ist, dass die Grenze beim nächsten Umbau meldet, wenn sie verletzt wird. Typen können Grenzen ziehen, die sonst nur in Absprachen existieren.

1:23 Ein Beispiel: Wenn die Domäne keine Infrastrukturtypen kennt — also nichts über Datenbanken, Netzwerke oder Frameworks weiß —, dann ist das im Code sichtbar und nachprüfbar. Nicht als guter Vorsatz in einem Architekturdokument, sondern als Übersetzungsfehler, sobald jemand die Grenze überschreitet. Das ist ein qualitativer Unterschied.

1:42 Ein Architekturdokument beschreibt, wie es sein sollte. Eine Typgrenze sorgt dafür, dass es so bleibt — auch wenn niemand mehr weiß, dass es das Dokument gibt. Vier ganz konkrete Vorteile. Die Domäne lässt sich ohne Datenbank und ohne Netz testen — das ist der spürbarste, denn solche Tests laufen in Millisekunden statt Sekunden.

2:03 Eine Infrastrukturentscheidung bleibt austauschbar, statt bis in den Kern durchzuschlagen. Der Ort einer Änderung ist am Typ ablesbar, nicht am Dateinamen — Dateinamen lügen bekanntlich mit der Zeit. Und der vierte ist ein sozialer Gewinn: Neue Mitarbeitende erkennen die Struktur, ohne sie erklärt zu bekommen. Der Code selbst ist die Einführung.

2:24 Der zweite Punkt ist der verbreitetste Selbstbetrug in Architekturfragen: Ordner nach Schichten benennen, ohne dass Typen die Grenze durchsetzen. Das sieht in der Projektübersicht sauber aus und hält nichts — ein Import quer über die Schichten ist genauso leicht geschrieben wie jeder andere. Der erste Punkt ist der häufigste konkrete Fehler: Transporttypen als Domänentypen verwenden. Damit hängt Ihr Kern am Format der Gegenseite, wie wir in Modul 6 gesehen haben.

2:50 Und der dritte: die Domäne von einem Framework-Typ abhängig machen — auch nur an einer einzigen Stelle genügt.

Interfaces und Ports

2:57 Jetzt zum wichtigsten Baustein dieser Trennung. Ein Port beschreibt, was die Domäne braucht — nicht, was die Technik kann. Diese Blickrichtung klingt nach einer Feinheit und entscheidet in Wahrheit darüber, ob Ihre Austauschbarkeit echt ist oder nur behauptet. Ein Port ist eine Schnittstelle, die aus Sicht der Domäne formuliert ist.

3:17 Das Bild: Sie bestellen im Restaurant „ein Glas Wasser" — nicht „öffne den Hahn, warte drei Sekunden, stelle das Glas darunter". Was Sie brauchen, nicht wie es gemacht wird. Und jetzt der entscheidende Satz: Wird der Port aus Sicht der Infrastruktur geschnitten, schlagen deren Eigenheiten überall durch. Dann heißt eine Methode plötzlich „hole Zeile per Schlüssel" — und jeder Austausch der Technik zwingt Sie, die halbe Domäne anzufassen. Die Austauschbarkeit war dann nur behauptet.

3:46 Sehen Sie sich diese Schnittstelle an — und achten Sie darauf, was fehlt. Kein Wort über Tabellen, kein Wort über Transaktionen, kein Wort über Verbindungen. Zwei Methoden, beide in der Sprache der Werkstatt: laden und sichern. Genau daran ist die Blickrichtung erkennbar, wie die Fußzeile sagt. Die Domäne kennt ausschließlich diesen Typ — was dahinter steckt, eine Datenbank, eine Datei oder ein fremder Dienst, ist ihr gleichgültig.

4:12 Und weil es ihr gleichgültig ist, können Sie es tauschen, ohne sie anzufassen. Das ist der ganze Gewinn, und er hängt an dieser Blickrichtung. Vier Prüffragen, und die dritte ist die härteste. Kommt der Port ohne technische Begriffe aus? Hat er wenige Methoden, und hat jede einen fachlichen Anlass? Und dann: Ist ein zweiter Adapter dafür vorstellbar, ohne den Port zu ändern?

4:36 Stellen Sie sich das ernsthaft vor — eine Datei statt einer Datenbank, ein Test statt eines echten Dienstes. Wenn Sie dafür den Port anfassen müssten, ist er technikgetrieben geschnitten. Und der vierte Punkt fasst es zusammen: Der Port wird von der Domäne definiert, nicht von der Infrastruktur geliefert.

Immutability und funktionale Muster

4:55 Jetzt zu einem Thema, bei dem TypeScript weniger leistet, als viele denken — und das trotzdem lohnt. Es geht darum, Änderungen sichtbar zu machen, statt sie über den Code zu verteilen. Und um eine wichtige Einschränkung, die wir schon kennen. Unveränderliche Daten machen Nebenwirkungen sichtbar — wenn nichts heimlich geändert wird, muss jede Änderung irgendwo hingeschrieben werden.

5:18 In TypeScript wird das unterstützt, aber nicht erzwungen: Der Schreibschutz schützt die Eigenschaft beim Übersetzen, nicht das Objekt zur Laufzeit. Das ist die Typlöschung aus Modul 1, zum dritten Mal in diesem Seminar. Was bleibt, ist dennoch wertvoll: eine Aussage über Absicht, die im Review prüfbar wird. Wer gegen sie verstößt, muss es sichtbar tun — und Sichtbarkeit ist bei Architektur oft schon die halbe Miete.

5:43 Das Muster in zwei Zeilen: Statt am bestehenden Auftrag etwas zu ändern, erzeugen wir einen neuen mit dem geänderten Feld. Alles andere wird übernommen. Wer das zum ersten Mal sieht, denkt an Verschwendung — in der Praxis ist der Gewinn an Nachvollziehbarkeit den Aufwand fast immer wert, weil ein Wert, den man in der Hand hält, sich nicht mehr unter der Hand ändert.

6:03 Und die Fußzeile ruft die Einschränkung noch einmal auf: Zur Laufzeit ist das Objekt änderbar wie jedes andere. Der Schutz gilt für Menschen, die den Code lesen — nicht für Programme, die ihn ausführen. Der erste Punkt ist der gefährlichste, weil er auf einer falschen Sicherheit aufbaut: den Schreibschutz für Laufzeitschutz halten und deshalb auf Kopien verzichten.

6:24 Der zweite ist ein Klassiker, der einen ganzen Nachmittag kosten kann: flache Kopien anfertigen, wo verschachtelte Daten geändert werden — die äußere Hülle ist neu, das Innere wird geteilt, und die Änderung schlägt an einer ganz anderen Stelle durch. Und der dritte ist eine Maßfrage: Unveränderlichkeit erzwingen, wo eine große Liste sehr häufig geändert wird. Dort ist die Kopiererei tatsächlich ein Kostenfaktor.

Asynchronität und Fehlerbehandlung

6:47 Jetzt die Kombination aus Modul 3 und Modul 6, angewandt auf asynchronen Code. Der Grundgedanke: Asynchroner Code verliert seine Fehler leichter als synchroner. Und dagegen hilft genau das Werkzeug, das wir schon kennen — der Ergebnistyp. In asynchronem Code liegen die Fehlerpfade weiter auseinander als in synchronem — zwischen dem Auslösen und dem Behandeln liegt buchstäblich Zeit, und oft auch eine andere Stelle im Programm.

7:14 Und ein nicht abgewartetes Versprechen verschluckt seinen Fehler vollständig: Es passiert etwas Falsches, und niemand erfährt davon. Was hilft, ist eine Kombination: typisierte Ergebnisse, konsequentes Abwarten und eine bewusste Entscheidung zwischen Ausnahme und Ergebniswert. Diese Entscheidung haben wir in Modul 3 vorbereitet — hier wird sie besonders wertvoll, weil das Abwarten den Blick verstellt.

7:36 Beide Zweige dieser Funktion liefern denselben Typ — Erfolg und Misserfolg sind gleichberechtigte Ergebnisse. Die Ausnahme wird an der Grenze gefangen und in einen Wert übersetzt, statt weitergereicht zu werden. Und die Fußzeile nennt den Gewinn für den Aufrufer, und der ist beträchtlich: Er sieht am Typ, dass es schiefgehen kann.

7:55 Bei einer geworfenen Ausnahme sähe er gar nichts — die Signatur verschweigt sie, und man erfährt davon erst im Betrieb. Wer eine Funktion aufruft, sollte an ihrem Vertrag ablesen können, worauf er sich einstellen muss. Der erste Punkt ist der teuerste Fehler in asynchronem Code überhaupt: ein Versprechen nicht abwarten und den Fehler damit unsichtbar machen.

8:16 Es passiert leicht, weil der Code funktioniert — nur eben still falsch. Der zweite ist ein Leistungsthema: in einer Schleife nacheinander warten, wo nebenläufig gearbeitet werden könnte; das macht aus zehn parallelen Aufrufen eine zehnfach lange Wartezeit. Und der dritte knüpft an Modul 3 an: Nach dem Abwarten ist die vorher erreichte Typverengung nicht mehr gültig, weil dazwischen etwas passiert sein kann.

Typisierte Tests und Fixtures

8:39 Jetzt zu den Tests — und gleich mit einer Haltungsfrage: Tests sind Produktionscode mit anderem Zweck. Wer sie als Wegwerfcode behandelt, bekommt Wegwerfqualität. Und nirgends zeigt sich das deutlicher als bei den Testdaten. Ein Testdatensatz ohne Typ ist ein Datensatz, der beim nächsten Modellumbau stillschweigend falsch wird. Er bleibt grün — er prüft nur eben ein Modell, das es nicht mehr gibt.

9:04 Das ist eine der unangenehmsten Fehlerarten überhaupt, weil sie das Vertrauen in die Testsuite untergräbt, ohne sich zu zeigen. Ein typisierter Testdatensatz bricht stattdessen den Übersetzungslauf — an genau der Stelle, an der jemand das Modell geändert und den Test vergessen hat. Aus einem stillen Fehler wird eine Arbeitsanweisung.

9:23 Der ganze Unterschied steckt in der Typangabe hinter dem Namen. Mit ihr hängt dieser Testdatensatz am Modell: Kommt ein Pflichtfeld hinzu, bricht die Stelle. Ohne sie ist es ein beliebiges Objekt, das zufällig passende Feldnamen hat. Die Fußzeile bringt die Folge auf den Punkt: Ohne die Annotation bliebe der Test grün und prüfte ein Modell, das es nicht mehr gibt.

9:44 Das ist übrigens einer der wenigen Fälle, in denen ich zu einer Annotation rate, obwohl der Compiler auch ableiten könnte — hier ist die Annotation der Test. Vier Wirkungen, und die erste ist die wichtigste: Sie halten die Testdaten am Modell, ohne dass jemand daran denken muss. Alles, woran gedacht werden muss, wird irgendwann vergessen.

10:04 Zweitens dokumentieren sie, welche Felder fachlich zusammengehören — ein guter Testdatensatz ist ein Beispiel für ein gültiges Objekt. Drittens machen sie Testhilfen wiederverwendbar, statt dass jeder sich seine kopiert. Und viertens verhindern sie Tests, die auf einem veralteten Modell grün bleiben — die schlimmste Sorte, weil sie Sicherheit vortäuscht.

Type-level Tests und Runtime-Tests

10:25 Jetzt eine Unterscheidung, die in der Praxis oft verschwimmt: zwei Testarten, die zwei verschiedene Fragen beantworten. Und die Pointe ist, dass keine die andere ersetzen kann — auch wenn beide „Test" heißen. Ein Test auf Typebene stellt sicher, dass eine falsche Verwendung gar nicht erst übersetzt. Er sagt nichts darüber, ob die Funktion richtig rechnet.

10:48 Ein gewöhnlicher Test sagt genau das — und nichts darüber, ob jemand die Funktion falsch aufrufen könnte. Zwei Fragen: Ist es möglich? Ist es richtig? Beide werden gebraucht, und beide zu haben ist billiger, als es klingt. Ein Bild: Der eine prüft, ob der Stecker überhaupt in die Dose passt. Der andere, ob das Gerät danach das Richtige tut.

11:10 Die dritte Zeile ist die praktisch interessanteste. Bei einem Modellumbau bricht der Test auf Typebene zuverlässig, und zwar beim Übersetzen. Der gewöhnliche Test bricht nur, wenn es zufällig auffällt — wenn die Änderung eben gerade das berührt, was er prüft. Die letzte Zeile nennt die Kosten, und die sind für beide gering: einmal Übersetzungszeit, einmal Laufzeit im Testlauf.

11:32 Es gibt also wenig Grund, sich zwischen ihnen zu entscheiden. Der häufigste Grund ist, dass die eine Sorte schlicht nicht bekannt ist. Oben ein Test auf Typebene — und beachten Sie, dass hier die Kommentarzeile der eigentliche Test ist. Sie sagt: Die nächste Zeile muss einen Fehler erzeugen. Tut sie es nicht, meldet sich der Compiler über den ausgebliebenen Fehler. Ein Test, der prüft, dass etwas nicht geht.

11:58 Unten der gewöhnliche Test: Rechnet die Summenfunktion richtig? Zwei Zeilen, zwei Fragen, und zusammen decken sie ab, was einzeln jeweils offenbliebe. Diese Kombination ist besonders wertvoll bei öffentlichen Schnittstellen, wo beides zugesagt ist.

Typbezogenes Linting unter TypeScript 7

12:14 Und jetzt der härteste Blocker der ganzen Migration. Ich behandle das bewusst nicht als Werkzeugliste mit Häkchen — die wäre in drei Monaten falsch. Es geht um eine Strukturfrage, und die trägt: Welche Werkzeuge brauchen den Compiler als Bibliothek? Lint-Regeln, die Typinformation auswerten — also nicht nur die Syntax ansehen, sondern wissen wollen, welchen Typ ein Ausdruck hat —, greifen programmatisch auf den Compiler zu.

12:40 Und genau diese Schnittstelle liefert TypeScript 7.0 nicht mit; sie ist für 7.1 vorgesehen. Die Konsequenz ist unbequem und klar: Wer typbezogen lintet, kann den Compiler nicht einfach tauschen. Unabhängig davon, wie sauber der eigene Code ist. Das ist der Punkt, an dem viele Migrationspläne gerade stehen — und es ist keine Frage des Aufwands, sondern eine des Wartens.

13:03 Diese eine Frage entscheidet alles: Braucht das Werkzeug den Compiler als Bibliothek? Wenn ja — weil es den Syntaxbaum oder die Typen liest, oder weil es eingebettete Sprachen verarbeitet wie in Vue, Svelte, Astro oder MDX —, dann wartet es auf 7.1. Wenn nein — weil es einen eigenen Parser mitbringt wie Formatierer und Bündler, oder weil es schlicht den Compilerbefehl aufruft —, dann ist es nicht betroffen.

13:27 Und die Fußzeile gibt Ihnen die praktische Prüfmethode: Steht TypeScript in den Abhängigkeiten des Werkzeugs? Dann betroffen. Prüfen Sie es selbst nach, der Stand ändert sich. Und hier der Ausweg für die Zwischenzeit: Beide Compilerversionen laufen nebeneinander im selben Projekt. Der gewohnte Name zeigt per Alias auf das Kompatibilitätspaket — dorthin greifen die Werkzeuge, wenn sie den Compiler importieren.

13:52 Der neue Compiler steht unter einem eigenen Namen daneben und prüft. So bekommen Sie die schnelle Prüfung, ohne Ihre Lint-Regeln zu verlieren. Das ist eine Zwischenlösung, kein Zielzustand — aber eine, die funktioniert und die Ihnen erlaubt, jetzt anzufangen statt zu warten. Der erste Punkt ist der Reflex, den ich Ihnen ausreden möchte: die Abhängigkeitsprüfung mit einem Erzwingen-Schalter übergehen.

14:16 Die fehlende Schnittstelle entsteht dadurch nicht — Sie schalten nur die Warnung ab und bekommen den Fehler später und unverständlicher. Der zweite: den Parallelbetrieb zum Dauerzustand machen, statt ihn abzunehmen; jede Zwischenlösung braucht ein Ablaufdatum. Und der dritte ist eine Frage der Ehrlichkeit nach oben: Sagen Sie keinen Termin für 7.1 zu. Vorgesehen ist sie — datiert ist sie nicht.

Quality Gates in der Pipeline

14:40 Zum Abschluss die Pipeline: fünf Tore, und eines davon fehlt erfahrungsgemäß immer zuerst. Sie ahnen vermutlich schon, welches — und der Grund dafür ist der Leitsatz vom Anfang dieses Moduls. Sehen Sie sich die rechte Spalte an, dort steht die eigentliche Aussage. Bei Tests und Build steht „niemand" — die fallen nicht aus, weil sie sichtbar brechen.

15:02 Wenn der Build kaputt ist, merkt es jeder sofort. Bei der Typprüfung steht dagegen: fällt aus, wenn sie im Build-Schritt versteckt ist. Beim Linting: wenn die Werkzeuglage sie blockiert — genau unser Thema von eben. Und bei den Abhängigkeiten: wenn die Meldung zur Gewohnheit wird. Drei von fünf Toren können lautlos verschwinden. Das sind die drei, auf die Sie achten müssen.

15:25 Fünf Regeln, und der rote Faden lautet: Jedes Tor muss einzeln sichtbar sein. Eigenes Skript mit eigenem Namen. Die Pipeline ruft sie einzeln, nicht als Kette in einem Aufruf — sonst sehen Sie nur, dass irgendetwas fehlgeschlagen ist. Ein rotes Tor blockiert, sonst ist es kein Tor, sondern ein Hinweis. Blockierte Tore werden benannt, statt stillschweigend entfernt.

15:47 Und der letzte Punkt entscheidet über die Akzeptanz im Team: Derselbe Befehl muss lokal laufen wie in der Pipeline. Was lokal nicht läuft, wird als Schikane empfunden — und dann sucht jemand einen Weg daran vorbei. Der erste Punkt ist der, um den sich dieses ganze Modul dreht: die Typprüfung im Build-Schritt verstecken und sie dort verlieren.

16:08 Sobald ein schnelleres Werkzeug den Build übernimmt, ist sie weg — und niemandem fällt es auf, weil alles grün bleibt. Der zweite ist eine Frage der Buchführung: ein Tor abschalten, weil die Werkzeuglage klemmt, ohne es zu vermerken. Dann wird die Zwischenlösung zum Zustand. Und der dritte betrifft die Sicherheit: Meldungen so oft durchwinken, bis sie niemand mehr liest. Ein Tor, das immer offen steht, ist eine Tür.

Übung — absichern, dann refaktorieren

16:33 Zum Abschluss des Tages eine Übung mit einer klaren Reihenfolge: erst das Netz, dann der Umbau. Diese Reihenfolge ist nicht verhandelbar — und wer sie schon einmal umgedreht hat, weiß auch, warum. Das Lernziel: bestehendes Verhalten mit Tests festhalten und anschließend typsicher umbauen, ohne das Verhalten zu verändern.

16:53 Erfolgreich sind Sie unter drei Bedingungen: Die Tests waren vor dem Umbau grün, sie sind es danach unverändert, und mindestens eine Grenze ist jetzt durch einen Port beschrieben. Die Abrechnung ist bewusst so geschnitten, dass sie Datum, Geld und Speicherung vermischt — dort sind drei Grenzen zu finden. Wer früh fertig ist, ergänzt einen Test auf Typebene für eine Verwendung, die künftig nicht mehr möglich sein soll.

17:17 Schritt eins ist nicht verhandelbar, wie die Fußzeile sagt: Verhalten mit Tests festhalten, ohne eine Zeile zu ändern. Wer ohne Netz umbaut, hat am Ende keinen Vergleichsmaßstab und weiß nicht, ob er etwas kaputt gemacht hat. Danach benennen Sie die Grenzen — was ist Domäne, was Infrastruktur? — und schneiden einen Port aus Sicht der Domäne. Dann bauen Sie in kleinen Schritten um, mit Tests nach jedem Schritt.

17:41 Und zum Schluss prüfen Sie, welche Abhängigkeit jetzt tatsächlich austauschbar ist. Das ist die Probe auf die Blickrichtung aus Kapitel 2. Damit endet der zweite Tag. Vier Sätze zum Mitnehmen: Ein Port beschreibt, was die Domäne braucht — nicht, was die Technik kann. Tests auf Typebene und gewöhnliche Tests beantworten verschiedene Fragen und ersetzen einander nicht.

18:04 Typbezogenes Linting braucht die Compiler-Schnittstelle und wartet auf 7.1 — das ist der Blocker, den Sie in Ihrer Planung berücksichtigen müssen. Und die Typprüfung gehört als eigenes Tor in die Pipeline, nicht in den Build. Morgen wird daraus ein Plan: Wie migriert man ein bestehendes Projekt auf TypeScript 7?

Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →