Komplexitätsmanagement

Eine einfache Webanwendung zu halten, kann überraschend kompliziert sein. In diesem Modul erfahren Sie, wie Web APIs mit Threading funktionieren und wie Sie diese für gängige PWA-Muster wie die Statusverwaltung verwenden können.

Einfachheit und Komplexität

In seinem Vortrag Simple Made Easy spricht Rich Hickey über die Eigenschaften von einfachen und komplexen Dingen. Bei einfachen Dingen konzentriert er sich auf:

„Eine Rolle, eine Aufgabe, ein Konzept oder eine Dimension.“

Es wird jedoch betont, dass es bei Einfachheit nicht darum geht:

„Eine Instanz haben oder einen Vorgang ausführen.“

Ob etwas einfach oder nicht einfach ist, hängt davon ab, wie gut es miteinander verbunden ist.

Komplexität entsteht durch Binden, Weben oder – im Sinne von Rich's Begriff – das Zusammenführen von Dingen. Sie können die Komplexität berechnen, indem Sie die Anzahl der verwalteten Rollen, Aufgaben, Konzepte oder Dimensionen zählen.

Einfachheit ist in der Softwareentwicklung von entscheidender Bedeutung, da einfacher Code leichter zu verstehen und zu pflegen ist. Einfachheit ist auch bei Web-Apps erforderlich, da sie dazu beitragen kann, dass unsere App in jedem möglichen Kontext schnell und barrierefrei zugänglich wird.

PWA-Komplexität verwalten

Jeglicher JavaScript-Code, den wir für das Web schreiben, berührt den Hauptthread an einem Punkt. Der Hauptthread ist jedoch standardmäßig sehr komplex, über den Sie als Entwickler keine Kontrolle haben.

Der Hauptthread lautet:

  • Verantwortlich für das Zeichnen der Seite, wobei es sich um einen komplexen, mehrstufigen Prozess handelt, der die Berechnung von Stilen, das Aktualisieren und Zusammensetzen von Ebenen sowie das Malen auf dem Bildschirm umfasst.
  • Verantwortlich dafür, auf Ereignisse zu warten und darauf zu reagieren, einschließlich Ereignissen wie Scrollen.
  • Sie sind für das Laden und Entfernen der Seite verantwortlich.
  • Medien, Sicherheit und Identität verwalten Das ist alles, bevor Code, den Sie schreiben, auch in diesem Thread ausgeführt werden kann. Beispiel:
  • DOM manipulieren
  • Zugriff auf sensible APIs, z. B. Gerätefunktionen oder Medien/Sicherheit/Identität

Wie Surma in seinem Vortrag auf dem Chrome Dev Summit 2019 formulierte, ist der Hauptthread überarbeitet und zu wenig bezahlt.

Trotzdem befindet sich der meiste Anwendungscode auch im Hauptthread.

All dieser Code erhöht die Komplexität des Hauptthreads. Der Hauptthread ist der einzige Thread, den der Browser zum Layouten und Rendern von Inhalten auf dem Bildschirm verwenden kann. Wenn Ihr Code also mehr und mehr Verarbeitungsleistung erfordert, muss er schnell ausgeführt werden, da der Browser nicht in der Lage ist, auf Nutzereingaben zu reagieren oder die Seite neu zu zeichnen.

Wenn Interaktionen nicht mit der Eingabe verbunden werden, wenn Frames ausfallen oder die Nutzung einer Website zu lange dauert, sind die Nutzenden frustriert, sie haben das Gefühl, dass die Anwendung defekt ist, und ihr Vertrauen in die Website nimmt ab.

Die schlechte Nachricht? Die zusätzliche Komplexität des Hauptthreads ist eine fast sichere Möglichkeit, das Erreichen dieser Ziele zu erschweren. Die gute Nachricht? Weil der Hauptthread eindeutig ist: Er kann als Leitfaden verwendet werden, um die Abhängigkeit von ihm für den Rest Ihrer Anwendung zu verringern.

Trennungen von Anliegen

Webanwendungen können viele unterschiedliche Aufgaben erledigen, aber im Allgemeinen können Sie sie in Arbeit aufteilen, die die Benutzeroberfläche direkt betrifft, und Arbeit, die nicht funktioniert. UI-Arbeit ist Arbeit, die:

  • Berührt direkt das DOM.
  • Nutzt APIs, die Gerätefunktionen wie Benachrichtigungen oder den Zugriff auf Dateisysteme berühren
  • Berührt Identität, z. B. Nutzer-Cookies, lokaler oder Sitzungsspeicher.
  • Verwaltet Medien wie Bilder, Audio oder Video.
  • Hat Auswirkungen auf die Sicherheit, die ein Eingreifen des Nutzers erfordern würden, z. B. die serielle Web-API.

Nicht über die Benutzeroberfläche bezogene Aufgaben können Folgendes umfassen:

  • Reine Berechnungen.
  • Datenzugriff (Abrufen, IndexedDB usw.).
  • Kryptowährung.
  • Nachrichten.
  • Blob- oder Stream-Erstellung oder -Bearbeitung

Nicht-UI-Aufgaben werden oft durch UI-Arbeiten gebucht: Ein Nutzer klickt auf eine Schaltfläche, die eine Netzwerkanfrage für eine API auslöst, die geparste Ergebnisse zurückgibt, die dann zum Aktualisieren des DOMs verwendet werden. Beim Schreiben von Code wird diese End-to-End-Erfahrung oft berücksichtigt, aber die einzelnen Teile des Ablaufs normalerweise nicht. Die Grenzen zwischen UI-Arbeit und Nicht-UI-Arbeit sind genauso wichtig wie die End-to-End-Erfahrungen, da sie der erste Ort sind, an dem Sie die Komplexität des Hauptthreads reduzieren können.

