Christoph Haag
Christoph ist Softwareentwickler mit einer Passion für Musik
30.04.2024 | 4 min Lesezeit
Was ist der Unterschied zwischen folgenden beiden Code-Schnipseln?
import "./styles.css";
document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;
import "./styles.css";
document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;
Der Unterschied ist, mit Variante 2 hätte ich mir mehrere Stunden Debugging und Kopfzerbrechen gespart.
React basiert im Wesentlichen auf der Gleichung f(state) = UI
. Die Ausgabe ist nur von den Eingabeparametern abhängig. Das ist das Grundprinzip der funktionalen Programmierung und man nennt eine solche Funktion pure - nebeneffektfrei. Das bedeutet im Umkehrschluss auch, dass eine Funktion die Eingabeparameter nicht verändern darf.
Schauen wir uns den Code an, der das Problem verursacht hat. Es ist ein React hook zu sehen, der über einen Store auf zwei Listen itemsA
und itemsB
zugreift.
Das Ergebnis enthält alle Elemente aus der A-Liste und soll zusätzlich alle Items aus der B-Liste enthalten, die nicht schon in der ersten Liste existieren (!result.some(x => isEqual(x, itemB))
).
Am Ende wird das Ergebnis noch sortiert und zurückgegeben.
import "./styles.css";
document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;
Sieht eigentlich gut aus, oder? Es gab sogar Unit-Tests, die diese Funktion abgetestet haben. 100% Code-Coverage. Alles im Grünen. TypeScript? Keine Fehler. Zur Laufzeit: Eine Katastrophe. Es gab Stellen in der UI, an denen nur Items aus der A-Liste angezeigt werden durften, aber es wurden auch Items aus der B-Liste angezeigt.
Es gab im gesamten Code nur eine einzige Methode, diese Items zu setzen und zu verändern. Ich habe gedebuggt, Unit-Tests für diese Methode geschrieben und bin verzweifelt. Diese Methode wurde immer korrekt aufgerufen. 100% Code-Coverage, keine TypeScript Fehler. Meine Liste an Items muss also noch an anderer Stelle verändert werden. Es muss eine Funktion geben, die auf die A-Liste zugreift und diese verändert. Es muss eine Funktion geben, die nicht pure ist.
Nach langem Suchen habe ich schließlich den oben gezeigten Code gefunden, untersucht und festgestellt: die useItems-Funktion ist das Problem. Eine einzige Zeile, ein flüchtiger Fehler, nicht offensichtlich, aber mit großer Tragweite.
import "./styles.css";
document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;
Die A-Liste wird verändert. Schwierig zu sehen, da itemsA zuerst der Variablen result zugewiesen wird. Die Methode push mutiert das Array. Mein Store, meine Datenstruktur, meine Unit-Tests, mein TypeScript-Compiler. Nichts hat mich davor gewarnt - alles umsonst.
Die Lösung in der Datenstruktur:
import "./styles.css";
document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;
Meine Liste aus Items darf nicht verändert werden. Und siehe da, mit dieser Änderung rettet mich TypeScript.
import "./styles.css";
document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;
TypeScript ist hier besonders charmant, da damit auch sichergestellt ist, dass der selbe Fehler beim Zugriff auf die Items-Liste nicht noch einmal passiert.
Die Lösung im Code:
import "./styles.css";
document.getElementById("app").innerHTML = `
<h1>Hello world</h1>
`;
Mit dieser Änderung wird ein neues Array erstellt und das B-Item hinzugefügt. Die ursprüngliche Liste bleibt unverändert. Die Funktion ist pure.
Problem solved. Time wasted. Lesson learned.