Der Ordner heißt store. Er enthält eine Datei namens index.ts, und diese Datei ist 640 Zeilen lang. Oben stehen sechs Imports, unten steht ein Kommentar mit dem Datum, an dem jemand aufgehört hat, ihn zu pflegen. Ich habe ihn an einem Mittwoch im Dezember geöffnet, weil ein Ladezustand nicht verschwand, und ihn drei Stunden später wieder geschlossen.

Das ist kein Frontend-Problem. Das ist ein Problem mit dem Ort, an dem Dinge liegen.

Zustand ist eine Antwort auf eine Frage

Der nützlichste Satz, den ich dieses Semester gehört habe, war: Zustand ist die minimale Menge an Information, die man braucht, um die aktuelle Ansicht zu erzeugen. Minimal ist das entscheidende Wort. Alles andere ist entweder ableitbar oder eine Kopie, und Kopien laufen auseinander, sobald jemand eine Zeile übersieht.

In unserem Store lagen: die Liste der Artikel, die gefilterte Liste, die sortierte Liste, die Anzahl der Treffer, ein Boolean „lädt“, ein Boolean „lädt zum zweiten Mal“, und ein String mit der letzten Fehlermeldung. Vier davon waren Ableitungen. Der Rest waren Notizen darüber, was die Ableitung zuletzt ergeben hatte.

Der eigentliche Grund für die Größe war nicht Faulheit, sondern Angst. Jeder dieser sechs Zustände war irgendwann eine Antwort auf einen Bug: Die gefilterte Liste entstand, weil ein Render zu früh lief. Die Trefferzahl, weil die Liste asynchron nachgeladen wurde. Der zweite Lade-Boolean, weil jemand das erste Laden von einem Nachladen unterscheiden wollte, ohne den Ort dafür zu ändern. Zustand wächst aus Angst vor Wettläufen, und jeder einzelne Eintrag hat eine Geschichte, die einmal stimmte.

Der Ordner ist ein Symptom

Editorfenster mit einer langen Datei und markierten Zeilen im Projektbaum
640 Zeilen, sechs Zuständigkeiten, ein Ordner. Der Projektbaum verrät es schon.

Wenn ein Ordner alles enthält, hat niemand entschieden, wo etwas hingehört. Das ist keine Faulheit, das ist ein fehlendes Kriterium. Bei uns lautete das Kriterium irgendwann „was nicht in eine Komponente passt“, und das ist die Definition eines Reste-Ordners.

Das bessere Kriterium ist unspektakulär: Ein Stück Zustand gehört dorthin, wo es entschieden wird. Der Ladezustand einer Liste gehört zur Liste, nicht zur Anwendung. Der angemeldete Nutzer gehört zur Anwendung. Der Filter gehört in die URL, weil er sonst beim Neuladen verschwindet und weil er dann teilbar ist.

Ein Reducer in C, weil es dort wehtut

Um zu verstehen, wie viel Zustand wirklich nötig ist, habe ich die Logik testweise in C nachgebaut. C zwingt einen, jeden Fall zu benennen, und es gibt keine Bibliothek, die die Unordnung versteckt. Das hier ist der ganze Reducer für die Artikelliste, nach dem Aufräumen.

typedef struct {
    int  count;
    int  offset;
    int  sort;      /* 0 = neu, 1 = preis, 2 = name */
    bool loading;
    bool failed;
} list_state;

list_state reduce(list_state s, event e)
{
    switch (e.type) {
    case EV_RELOAD:
        s.loading = true;
        s.failed  = false;
        s.offset  = 0;
        break;
    case EV_PAGE:
        s.offset += e.page_size;
        s.loading = true;
        break;
    case EV_SORT:
        s.sort   = e.sort;
        s.offset = 0;           /* Sortierung beginnt vorn */
        s.loading = true;
        break;
    case EV_OK:
        s.count   = e.count;
        s.loading = false;
        break;
    case EV_FAIL:
        s.failed  = true;
        s.loading = false;      /* nie beides gleichzeitig */
        break;
    }
    return s;
}

Fünf Felder, fünf Ereignisse, und kein Fall, in dem loading und failed gleichzeitig wahr sind. Das ist die ganze Regel, die den Ladezustand vorher dauerhaft sichtbar gemacht hat: Ein Fehler setzt das Laden zurück. Niemand hatte das aufgeschrieben, und deshalb hat es niemand geprüft.

Was danach übrig blieb

Von den sechs Zuständen sind drei geblieben. Die gefilterte Liste wird abgeleitet, die Trefferzahl ergibt sich aus der Länge, und die „lädt zum zweiten Mal“-Flagge war ein Workaround für ein doppeltes Fetch, das niemand bemerkt hatte. Die Datei ist von 640 auf 180 Zeilen geschrumpft, und der Ordner heißt jetzt nicht mehr store, sondern articles. Kleine Änderung, große Wirkung auf die Frage, wo man morgens zuerst hinsieht.

Der Preis war ein Tag Arbeit und ein Nachmittag, an dem ich zwei Funktionen gesucht habe, die es nicht mehr gab. Den zweiten Nachmittag rechne ich nicht als Verlust, sondern als Beweis, dass vorher zu viel da war.

Beim Aufräumen habe ich eine Regel benutzt, die ich seitdem öfter brauche: Wenn zwei Felder immer gleichzeitig gesetzt werden, sind sie ein Feld. Und wenn ein Feld nur existiert, damit eine Komponente nicht zweimal rendert, gehört es nicht in den Zustand, sondern in die Komponente. Beides klingt banal, hat aber vier der sechs Felder erledigt, ohne dass ich über Architektur nachdenken musste. Die zwei übrigen sind die, über die man tatsächlich streiten kann: Ladezustand und Fehler.

Fazit

Ein Ordner, in dem alles landet, ist keine Architektur, sondern ein Regal. Zustand braucht ein Kriterium, und das Kriterium ist nicht technisch: Es ist die Frage, wo eine Entscheidung getroffen wird. Wer sie beantwortet, braucht keinen Store mit sechshundert Zeilen – und keine Diskussion darüber, welche Bibliothek die richtige ist.

Meine Frage an dich: Wenn du in deinem Projekt den größten Zustandscontainer öffnest – wie viele Felder darin könntest du aus anderen ableiten?