Zustandsmaschinen: Unmögliche Zustände unmöglich machen
Warum „isLoading“-Boolesche Fehler hervorrufen? Ein tiefer Einblick in Finite State Machines (FSM), Statecharts und XState für kugelsichere UI-Logik.
Schauen wir uns eine typische Komponente an, die von einem Junior-Entwickler geschrieben wurde.
„tsx const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); const [data, setData] = useState(null);
const fetchData = async () => { setIsLoading(true); versuche es mit { const res = Warten auf api.get(); setData(res); } fangen (irrt) { setIsError(true); } setIsLoading(false); // Bug: Was passiert, wenn ein Fehler auftritt? logische Spaltung. }; „
Der Fehler: Wenn der „catch“-Block ausgeführt wird, wird „isError“ wahr. Dann wird „setIsLoading(false)“ ausgeführt. Was aber, wenn der Benutzer auf „Wiederholen“ klickt? Verwenden Sie „Abruf starten“ erneut. „isLoading“ ist wahr. „isError“ ist ebenfalls wahr (vom vorherigen Lauf). Die Benutzeroberfläche zeigt gleichzeitig einen Spinner UND eine Fehlermeldung an. Dies ist ein unmöglicher Zustand.
Sie beheben das Problem, indem Sie am Anfang „setIsError(false)“ hinzufügen. Dann fügen Sie „isSuccess“ hinzu. Jetzt haben Sie 3 Boolesche Werte. €2^3 = 8€ mögliche Kombinationen. Nur 4 sind gültig. 50 % Ihres Zustandsraums sind Bugs.
Bei Maison Code Paris optimieren wir auf Zuverlässigkeit. Wir lehnen „Boolesche Suppe“ ab. Wir verwenden Zustandsmaschinen.
Warum Maison Code darüber spricht
Bei Maison Code Paris fungieren wir als das architektonische Gewissen unserer Kunden. Wir übernehmen oft „moderne“ Stacks, die ohne grundlegendes Verständnis für Skalierung gebaut wurden.
Wir diskutieren dieses Thema, weil es einen kritischen Wendepunkt in der technischen Reife darstellt. Die korrekte Implementierung unterscheidet ein fragiles MVP von einer widerstandsfähigen Plattform auf Unternehmensniveau.
Warum Maison Code die Logik formal modelliert
Wir erstellen Finanz-Dashboards, bei denen ein „Fehler“ nicht nur ärgerlich ist; es ist eine Belastung. Um die Korrektheit zu gewährleisten, nutzen wir State Machines (XState):
- Sicherheit: Unmögliche Zustände (Laden + Fehler) sind mathematisch nicht darstellbar.
- Dokumentation: Der Code ist das Diagramm. Wir exportieren Zustandsdiagramme, um Stakeholdern genau zu zeigen, wie der Checkout-Ablauf funktioniert.
- Testbarkeit: Wir generieren automatisch 100 % Pfadabdeckungstests aus der Maschinendefinition. Wir hoffen nicht, dass es funktioniert; Wir beweisen, dass es funktioniert.
Die Finite-State-Maschine (FSM)
Eine Zustandsmaschine ist ein Verhaltensmodell. Es besteht aus:
- Zustände: (z. B. „Leerlauf“, „Laden“, „Erfolg“, „Fehler“).
- Ereignisse: (z. B. „FETCH“, „RETRY“, „CANCEL“).
- Übergänge: (z. B. „idle“ + „FETCH“ -> „loading“).
Entscheidend ist, dass sich die Maschine jeweils nur in einem Zustand befinden kann. Wenn Sie sich tatsächlich im Status „Laden“ befinden und das Ereignis „FETCH“ eintritt (Doppelklicks des Benutzers), ignoriert der Computer es (es sei denn, Sie erlauben es ausdrücklich). Die Rennbedingungen verschwinden.
XState: Die Bibliothek
Wir verwenden XState. Es ist der Standard für FSMs in JavaScript. Es implementiert Statecharts (W3C SCXML-Standard), was Folgendes ermöglicht:
- Hierarchische Zustände (übergeordnet/untergeordnet).
- Parallelstaaten (orthogonale Regionen).
- Verlaufszustände (Erinnern Sie sich, wo Sie aufgehört haben).
Implementierung
„Typoskript import { createMachine, Assign } from ‘xstate’;
const fetchMachine = createMachine({ id: ‘abrufen’, Initiale: ‘idle’, Kontext: { Daten: null, Fehler: null, }, Staaten: { im Leerlauf: { on: { FETCH: ‘loading’ } }, Laden: { // Ein Versprechen (Dienst) aufrufen aufrufen: { src: ‘fetchData’, onDone: { Ziel: ‘Erfolg’, Aktionen: zuweisen({ data: (context, event) => event.data }) }, onError: { Ziel: ‘Misserfolg’, Aktionen: zuweisen({ Fehler: (Kontext, Ereignis) => event.data }) } } }, Erfolg: { // Terminalstatus? Oder vielleicht eine Aktualisierung zulassen am: { AKTUALISIEREN: ‘Laden’ } }, Fehler: { on: { RETRY: ‘loading’ } } } }); „
Beachten Sie die Klarheit. Können Sie es erneut versuchen, wenn es erfolgreich ist? Nein. Der Übergang ist nicht definiert. Können Sie beim „Laden“ „FETCH“ ausführen? Nein. Die Logik ist von Natur aus streng.
Wachen und Kontext
Manchmal sind Übergänge an Bedingungen geknüpft. „Der Benutzer kann NUR in den Status „Zahlung“ wechseln, WENN „formIsValid“ wahr ist.“
„Typoskript // Wache am: { WEITER: { Ziel: ‘Zahlung’, cond: (context) => context.formIsValid } } „
Dadurch wird die „Geschäftslogik“ effektiv aus der Ansichtsebene (React Components) in die Modellebene (Machine) verschoben.
Die React-Komponente wird dumm. Es rendert lediglich den Status und sendet Ereignisse.
machine.send('NEXT'). Es ist egal, ob es als nächstes weitergeht. Die Maschine entscheidet.
Parallele Staaten (orthogonale Regionen)
Echte Apps sind komplex. Stellen Sie sich ein Upload-Widget vor.
- Es wird eine Datei hochgeladen (0 % -> 100 %).
- Der Benutzer kann das Widget minimieren/maximieren.
Diese sind unabhängig. Sie können „hochladen“ UND „minimieren“. XState erledigt dies über Parallel States.
„Typoskript Staaten: { UploadProzess: { Initiale: ‘ausstehend’, Zustände: { ausstehend: {}, Hochladen: {}, abgeschlossen: {} } }, ui: { Initiale: ‘erweitert’, Zustände: { erweitert: {}, minimiert: {} } } } „
Visualisierer und Kommunikation
Das beste Feature von XState ist der Visualizer. Sie können Ihren Code kopieren und in „stately.ai/viz“ einfügen und es wird ein interaktives Diagramm generiert. Wir nutzen dies, um mit Produktmanagern zu kommunizieren. PM: „Der Benutzer sollte nicht in der Lage sein, zu stornieren, sobald die Zahlung beginnt.“ Entwickler: „Sehen Sie sich das Diagramm an. Es gibt keinen Pfeil „ABBRECHEN“ im Status „Verarbeitung_Zahlung“. Es gleicht das mentale Modell mit dem Codemodell aus.
Modellbasiertes Testen
Da es sich bei der aktuellen Implementierung um ein Diagramm handelt, können wir Tests automatisch generieren. „@xstate/test“ kann den kürzesten Pfad zu jedem Bundesstaat berechnen. Es wird ein Testplan erstellt:
- Beginnen Sie im Leerlauf.
- Feuern Sie „FETCH“ ab.
- Erwarten Sie „Laden“.
- Versprechen einlösen.
- Erwarten Sie „Erfolg“.
Es stellt sicher, dass Sie eine 100-prozentige Abdeckung Ihrer Logikabläufe haben.
10. Das Akteurmodell (XState Actors)
Eine einzelne Maschine ist großartig. Aber was ist, wenn Sie 10 Upload-Widgets haben? Sie wollen keine riesige „UploadMachine“. Sie möchten 10 kleine „uploadActor“ erzeugen. Die übergeordnete Maschine (die Seite) kommuniziert mit dem untergeordneten Akteur (dem Widget) über Nachrichten („send({ type: ‘UPLOAD_COMPLETE’ })`). Dies ist das Actor Model (populär gemacht durch Erlang/Elixir). Es sorgt für Isolation. Wenn ein Akteur abstürzt, stürzt nicht die gesamte App ab. XState macht dies mit „spawn()“ trivial.
11. Visual State Designer
Warum überhaupt Code schreiben? Mit Stately.ai können Sie Boxen per Drag-and-Drop verschieben, um die Logik zu entwerfen. Da der Code das Diagramm ist (isomorph), exportiert der Designer JSON, das Ihr Code importiert. Dies öffnet die Tür für Low-Code-Logik, die von leitenden Ingenieuren verwaltet wird. Es stellt sicher, dass die „Geschäftsregeln“ für die Beteiligten sichtbar sind und nicht in „useEffect“-Spaghetti versteckt sind.
13. Hierarchische Zustände (zusammengesetzte Zustände)
Ein Checkout ist nicht nur eine Liste von Zuständen. Es hat Phasen.
- Kaufabwicklung:
- Versand: („Adresse“, „Methode“)
- Zahlung: (
card_entry,3ds_verification) Wenn Sie „Zahlung“ abbrechen, kehren Sie dann zu „Versand“ zurück? Mit hierarchischen Zuständen können Sie zu „checkout.shipping.history“ wechseln. Dadurch können wir komplexe Strömungen ohne „State Explosion“ modellieren. Wir gruppieren verwandte Zustände in einem übergeordneten Knoten. Das übergeordnete Element verarbeitet globale Ereignisse (z. B. „LOGOUT“-Übergänge zu „home“ von einem beliebigen Unterstatus).
14. Testen: Das Modell IST der Test
Mit „@xstate/test“ schreiben wir nicht für jeden Tastenklick manuelle E2E-Tests. Wir schreiben Pfadbehauptungen.
- Aktivieren Sie im Status „Versand“ „getByText(“Lieferadresse”)“.
- Aktivieren Sie im Status „Zahlung“ „getByText(“Credit Card”)“. Die Bibliothek führt dann eine gerichtete Graph-Traversierung (kürzester Pfad) durch und führt Puppeteer/Playwright aus. Es generiert automatisch Hunderte von Tests. Wenn Sie einen neuen Status hinzufügen, aktualisieren sich die Tests selbst.
15. Umgang mit Staatsexplosionen
Der größte Kritikpunkt an FSMs ist „State Explosion“. Wenn Sie 10 boolesche Werte haben, haben Sie €2^{10} = 1024€ Zustände. Sie alle in einer Maschine aufzulisten, ist unmöglich. Lösung: Parallelzustände (Orthogonalität). Anstelle von „loading_and_modal_open“, „loading_and_modal_closed“, „success_and_modal_open“ … Sie haben zwei Regionen: „data: { Loading, Success }“ UND „ui: { modal_open, modal_closed }“. Dies reduziert die Komplexität von Multiplikativ (€M * N€) auf Additiv (€M + N€).
16. Formale Verifizierung (Sicherheit)
Da XState ein mathematischer Graph ist, können wir Dinge mathematisch beweisen. „Ist es möglich, den Status ‚Zahlung‘ zu erreichen, ohne den ‚Versand‘ zu durchlaufen?“ Wir können einen Diagrammalgorithmus ausführen, um zu überprüfen, ob ein Pfad existiert. Wenn dies der Fall ist, schlägt der Build fehl. Dies ist eine formale Verifizierung. Es ist normalerweise der NASA/Avionik vorbehalten, aber XState bringt es auf React-Formulare. Dies gibt uns die Gewissheit, dass unsere „Gatekeeping“-Logik unzerbrechlich ist.
17. Fazit
Zustandsmaschinen sorgen für mehr Ausführlichkeit. Das Schreiben einer Maschine dauert länger als „useState“. Bei komplexen Abläufen (Checkout, Onboarding, Assistenten) ist der ROI jedoch enorm. Sie tauschen „Implementierungsgeschwindigkeit“ gegen „Wartungsgeschwindigkeit“ und „Zuverlässigkeit“.
Wir glauben, dass UI-Logik Algorithmendesign ist. Es verdient eine formale Modellierung.
Buggy-Kasse?
Sind in Ihrem Zahlungsablauf Rennbedingungen enthalten?
Umgestaltung von Zustandsmaschinen. Beauftragen Sie unsere Architekten.