Das Ziel ist es zu zeigen, wo TS die Illusion von Sicherheit vermittelt, sodass Sie Fehler erhalten können, während das Programm ausgeführt wird.Wir werden nicht über Bugs sprechen, in TS gibt es genug1.500 offene Bugs und 6.000 geschlossene ('is: issue is: open label: Bug').Alle Beispiele werden betrachtet mit:- Der strikte TS- Modus ist aktiviert (schrieb einen Artikel, während er verstand)
- Ohne explizites "any": "as any", "Objects", "Function", {[key: string]: unknown}
- Ohne implizites "any": (noImplicitAny): untypisierte Importe (reine JS-Dateien), falsche Typinferenz
- Ohne falsche Vermutungen über Typen: Antwort vom Server, Eingabe von Bibliotheken von Drittanbietern
Inhalt:- Einführung
- Nominaltypen, benutzerdefinierte Typen - wenn die Dinge gleich, aber so unterschiedlich erscheinen
- Typvarianz, genaue Typen - über die Beziehung zwischen Typen
- Verfeinerung ungültig machen - über Vertrauen sprechen
- Ausnahmen - lohnt es sich zuzugeben, wenn man durcheinander ist?
- Unsichere Operationen - Vertrauen ist nicht immer gut
- Bonusfälle - Typprüfung in der PR-Überprüfungsphase
- Fazit
Einführung
Ist es schwierig, eine Funktion zum Hinzufügen von zwei Zahlen in JS zu schreiben? Nehmen Sie eine naive Implementierungfunction sum(a, b) {
return a + b;
}
Lassen Sie uns unsere Implementierung von `sum (2, 2) === 4` überprüfen. Scheint alles zu funktionieren? Nicht wirklich, wenn wir eine Funktion beschreiben, sollten wir über alle Arten von Eingabewerten nachdenken sowie darüber, was die Funktion zurückgeben kann1.1 + 2.7
NaN + 2
99999999999999992 + 99999999999999992
2n + 2
{} + true
2 + '2'
Solidität ist die Fähigkeit des Analysators, zu beweisen, dass während der Ausführung des Programms keine Fehler vorliegen. Wenn das Programm vom Analysegerät akzeptiert wurde, ist es garantiert sicher.Sicheres Programm ist ein Programm, das für immer ohne Fehler arbeiten kann. Jene. Das Programm wird nicht abstürzen oder Fehler auslösen.Richtiges Programm - ein Programm, das tut, was es sollte und nicht tut, was es nicht sollte. Die Richtigkeit hängt von der Ausführung der Geschäftslogik ab.Typen können beweisen, dass das Programm als Ganzes sicher ist, und Tests, dass das Programm nur innerhalb der Testdaten sicher und korrekt ist (100% Abdeckung, das Fehlen von „Mutanten“ von Stryker, Bestehen eines eigenschaftsbasierten Tests usw. können nichts beweisen, und Lizenzen reduziert Risiken). Es gibt Legenden, nach denen Theorembeweiser die Richtigkeit des Programms beweisen können.Es ist wichtig, die TS-Philosophie zu verstehen, zu verstehen, was das Tool zu lösen versucht und was wichtig ist, was es nicht zu lösen versucht.Ein Hinweis zu SoundnessTS überspringt einige Vorgänge, die bei der Kompilierung nicht sicher sind. Orte mit schlechtem Verhalten wurden sorgfältig durchdacht.EntwurfszieleNicht das Ziel für TS - Um ein Typensystem mit einer Sicherheitsgarantie zu erstellen, konzentrieren Sie sich stattdessen auf das Gleichgewicht zwischen Sicherheit und Produktivität.Beispielstruktur:Das Problem ist kein sicheres Verhalten. Die Liste ist möglicherweise nicht vollständig. Dies habe ich in Artikeln, Berichten und TS Git Probleme.Der Vorschlag ist eine TS-Ausgabe, die vor drei bis vier Jahren geöffnet wurde und eine Reihe von Kommentaren und interessanten Erklärungen der Autoren enthält.Tipp - IMHO des Autors, was der Autor als bewährte Verfahren ansiehtStrukturelle vs nominelle Typisierung
Strukturelle vs. nominelle Typisierung 1. Problem
Strukturelle Typisierung - Beim Vergleich von Typen werden deren Namen oder Deklarationsorte nicht berücksichtigt, und Typen werden gemäß der "Struktur" verglichen.Wir möchten den Brief "sendEmail" an die richtige Adresse "ValidatedEmail" senden. Es gibt eine Funktion zum Überprüfen der Adresse "validateEmail", die die richtige Adresse "ValidatedEmail" zurückgibt. Leider können Sie mit TS eine beliebige Zeichenfolge an "sendEmail" senden, da `ValidatedEmail` für TS unterscheidet sich nicht von` string`type ValidatedEmail = string;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail("asdf@gmail.com"));
// Should be error!
sendEmail("asdf@gmail.com");
Strukturelle vs nominelle Typisierung 1. Angebot
github.com/microsoft/TypeScript/issues/202Geben Sie das Schlüsselwort "nominal" ein, damit die Typen "Nominal" aktiviert werden. Jetzt können wir verbieten, nur "string" zu übergeben, wo "ValidatedEmail" erwartet wirdnominal type ValidatedEmail = string;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail('asdf@gmail.com'));
// Error!
sendEmail('asdf@gmail.com');
Strukturelle oder nominelle Eingabe 1. Tipp
Wir können einen "undurchsichtigen" Typ erstellen, der etwas "T" nimmt und ihm Einzigartigkeit verleiht, indem wir ihn mit einem Typ kombinieren, der aus dem übergebenen "K" erstellt wurde. `K` kann entweder ein eindeutiges Symbol (` eindeutiges Symbol`) oder eine Zeichenfolge sein (dann muss sichergestellt werden, dass diese Zeichenfolgen eindeutig sind).type Opaque<K extends symbol | string, T>
= T & { [X in K]: never };
declare const validatedEmailK: unique symbol;
type ValidatedEmail = Opaque<typeof validatedEmailK, string>;
declare function validateEmail(email: string): ValidatedEmail;
declare function sendEmail(mail: ValidatedEmail): void;
sendEmail(validateEmail('asdf@gmail.com'));
// Argument of type '"asdf@gmail.com"' is not assignable
// to parameter of type 'Opaque<unique symbol, string>'.
sendEmail('asdf@gmail.com');
Strukturelle vs. nominelle Typisierung 2. Problem
Wir haben eine Dollar- und eine Euro-Klasse. Jede der Klassen hat eine Additionsmethode zum Addieren des Dollars zum Dollar und des Euro zum Euro. Für TS sind diese Klassen strukturell gleich und wir können den Dollar zum Euro hinzufügen.export class Dollar {
value: number;
constructor(value: number) {
this.value = value;
}
add(dollar: Dollar): Dollar {
return new Dollar(dollar.value + this.value);
}
}
class Euro {
value: number;
constructor(value: number) {
this.value = value;
}
add(euro: Euro): Euro {
return new Euro(euro.value + this.value);
}
}
const dollars100 = new Dollar(100);
const euro100 = new Euro(100);
dollars100.add(dollars100);
euro100.add(euro100);
dollars100.add(euro100);
Strukturelle vs nominelle Typisierung 2. Angebot
github.com/microsoft/TypeScript/issues/202Der Satz ist alle gleich, mit "nominal", aber seitdem Da Klassen auf magische Weise nominal werden können (dazu später mehr), werden die Möglichkeiten einer expliziteren Transformation in Betracht gezogen.Strukturelle oder nominelle Eingabe 1. Tipp
Wenn die Klasse ein privates Feld hat (nativ mit "#" oder von TS c "privat"), wird die Klasse auf magische Weise zu "Nominal". Der Name und der Wert können beliebig sein. Das `!` (Definite Assignment Assertion) wird verwendet, um zu verhindern, dass TS auf ein nicht initialisiertes Feld schwört (strictNullChecks, strictPropertyInitialization-Flags sind aktiviert).class Dollar {
private desc!: never;
value: number;
constructor(value: number) {
this.value = value;
}
add(dollar: Dollar) {
return new Dollar(dollar.value + this.value);
}
}
class Euro {
private desc!: never;
value: number;
constructor(value: number) {
this.value = value;
}
add(euro: Euro) {
return new Euro(euro.value + this.value);
}
}
const dollars100 = new Dollar(100);
const euro100 = new Euro(100);
dollars100.add(dollars100);
euro100.add(euro100);
dollars100.add(euro100);
Typvarianz 1. Problem
Kurz gesagt, eine Programmieroption ist die Möglichkeit, Supertype / Subtype dort zu übergeben, wo Type erwartet wird. Zum Beispiel gibt es eine Hierarchie Form -> Kreis -> Rechteck. Ist es möglich, eine Form / ein Rechteck zu übertragen oder zurückzugeben, wenn Kreis erwartet wird?Variante in der Programmierung habr , SO .Wir können den Typ mit dem Feld, in dem die Zahl liegt, an eine Funktion übergeben, die das Feld als Zeichenfolge oder Zahl erwartet, und das übertragene Objekt im Körper mutieren und das Feld in eine Zeichenfolge ändern. Jene. `{status: number} als {status: number | string} as {status: string} `Hier ist ein Trick wie das Verwandeln einer Zahl in einen String, der einen Überraschungsfehler verursacht.function changeStatus(arg: { status: number | string }) {
arg.status = "NotFound";
}
const error: { status: number } = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
Typabweichung 1. Angebot
github.com/Microsoft/TypeScript/issues/10717Es wird vorgeschlagen, "in / out" einzuführen, um die Kovarianz / Kontravarianz für Generika explizit zu begrenzen.function changeStatus<
out T extends {
status: number | string;
}
>(arg: T) {
arg.status = "NotFound";
}
const error: { status: number } = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
Typvarianz 1. Tipp
Wenn wir mit unveränderlichen Strukturen arbeiten, tritt kein solcher Fehler auf (wir haben das Flag strictFunctionTypes bereits aktiviert).function changeStatus(arg: Readonly<{ status: number | string }>) {
arg.status = "NotFound";
}
const error: Readonly<{ status: number }> = { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
Typvarianz 1. Bonus
Readonly kann dem veränderlichengithub.com/Microsoft/TypeScript/issues/13347github.com/microsoft/TypeScript/pull/6532#issuecomment-174356151zugewiesen werden. Selbst wenn wir den Readonly-Typ erstellt haben, wird TS nicht verbieten, an die Funktion zu übergeben, bei der nicht erwartet Readonly `Readonly <{readonly status: number}> als {status: number | Zeichenfolge} als {Status: Zeichenfolge} `function changeStatus(arg: { status: number | string }) {
arg.status = "NotFound";
}
const error: Readonly<{ readonly status: number }>
= { status: 404 };
changeStatus(error);
console.log(error.status.toFixed());
Typvarianz 2. Problem
Objekte können zusätzliche Felder enthalten, die die ihnen entsprechenden Typen nicht haben: `{message: string; status: string} as {message: string} `. Aufgrund dessen sind einige Operationen möglicherweise nicht sicherconst error: { message: string; status: string } = {
message: "No data",
status: "NotFound"
};
function updateError(arg: { message: string }) {
const defaultError = { message: "Not found", status: 404 };
const newError: { message: string; status: number }
= { ...defaultError, ...arg };
console.log(newError.status.toFixed());
}
updateError(error);
TS dachte, dass als Ergebnis der Zusammenführung der Status "{... {Nachricht: Zeichenfolge, Status: Nummer}, ... {Nachricht: Zeichenfolge}}" eine Zahl sein wird.In Wirklichkeit `{... {Nachricht:" Nicht gefunden ", Status: 404}, ... {Nachricht:" Keine Daten ", Status:" NotFound "},}` Status - Zeichenfolge.Typabweichung 2. Angebot
github.com/microsoft/TypeScript/issues/12936Einführung eines "Exact" -Typs oder einer ähnlichen Syntax, um zu sagen, dass ein Typ keine zusätzlichen Felder enthalten kann.const error: Exact<{ message: string; }> = {
message: "No data",
};
function updateError(arg: Exact<{ message: string }>) {
const defaultError = { message: "Not found", status: 404, };
const newError = { ...defaultError, ...arg };
console.log(newError.status.toFixed());
}
updateError(error);
Typvarianz 2. Tipp
Führen Sie Objekte zusammen, indem Sie Felder explizit auflisten oder unbekannte Felder herausfiltern.const error: { message: string; status: string } = {
message: "No data",
status: "NotFound"
};
function updateError(arg: { message: string }) {
const defaultError = { message: "Not found", status: 404 };
const newError = { ...defaultError, message: arg.message };
console.log(newError.status.toFixed());
}
updateError(error);
Ungültigmachung der Verfeinerung. Problem
Nachdem wir etwas über den externen Zustand bewiesen haben, ist das Aufrufen von Funktionen nicht sicher, weil Es gibt keine Garantie dafür, dass Funktionen diesen externen Zustand nicht ändern:export function logAge(name: string, age: number) {
console.log(`${name} will lose ${age.toFixed()}`);
person.age = "PLACEHOLDER";
}
const person: { name: string; age: number | string } = {
name: "Person",
age: 42
};
if (typeof person.age === "number") {
logAge(person.name, person.age);
logAge(person.name, person.age);
}
Ungültigmachung der Verfeinerung. Satz
github.com/microsoft/TypeScript/issues/7770#issuecomment-334919251Fügen Sie den Modifikator "pure" für Funktionen hinzu, damit Sie diesen Funktionen zumindest vertrauen könnenUngültigmachung der Verfeinerung. Trinkgeld
Verwenden Sie unveränderliche Datenstrukturen, dann ist der Funktionsaufruf a priori sicher für frühere Überprüfungen.Bonus
Der Flusstyp ist so stark, dass er nicht alle oben aufgeführten Probleme aufweist, aber so ausgeführt wird, dass ich die Verwendung nicht empfehlen würde.Ausnahmen. Problem
TS hilft in keiner Weise, mit Ausnahmen zu arbeiten, da auf der Funktionssignatur nichts klar ist.import { JokeError } from "../helpers";
function getJoke(isFunny: boolean): string {
if (isFunny) {
throw new JokeError("No funny joke");
}
return "Duh";
}
const joke: string = getJoke(true);
console.log(joke);
Ausnahmen. Satz
github.com/microsoft/TypeScript/issues/13219Es wird vorgeschlagen, eine Syntax einzuführen, mit der Ausnahmen in einer Funktionssignatur explizit beschrieben werden könnenimport { JokeError } from '../helpers';
function getJoke(isFunny: boolean): string | throws JokeError {
}
function getJokeSafe(isFunny: boolean): string {
try {
return getJoke(isFunny);
} catch (error) {
if (error instanceof JokeError) {
return "";
} else {
return error as never;
}
}
}
console.log(getJokeSafe(true));
Ausnahmen. Bonus
github.com/microsoft/TypeScript/issues/6283Aus irgendeinem Grund ignoriert die Typdefinition für Promise in TS die Art des Fehlersconst promise1: Promise<number> = Promise.resolve(42);
const promise: Promise<never> = Promise.reject(new TypeError());
interface PromiseConstructor {
new <T>(
executor: (
resolve: (value?: T | PromiseLike<T>) => void,
reject: (reason?: any) => void
) => void
): Promise<T>;
}
Ausnahmen. Trinkgeld
Nehmen Sie einen der beiden Container wie Promise mit nur der besten Eingabe. ( Entweder Implementierungsbeispiel )import { Either, exhaustiveCheck, JokeError } from "../helpers";
function getJoke(isFunny: boolean): Either<JokeError, string> {
if (isFunny) {
return Either.left(new JokeError("No funny joke"));
}
return Either.right("Duh");
}
getJoke(true)
.mapLeft(error => {
if (error instanceof JokeError) {
console.log("JokeError");
} else {
exhaustiveCheck(error);
}
})
.mapRight(joke => console.log(joke));
Unsichere Operationen. Problem
Wenn wir ein Tupel fester Größe haben, kann TS garantieren, dass der angeforderte Index etwas enthält. Dies funktioniert nicht für das Array und TS wird uns vertrauen
export const constNumbers: readonly [1, 2, 3]
= [1, 2, 3] as const;
console.log(constNumbers[100].toFixed());
const dynamicNumbers: number[] = [1, 2, 3];
console.log(dynamicNumbers[100].toFixed());
Unsichere Operationen. Satz
github.com/microsoft/TypeScript/issues/13778Es wird vorgeschlagen, dem Rückgabetyp "T" für den Indexzugriff auf das Array "undefined" hinzuzufügen. In diesem Fall müssen Sie jedoch beim Zugriff auf einen beliebigen Index `?` Verwenden oder explizite Überprüfungen durchführen.
const dynamicNumbers: number[] = [1, 2, 3];
console.log(dynamicNumbers[100].toFixed());
console.log(dynamicNumbers[100]?.toFixed());
if (typeof dynamicNumbers[100] === 'number') {
console.log(dynamicNumbers[100].toFixed());
}
Unsichere Operationen. Trinkgeld
Um keine Entitäten zu erzeugen, die über den Bedarf hinausgehen, nehmen wir den zuvor bekannten Container "Entweder" und schreiben eine sichere Funktion für die Arbeit mit dem Index, die "Entweder <null, T>" zurückgibt.import { Either } from "../helpers";
function safeIndex<T>(
array: T[],
index: number,
): Either<null, T> {
if (index in array) {
return Either.right(array[index]);
}
return Either.left(null);
}
const dynamicNumbers: number[] = [1, 2, 3];
safeIndex(dynamicNumbers, 100)
.mapLeft(() => console.log("Nothing"))
.mapRight(el => el + 2)
.mapRight(el => console.log(el.toFixed()));
Bonus Funktionen zum Nachladen
Wenn wir sagen wollen, dass eine Funktion ein paar Zeilen benötigt und eine Zeichenfolge zurückgibt oder ein paar Zahlen und eine Zahl zurückgibt, sind diese Signaturen in der Implementierung zusammenhängend, und der Programmierer sollte ihre Richtigkeit garantieren, jedoch auf TS.PS betrachten die Alternative durch generische und bedingte Typen:function add(a: string, b: string): string;
function add(a: number, b: number): number;
function add(a: string | number,
b: string | number,
): string | number {
return `${a} + ${b}`;
}
const sum: number = add(2, 2);
sum.toFixed();
Bonus Typ Wache
TS vertraut dem Programmierer, dass "isSuperUser" korrekt bestimmt, wer "SuperUser" ist, und wenn "Vasya" hinzugefügt wird, werden keine Eingabeaufforderungen angezeigt.PS ist es wert, darüber nachzudenken, wie wir Typen unterscheiden können, die sich bereits in der Phase ihrer gewerkschaftlich gekennzeichneten Vereinigung befindentype SimpleUser = { name: string };
type SuperUser = {
name: string;
isAdmin: true;
permissions: string[]
};
type Vasya = { name: string; isAdmin: true; isGod: true };
type User = SimpleUser | SuperUser | Vasya;
function isSuperUser(user: User): user is SuperUser {
return "isAdmin" in user && user.isAdmin;
}
function doSomethings(user: User) {
if (isSuperUser(user)) {
console.log(user.permissions.join(","));
}
}
Schlussfolgerungen zu den Tipps
- Nominaltypen : Opaker Typ, private Felder- Typvarianz: Exakte Typen, DeepReadonly-Typ- Ausnahmen : Entweder Monade- Verfeinerung ungültig machen : Reine Funktionen- Unsichere Operationen ( Indexzugriff ) : Entweder / Vielleicht MonadenUnveränderliche Daten, reine Funktionen, Monaden ... Herzlichen Glückwunsch haben wir bewiesen, dass FP cool ist!Ergebnisse
- TS möchte ein Gleichgewicht zwischen Korrektheit und Produktivität herstellen
- Tests können Sicherheit und Richtigkeit nur für Testdaten nachweisen.
- Typen können die Gesamtsicherheit eines Programms belegen.
- Mutation - schlecht, okay?
Da DZ empfehlen würde, mit Flow zu spielen, nachdem ein einfacher Fehler korrigiert wurde:
declare function log(arg: { name: string, surname?: string }): void;
const person: { name: string } = { name: 'Negasi' };
log(person);
Beispielcode, Lösung und Analyse des Problems, nützliche Links im Repository .