Auf eine einzelne Aufgabe konzentrieren

Eine der einfachsten Möglichkeiten zur Vereinfachung von Code ist das Aufteilen von Funktionen, sodass sich jede auf eine einzelne Aufgabe bezieht. Aufgaben können anhand der festgelegten Grenzen festgelegt werden, indem die End-to-End-Erfahrung durchgegangen wird:

  • Reagieren Sie zuerst auf die Eingabe der Nutzenden. Das ist UI-Arbeit.
  • Stellen Sie als Nächstes eine API-Anfrage. Hierbei handelt es sich um Aufgaben, die nichts mit der Benutzeroberfläche zu tun haben.
  • Parsen Sie als Nächstes die API-Anfrage. Auch hier handelt es sich um Aufgaben, die nichts mit der Benutzeroberfläche zu tun haben.
  • Bestimmen Sie als Nächstes Änderungen am DOM. Es kann sich dabei um UI-Funktionen handeln oder wenn Sie z. B. eine virtuelle DOM-Implementierung verwenden, ist dies möglicherweise nicht der Fall.
  • Nehmen Sie abschließend Änderungen am DOM vor. Das ist UI-Arbeit.

Die ersten klaren Grenzen liegen zwischen Arbeiten mit der Benutzeroberfläche und Arbeiten ohne UI. Dann gibt es Bewertungsaufrufe: Sind das Erstellen und Parsen einer API-Anfrage eine oder zwei Aufgaben? Sind DOM-Änderungen, die nicht auf der Benutzeroberfläche basieren, mit der API-Funktion gebündelt? Im selben Thread? In einem anderen Thread? Die richtige Trennungsebene ist hier der Schlüssel zur Vereinfachung der Codebasis und dazu, dass Teile davon erfolgreich aus dem Hauptthread verschoben werden können.

Zusammensetzbarkeit

Wenn Sie große End-to-End-Workflows in kleinere Teile aufteilen möchten, müssen Sie die Zusammensetzbarkeit Ihrer Codebasis berücksichtigen. Berücksichtigen Sie die folgenden Aspekte der funktionalen Programmierung:

  • Kategorisieren Sie die Arten von Aufgaben, die Ihre Anwendung ausführt.
  • Erstellung allgemeiner Eingabe- und Ausgabeschnittstellen für sie.

Beispielsweise verarbeiten alle API-Abrufaufgaben am API-Endpunkt ein Array von Standardobjekten und alle Datenverarbeitungsfunktionen übernehmen und geben ein Array von Standardobjekten zurück.

JavaScript verfügt über einen Algorithmus für strukturierte Klonen, der zum Kopieren komplexer JavaScript-Objekte bestimmt ist. Web Worker verwenden ihn beim Senden von Nachrichten und IndexedDB verwendet ihn zum Speichern von Objekten. Wenn Sie Schnittstellen auswählen, die Sie mit dem Algorithmus zum strukturierten Klonen verwenden können, sind sie noch flexibler in der Ausführung.

Vor diesem Hintergrund können Sie eine Bibliothek mit zusammensetzbaren Funktionen erstellen, indem Sie Ihren Code kategorisieren und allgemeine E/A-Schnittstellen für diese Kategorien erstellen. Zusammensetzbarer Code ist ein Markenzeichen für einfache Codebasis: lose gekoppelte, austauschbare Teile, die „neben“ nebeneinander stehen und aufeinander aufbauen können – im Gegensatz zu komplexem Code, der eng miteinander verbunden ist und sich daher nicht einfach trennen lässt. Im Internet kann zusammensetzbarer Code den Unterschied ausmachen, ob der Hauptthread überlastet wird oder nicht.

Mit zusammensetzbarem Code ist es an der Zeit, einen Teil davon aus dem Hauptthread zu entfernen.

Mit Web Workern die Komplexität verringern

Mit Web Workern, einer oft nicht ausgelasteten, aber allgemein verfügbaren Webfunktion, können Sie Arbeit aus dem Hauptthread verschieben.

Mit Web Workern kann eine PWA (einige) JavaScript-Elemente außerhalb des Hauptthreads ausführen.

Es gibt drei Arten von Workern.

Dedicated Worker, wie bei der Beschreibung von Web Workern am häufigsten gedacht, können von einem einzelnen Skript in einer einzelnen ausgeführten Instanz einer PWA verwendet werden. Arbeiten, die nicht direkt mit dem DOM interagieren, sollten nach Möglichkeit an einen Web Worker verschoben werden, um die Leistung zu verbessern.

Freigegebene Worker ähneln dedizierten Workern, mit dem Unterschied, dass sie von mehreren Skripts in mehreren geöffneten Fenstern gemeinsam verwendet werden können. Dies bietet die Vorteile eines dedizierten Workers, jedoch mit einem gemeinsamen Status und internem Kontext zwischen Fenstern und Skripts.

Ein freigegebener Worker kann beispielsweise den Zugriff und die Transaktionen für die IndexedDB einer PWA verwalten und Transaktionsergebnisse an alle aufrufenden Scripts senden, damit diese auf Änderungen reagieren können.

Der endgültige Web Worker wird in diesem Kurs ausführlich behandelt: Service Worker, die als Proxy für Netzwerkanfragen fungieren und von allen Instanzen einer PWA gemeinsam genutzt werden.

Selbst ausprobieren

Zeit für Code! Basierend auf allem, was Sie in diesem Modul gelernt haben, können Sie eine PWA von Grund auf neu erstellen.

Ressourcen