Abschnitt 14 · Lektion 5
Synthese: gegen falsche Eingaben härten
Bis hierhin hast du Funktionen geschrieben, die funktionieren. In diesem Abschnitt geht es um Funktionen, die auch dann noch etwas Sinnvolles tun, wenn das, was ankommt, nicht das ist, was du erwartet hast.
Das ist keine Kür. Sobald Daten von außen kommen, aus einem Formular, aus dem Netz, aus dem Speicher des Browsers, ist „falsch” der Normalfall und nicht die Ausnahme.
Die drei Fragen
<p>Links die naive Fassung, rechts die gehärtete. Sieh dir die Console an.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} // Naiv: funktioniert, solange alles stimmt.
function schnittNaiv(zahlen) {
let summe = 0;
for (const zahl of zahlen) {
summe = summe + zahl;
}
return summe / zahlen.length;
}
// Gehärtet: dieselbe Rechnung, mit drei Antworten vorher.
function schnittHart(zahlen) {
if (!Array.isArray(zahlen)) {
throw new Error(`Liste erwartet, bekommen: ${typeof zahlen}`);
}
if (zahlen.length === 0) {
return 0;
}
let summe = 0;
for (const zahl of zahlen) {
if (typeof zahl !== "number" || !Number.isFinite(zahl)) {
continue;
}
summe = summe + zahl;
}
return summe / zahlen.length;
}
console.log("naiv, leer:", schnittNaiv([]));
console.log("hart, leer:", schnittHart([]));
try {
console.log("naiv, null:", schnittNaiv(null));
} catch (fehler) {
console.error("naiv wirft von selbst:", fehler.message);
}
try {
schnittHart(null);
} catch (fehler) {
console.error("hart wirft mit Ansage:", fehler.message);
} Fast jede Funktion lässt sich mit drei Fragen härten, und die Reihenfolge ist immer dieselbe:
- Was, wenn gar nichts kommt? Also
undefined,null, ein fehlendes Argument. - Was, wenn etwas vom falschen Typ kommt? Ein Text, wo eine Zahl erwartet wird. Ein Objekt, wo eine Liste erwartet wird.
- Was, wenn die Menge leer ist? Eine Liste ohne Einträge, ein Text ohne Zeichen.
Im ersten Beispiel siehst du dieselbe Rechnung zweimal. Die naive Fassung liefert bei einer leeren Liste NaN und wirft bei null einen TypeError, der nichts darüber sagt, was eigentlich schiefgelaufen ist. Die gehärtete Fassung liefert 0 und wirft eine Meldung, die den Fall benennt.
Beide „funktionieren” bei gültigen Daten gleich gut. Der Unterschied zeigt sich erst, wenn es klemmt, und dann zeigt er sich deutlich.
Sonderfälle nach oben
Die Prüfungen stehen ganz oben in der Funktion, und jede steigt sofort aus. Das ist der frühe Ausstieg aus Lektion 5.3, und er ist hier besonders wichtig.
Der Grund ist Lesbarkeit. Wenn die Sonderfälle oben abgehandelt sind, weiß der Rest der Funktion, dass er es mit gültigen Daten zu tun hat. Er muss nichts mehr prüfen und kann geradeaus rechnen.
Die Alternative wäre, die eigentliche Rechnung in ein if zu packen und immer weiter zu verschachteln. Nach dem dritten Sonderfall liest das niemand mehr.
Überspringen oder abbrechen
<p>Ein fauler Eintrag in der Liste. Zwei Umgangsweisen.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} const eingang = [
{ punkte: 10 },
{ punkte: "zehn" },
{ punkte: 30 },
];
// Streng: ein fauler Eintrag bringt alles zu Fall.
function summeStreng(liste) {
return liste.reduce((summe, eintrag) => {
if (typeof eintrag.punkte !== "number") {
throw new Error(`punkte muss eine Zahl sein, bekommen: ${typeof eintrag.punkte}`);
}
return summe + eintrag.punkte;
}, 0);
}
// Nachsichtig: was nicht passt, wird gezählt und übersprungen.
function summeNachsichtig(liste) {
let uebersprungen = 0;
const summe = liste.reduce((zwischenstand, eintrag) => {
if (typeof eintrag.punkte !== "number") {
uebersprungen = uebersprungen + 1;
return zwischenstand;
}
return zwischenstand + eintrag.punkte;
}, 0);
console.warn("Übersprungen:", uebersprungen);
return summe;
}
try {
console.log("streng:", summeStreng(eingang));
} catch (fehler) {
console.error("streng bricht ab:", fehler.message);
}
console.log("nachsichtig:", summeNachsichtig(eingang)); Bei einer Liste gibt es zwei Umgangsweisen mit einem faulen Eintrag, und beide sind je nach Lage richtig.
Abbrechen ist richtig, wenn ein Teilergebnis nichts wert ist. Eine Rechnungssumme, in der eine Position fehlt, ist schlimmer als gar keine Summe, denn sie sieht richtig aus.
Überspringen ist richtig, wenn das Ergebnis auch ohne den Eintrag brauchbar bleibt. Eine Statistik über tausend Messwerte ist nicht wertlos, weil drei davon Unsinn sind.
Was du in beiden Fällen tun solltest: das Übergehen sichtbar machen. Im Beispiel zählt die nachsichtige Fassung die übersprungenen Einträge und meldet sie mit console.warn. Stilles Überspringen ist die Variante, die dich in einem halben Jahr Nerven kostet.
Wo die Grenze liegt
<p>Nur eine der drei Funktionen prüft, und das reicht.</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} // An der Grenze: prüft, weil hier fremde Daten ankommen.
function preisAnzeigen(rohwert) {
const cent = Number(rohwert);
if (!Number.isFinite(cent) || cent < 0) {
throw new Error(`Preis in Cent erwartet, bekommen: ${rohwert}`);
}
return formatieren(inEuro(cent));
}
// Intern: verlässt sich auf den Aufrufer. Kein zweites Netz.
function inEuro(cent) {
return cent / 100;
}
// Ebenfalls intern.
function formatieren(euro) {
return euro.toFixed(2).replace(".", ",") + " Euro";
}
console.log(preisAnzeigen("4900"));
console.log(preisAnzeigen(1990));
try {
preisAnzeigen("vier Euro");
} catch (fehler) {
console.error(fehler.message);
} Nicht jede Funktion muss jede Eingabe überleben. Wenn du das versuchst, bekommst du Code, in dem die Hälfte aller Zeilen Prüfungen sind, die nie zuschlagen.
Die Regel lautet: An der Grenze zur Außenwelt wird geprüft, innen nicht.
Eine Funktion, in die fremde Daten hineingehen, prüft. Was danach weitergereicht wird, ist geprüft, und die internen Helfer dürfen sich darauf verlassen. Im dritten Beispiel prüft nur preisAnzeigen. inEuro und formatieren bekommen nie etwas anderes zu sehen als eine gültige Zahl, weil vor ihnen jemand steht, der aufgepasst hat.
Genau so ist auch der Rest dieser Seite gebaut, an der du gerade lernst. Jede Anfrage von außen wird an der Systemgrenze einmal geprüft, danach vertraut der Code sich selbst. Zweimal prüfen bringt keine zusätzliche Sicherheit, nur zusätzliche Stellen, die auseinanderlaufen können.
Und wann ist es genug?
Eine ehrliche Antwort zum Schluss: Es gibt keinen Punkt, an dem eine Funktion gegen alles gewappnet ist. Du kannst immer noch einen Fall finden.
Der brauchbare Maßstab ist nicht Vollständigkeit, sondern die Frage: Wenn hier etwas schiefgeht, merkt es dann jemand, und weiß derjenige dann, wo er suchen muss? Wenn die Antwort zweimal ja ist, ist die Funktion robust genug.
Zum Mitnehmen
Drei Fragen härten fast jede Funktion: Was, wenn nichts kommt? Was, wenn etwas vom falschen Typ kommt? Was, wenn die Menge leer ist?
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.