Startseite / Artikel

Die wahre Komplexität moderner JavaScript-Sprachen rührt von den Entwicklungstools und nicht von der Sprache selbst her.

Dieser Artikel erläutert, wie Kernfunktionen von JavaScript wie async/await und optional chaining den Code vereinfachen, während übermäßige Werkzeuge und Abhängigkeiten unnötige Komplexität verursachen.

Verlag: REACTAPP.TOP17 Min. Lesezeit3951 Wörter

Quellen: Modern JavaScript Is Simpler Than We Pretend

In der JavaScript-Entwicklung gibt es einen seltsamen Moment, in dem man erkennt, dass man zu viel weiß.

Nicht zu viel über JavaScript selbst.

Sondern zu viel über das Ecosystem rund um JavaScript.

Man hat React erlernt. Dann kamen die Hooks. Danach trat das State-Management in den Vordergrund, gefolgt von einer bevorzugten State-Management-Bibliothek. Man hat feste Ansichten zur Datenerfassung auf der Client-Seite entwickelt. Man versteht, was ein Bundler tut – obwohl man seine Konfigurationsdatei nur selten öffnet. Man ist auf Server-Komponenten, Signals, Islands, Edge-Runtimes, Meta-Frameworks, Build-Tools, Linters, Formatter, Package-Manager sowie mindestens drei konkurrierende Konventionen zur Organisation einer Datei mit dem Namen components gestoßen.

Dann öffnet man eine kleine JavaScript-Datei und stellt fest, dass die eigentliche Logik auf Folgendem beruht:

const users = await fetch("/api/users").then(res => res.json());

Das ist schon alles.

Die Sprache an sich ist oft überraschend einfach zu verstehen.

Was modernes JavaScript überwältigend erscheinen lässt, sind all die Zusatzkomponenten, die darauf aufgebaut sind.

Dass JavaScript-Codebasen von ein paar Dateien zu Systemen mit Hunderten von Abhängigkeiten anwachsen, zeigt, dass wir Komplexität mit wahrer Schwierigkeit verwechselt haben.

Mit modernem JavaScript zu arbeiten bedeutet nicht, jede neue Funktion zu übernehmen, die es bietet.

Das bedeutet vielmehr, zu erkennen, welche Probleme die Sprache bereits für einen gelöst hat.

Die Sprache ist still gewachsen

JavaScript hatte einst den Ruf, eine seltsame, hastig entworfene Sprache zu sein, die irgendwie dazu führte, dass sie die gesamte Webwelt antreibt.

Ein Teil dieses Rufs war berechtigt.

In den frühen Versionen gab es erhebliche Probleme. Die Browser-APIs verhielten sich in verschiedenen Umgebungen nicht konsequent. Das Verständnis von asynchronem Code war wirklich schwierig. Der Laufzeitumgebung fehlten eingebaute Module, weshalb große Codebasen auf Mustern angewiesen waren, die oft komplizierter erschienen als die Sprache selbst.

Das heutige JavaScript ist eine völlig andere Geschichte.

Das haben Sie jetzt zur Verfügung:

  • Block-scope-Variablen durch const und let
  • kompakte Funktionssyntax mithilfe von Pfeilfunktionen
  • Destructuring zur Extraktion von Werten aus Objekten und Arrays
  • Satzinterpolation mit Template-Literals
  • Standardparameter, um manuelle Fallback-Prüfungen zu vermeiden
  • Spread-Syntax zum Kopieren und Zusammenführen von Daten
  • ein natives Modulsystem
  • Klassen, die nur dann verwendet werden, wenn sie wirklich notwendig sind
  • eine eingebaute Möglichkeit, asynchrone Aufgaben über Promises darzustellen
  • eine Syntax, die asynchrone Aufgaben wie gewöhnliche Schritte darstellt, nämlich async und await
  • sicheres Zugriff auf Eigenschaften mithilfe des optional chaining
  • eine sauberere Methode zum Rückgriff auf Standardwerte, das nullish coalescing
  • Hilfsmittel für Arrays und Sammlungen, die direkt in der Sprache enthalten sind
  • wahre Privatsphäre innerhalb von Klassen durch private Felder
  • Generatoren und Iteratoren für benutzerdefinierte Sequenzen
  • spezielle Map- und Set-Datenstrukturen
  • solide, native JSON-Unterstützung
  • Runtimes, die immer leistungsfähiger werden
  • Auch keine einzige Funktion auf dieser Liste macht die Sprache schwer zu verwenden.

    Das eigentliche Problem ist, dass Entwickler dazu neigen, diese Syntax als einen überwältigenden Haufen von Regeln auswendig zu lernen, anstatt sie als ein recht kompaktes Werkzeugset zur Darstellung alltäglicher Programmierkonzepte zu betrachten.

    Sobald die zugrundeliegenden Ideen klar werden, wirkt die Syntax nicht mehr bedrohlich.

    Ihnen fehlt vermutlich die Hälfte der Architektur

    Eine der schnellsten Wege, ein JavaScript-Projekt zu überkomplizieren, besteht darin, bereits vor dem Verständnis des Problems Architekturentscheidungen zu treffen.

    Stellen Sie sich ein Dashboard vor: Es muss Daten laden, es dem Benutzer ermöglichen, diese zu bearbeiten, und das Ergebnis speichern.

    Ein sinnvoller erster Ansatz könnte so aussehen:

    async function loadUsers() {
      const response = await fetch("/api/users");
    
      if (!response.ok) {
        throw new Error("Failed to load users");
      }  return response.json();
    }
    

    Dann ist der Aufruf genauso einfach wie:

    const users = await loadUsers();
    

    Auf der Grundlage dieses Musters allein kann man eine erstaunliche Menge an funktionaler Software entwickeln.

    Doch moderne Gewohnheiten veranlassen uns stattdessen, sofort eine Flut von Fragen zu stellen:

    Sollten Sie sofort auf ein Framework zurückgreifen?

    Wie sieht es mit einer Schicht zur Dateneinholung aus?

    Brauchen Sie eine spezielle Bibliothek nur zum Verwalten des Zustands?

    Sollte es ein Caching-Mechanismus vor Ihren Anfragen geben?

    Ist eine Bibliothek für die Schema-Validierung erforderlich?

    Wie sollten Ordner und Dateien organisiert werden?

    Welches Muster sollten Ihre Komponenten befolgen?

    Welche Build-Tools sollten alles zusammenfügen?

    Welchen Ansatz sollte der Server verwenden, um Anfragen zu verarbeiten?

    Diese Fragen können letztendlich wichtig sein.

    Selten sind es jedoch die richtigen Fragen, um damit zu beginnen.

    Die erste Frage sollte immer lauten:

    Was benötigt diese Anwendung tatsächlich?

    Dieser Unterschied ist wichtig, denn jede Abstraktion hat ihren Preis.

    Jedes davon fügt ein Konzept hinzu, das jemand anderes erst lernen muss, bevor er den Code sicher bedienen kann.

    Falls eine einfache Funktion ausreicht, brauchen Sie wahrscheinlich keine Klasse.

    Falls ein Modul ausreicht, brauchen Sie wahrscheinlich kein Framework.

    Falls ein einfaches Objekt Ihren Zustand speichern kann, brauchen Sie wahrscheinlich kein Zustandsverwaltungssystem.

    Falls fetch() Ihren Anforderungen genügt, könnte eine weitere Schicht zum Abrufen von Daten nur zusätzliche Komplexität zwischen Ihnen und dem Netzwerk schaffen.

    Das bedeutet keineswegs, dass Frameworks per se eine schlechte Idee sind.

    Das bedeutet vielmehr, dass ein Framework ein Problem lösen sollte, das Sie tatsächlich haben, und nicht eines, von dem Sie annehmen, dass Sie es hätten.

    async und await beseitigen einen großen Teil der mentalen Belastung

    Nicht jede bedeutende Erweiterung in modernem JavaScript ist besonders auffällig.

    async und await sind ein hervorragendes Beispiel für eine unauffällige, aber bedeutende Verbesserung.

    Blicken wir auf den alten Ansatz mit Callbacks zurück:

    getUser(id, function (user) {
      getOrders(user, function (orders) {
        processOrders(orders, function (result) {
          console.log(result);
        });
      });
    });
    

    Das Problem lag nicht nur daran, dass dieser Code optisch unangenehm war.

    Tiefliegend verschachtelte Callbacks machten es wirklich schwierig, den Ablauf der Ausführung nachzuvollziehen.

    Promises besserten die Situation:

    getUser(id)
      .then(user => getOrders(user))
      .then(orders => processOrders(orders))
      .then(result => console.log(result));
    

    Dann brachten async und await den asynchronen Code noch näher an eine einfache, sequentiell aussehende Logik:

    const user = await getUser(id);
    const orders = await getOrders(user);
    const result = await processOrders(orders);
    
    console.log(result);
    

    Es handelt sich um eine solche Funktion, die im Alltag unbemerkt in den Hintergrund tritt.

    Das ist eigentlich ein Zeichen für gutes Design.

    Gut konzipierte Sprachfunktionen verschwinden in der normalen Nutzung, anstatt Aufmerksamkeit auf sich zu ziehen.

    Der Fehler besteht darin, anzunehmen, dass die Syntax zur Handhabung asynchronen Verhaltens aufgrund seiner inhärenten Komplexität ebenfalls kompliziert sein muss.

    In den meisten Fällen ist das jedoch nicht der Fall.

    Destructuring ist kein Persönlichkeitstest

    Einige Entwickler verwenden Destructuring so übermäßig, dass gewöhnlicher Code schwerer zu verstehen wird.

    Nehmen wir das hier:

    const { name, email, address: { city } } = user;
    

    Dafür gibt es seinen Platz.

    Aber manchmal liest sich das folgende Beispiel klarer:

    const name = user.name;
    const email = user.email;
    const city = user.address.city;
    

    Es gibt keine Bonuspunkte dafür, mehr Syntax in eine einzige Zeile zu quetschen.

    Destructuring lohnt sich, wenn es die Verbindung zwischen dem Code und den zugrunde liegenden Daten klarer macht.

    Zum Beispiel:

    function createUser({ name, email }) {
      // ...
    }
    

    Das ist ausdrucksstark.

    Es teilt dem Leser genau mit, welche Objektstruktur die Funktion erwartet.

    Aber sobald die Dekonstruktion so tief verschachtelt wird, dass der Leser die ursprüngliche Objektstruktur im Kopf wieder aufbauen muss, hilft diese Funktion nicht mehr, sondern wirkt gegen den Nutzer.

    Modernes JavaScript bleibt einfach, wenn man die Syntax als Mittel zur Kommunikation betrachtet und nicht als Dekoration.

    Optional Chaining hat ein echtes Problem gelöst

    Einige Funktionen reduzieren tatsächlich die Unordnung.

    Optional Chaining ist ein gutes Beispiel dafür.

    Bevor es existierte, sah man oft etwas wie:

    if (
      user &&
      user.profile &&
      user.profile.settings &&
      user.profile.settings.theme
    ) {
      return user.profile.settings.theme;
    }
    

    Nun kann man schreiben:

    return user?.profile?.settings?.theme;
    

    Das ist nicht nur kürzer.

    Es drückt die Absicht direkt aus.

    Der Entwickler sagt im Grunde:

    Der Pfad könnte nicht existieren. Falls nicht, sicher abbrechen.

    Die gleiche Logik gilt für nullish coalescing:

    const pageSize = options.pageSize ?? 20;
    

    was häufig präziser ist als:

    const pageSize = options.pageSize || 20;
    

    Denn || behandelt falsche Werte wie 0 oder "", als wären sie nicht vorhanden, während ?? nur auf null oder undefined reagiert.

    Eine solche Funktion ist es wert, gelernt zu werden, da sie unbeabsichtigte Komplexität beseitigt anstatt stilistische Effekte hinzuzufügen.

    Hier ist ein nützlicher Filter, den man auf jede neue Syntax anwenden kann:

    Macht das den Code verständlicher?

    Falls die Antwort ja lautet, sollte man sie erlernen.

    Falls der Code dadurch nur beeindruckender aussieht, ist Vorsicht geboten.

    Modernes JavaScript bedeutet nicht notwendigerweise funktionale Programmierkunststücke

    Es gibt eine weitere häufige Quelle vermeidbarer Komplexität: die Annahme, dass modernes JavaScript unbedingt im stark funktionalen Stil geschrieben werden muss.

    Oft stößt man auf etwas in der Art:

    const result = users
      .filter(user => user.active)
      .map(user => ({
        ...user,
        displayName: `${user.firstName} ${user.lastName}`
      }))
      .reduce((groups, user) => {
        const key = user.department;
        groups[key] ??= [];
        groups[key].push(user);
        return groups;
      }, {});
    

    Daran ist an sich nichts auszusetzen.

    Aber man vergisst leicht, dass eine einfache Schleife dennoch völlig gültiger JavaScript-Code ist.

    Auch diese Version kann genauso sinnvoll sein:

    const result = {};
    
    for (const user of users) {
      if (!user.active) continue;  const department = user.department;  if (!result[department]) {
        result[department] = [];
      }  result[department].push({
        ...user,
        displayName: `${user.firstName} ${user.lastName}`
      });
    }
    

    Die Schleifenversion benötigt mehr Zeilen.

    Außerdem kann es beim Debuggen einfacher sein, sie Schritt für Schritt durchzugehen.

    Professioneller Code wird nicht danach beurteilt, wie wenige Zeilen er verwendet.

    Er wird danach beurteilt, wie wenig unnötige kognitive Anstrengung er von demjenigen verlangt, der ihn später liest.

    Mannchmal verringern map, filter und reduce diese Anstrengung.

    Mannchmal erledigt eine Schleife die Aufgabe besser.

    Es ist wichtiger zu wissen, wann man welches Werkzeug verwenden sollte, als sich jedes verfügbare Array-Methoden zu merken.

    JavaScripts Objektmodell wird einfacher, sobald man aufhört, dagegen anzukämpfen

    Viel der Verwirrung um JavaScript entsteht dadurch, dass man versucht, es in das Denkmuster einer anderen Sprache zu pressen.

    JavaScript-Objekte können einfach Daten speichern:

    const user = {
      name: "Sarah",
      age: 31
    };
    

    Sie können auch Funktionen speichern:

    const user = {
      name: "Sarah",
    
      greet() {
        return `Hello, ${this.name}`;
      }
    };
    

    Man kann Klassen definieren:

    class User {
      constructor(name) {
        this.name = name;
      }
    
      greet() {
        return `Hello, ${this.name}`;
      }
    }
    

    Aber Klassen sind nicht in jeder Situation zwingend erforderlich.

    Ein einfaches Objekt in Kombination mit einer Factory-Funktion ist oft genauso verständlich:

    function createUser(name) {
      return {
        name,
    
        greet() {
          return `Hello, ${name}`;
        }
      };
    }
    

    Keiner der Stile ist in jedem Fall überlegen.

    Die wahre Fähigkeit besteht darin, zu erkennen, was ein bestimmter Codeabschnitt tatsächlich benötigt.

    Klassen sind sinnvoll, wenn es um echte Objektidentität, Verhalten, Lebenszyklusverwaltung, Vererbung oder Kapselung geht.

    Einfache Objekte eignen sich eher für einfache Daten.

    Funktionen eignen sich eher für Transformationen.

    Module eignen sich in der Regel für die Trennung zwischen verschiedenen Teilen eines Systems.

    Ihre Sprache unterstützt zwar Klassen, aber Sie müssen nicht jedes Konzept unbedingt in eine Klasse umwandeln.

    Das Modulsystem zählt zu den stärksten Eigenschaften von JavaScript

    Native Module lösen leise und effizient ein Problem, das früher viel unnötige Komplexität verursacht hat.

    Betrachten Sie dieses Muster:

    // math.js
    export function add(a, b) {
      return a + b;
    }
    

    In Kombination mit:

    // app.js
    import { add } from "./math.js";
    
    console.log(add(2, 3));
    

    Diese Konfiguration schafft klare Abgrenzungen bei Abhängigkeiten. Das importierende Modul lädt nur das, was es tatsächlich benötigt. Die Abhängigkeit bleibt im Code sichtbar. Sie können die Funktion auch einzeln testen. Dabei ist es nicht notwendig, alles in einen umfangreichen globalen Namespace zu packen.

    Was das Besondere daran macht, ist, dass man kein Framework benötigt, um Modularität zu erreichen. Die Sprache selbst ermöglicht es, klare Grenzen zwischen den Teilen des Systems zu ziehen. Ein Projekt kann beispielsweise wie folgt strukturiert sein:

    src/
      users.js
      orders.js
      payments.js
      validation.js
      config.js
      app.js
    

    ohne dass jedes Datei ein Designmuster demonstrieren muss.

    Ein gut strukturiertes Modul macht allein durch den Blick darauf drei Dinge offensichtlich: welche Funktionalität dazu gehört, welche Teile dieser Funktionalität es nach außen zugänglich macht und auf welche anderen Codeabschnitte es zur Funktionsfähigkeit angewiesen ist.

    Sobald diese Aspekte klar sind, befindet sich das Projekt bereits in einem ordentlichen Zustand.

    Nicht alles muss abstrahiert werden

    Das ist vermutlich der wichtigste Punkt hier.

    Es ist üblich, dass Entwickler Abstraktionen einführen, sobald sie wiederholten Code entdecken. Doch Wiederholung an sich ist nicht unbedingt ein Problem, das gelöst werden muss.

    Nehmen wir an, Sie haben:

    const user = await fetch("/api/users/42");
    const order = await fetch("/api/orders/18");
    

    Zu einem bestimmten Zeitpunkt werden Sie vielleicht feststellen, dass die Logik zur Verarbeitung der Antwort immer wiederholt wird. Diese Beobachtung könnte Sie dazu veranlassen, Folgendes zu schreiben:

    async function getJson(url) {
      const response = await fetch(url);
    
      if (!response.ok) {
        throw new Error(`Request failed: ${response.status}`);
      }  return response.json();
    }
    

    Dadurch können Sie es vereinfachen zu:

    const user = await getJson("/api/users/42");
    const order = await getJson("/api/orders/18");
    

    Diese Abstraktion hat ihren Platz – sie erfüllt eine klare Aufgabe.

    Vergleichen Sie das nun mit dem Aufbau einer generischen Anfragenfabrik, eines Anfrage-Strategie-Musters, einer Repository-Schnittstelle, eines Transport-Adapters, eines Antwort-Wrappers sowie einer vollständigen Service-Schicht, noch bevor Sie überhaupt einen zweiten API-Aufruf durchgeführt haben. In diesem Szenario haben Sie die Komplexität eigentlich nicht verringert – Sie haben sie lediglich in die Architektur selbst verlagert.

    Dieses Muster taucht ständig in JavaScript-Codebasen auf. Ein Entwickler bemerkt zwei ähnliche Zeilen und beginnt sofort, für eine hypothetische Zukunft zu entwerfen. Gute Ingenieurregeln empfehlen oft den gegenteiligen Ansatz.

    Warten Sie, bis die Wiederholung deutlich genug wird, sodass sich die richtige Abstraktion zeigt.

    Abstraktionen, die zu früh eingeführt werden, sind bekanntermaßen schwer wieder zu entfernen – hauptsächlich weil letztendlich niemand mit Sicherheit sagen kann, ob sie noch nützliche Arbeit leisten.

    Der Browser erledigt bereits viel für Sie

    Modernes JavaScript kann teilweise komplizierter erscheinen, als nötig ist – weil Entwickler oft übersehen, wie viel die Plattform selbst bereits leisten kann.

    Man kann den DOM direkt manipulieren, Event-Listener hinzufügen, Netzwerkanfragen senden, auf Web Storage zugreifen, Web Workers starten, WebSocket-Verbindungen herstellen, die Browserhistorie verwalten sowie mit nativen Formularen arbeiten.

    All das steht zur Verfügung, ohne für jede kleine Aufgabe ein separates Paket verwenden zu müssen. Zum Beispiel:

    const button = document.querySelector("#save");
    
    button.addEventListener("click", async () => {
      const response = await fetch("/api/save", {
        method: "POST",
        headers: {
          "Content-Type": "application/json"
        },
        body: JSON.stringify({ message: "Hello" })
      });  if (!response.ok) {
        throw new Error("Save failed");
      }
    });
    

    Das ist eine voll funktionsfähige Interaktion in einer echten Anwendung – ohne Einsatz eines Frameworks.

    Nichts davon ist ein Argument gegen Frameworks – sie lösen tatsächliche Probleme. Doch die zugrunde liegende Plattform verdient mehr Anerkennung, als sie oft erhält. Sobald man versteht, was der Browser und die Programmiersprache bereits kostenlos zur Verfügung stellen, ist man in einer viel besseren Position, zu entscheiden, wofür sich ein Framework eigentlich verantwortlich zeigen sollte.

    Abhängigkeiten haben ihren Preis

    Das Installieren eines Pakets kann echte Zeit sparen. Gleichzeitig kann es einen zu langfristigen Verpflichtungen verleiten, wenn man etwas hätte, das selbst mit zehn Codezeilen geschrieben werden könnte.

    Nehmen wir ein kleines Hilfsprogramm wie:

    function clamp(value, min, max) {
      return Math.min(Math.max(value, min), max);
    }
    

    Es ist sehr wahrscheinlich, dass Sie dafür kein externes Paket benötigen. Doch Aspekte wie Authentifizierung, Kryptografie, komplexe Datenergebnisse, Datenbankklienten, Bildverarbeitung sowie ausgereifte Validierungslogiken gehören in eine völlig andere Kategorie.

    Die Lektion hier lautet nicht „Vermeiden Sie Abhängigkeiten um jeden Preis“. Es geht darum, sich bewusst zu sein, was jede Abhängigkeit tatsächlich kostet.

    Jede Abhängigkeit bringt mit sich:

    • kontinuierliche Wartung
    • Sicherheitsaspekte
    • Aktualisierungsarbeiten der Versionen
    • Dokumentation, die gelesen werden muss
    • eine Hierarchie von transitiven Abhängigkeiten
    • Einflüsse auf Ihren Build-Prozess
    • eine weitere Abstraktion, die Sie lernen müssen

    Die besten JavaScript-Entwickler sind nicht unbedingt diejenigen, die sich am meisten npm-Pakete eingeprägt haben. Es sind vielmehr diejenigen, die erkennen können, wann ein Paket tatsächlich nicht benötigt wird.

    TypeScript erhöht die wahrgenommene Komplexität, aber das ist eine andere Diskussion

    In größeren Codebasen kann TypeScript die tägliche Entwicklungsarbeit erheblich verbessern. Gleichzeitig fügt es einer Sprache eine zusätzliche Schicht hinzu.

    Damit schreibt man letztendlich Dinge wie:

    type User = {
      id: number;
      name: string;
      email: string;
    };
    

    Zusätzlich dazu:

    function getUser(id: number): Promise<User> {
      // ...
    }
    

    Das hat tatsächlich Wert. Es ist jedoch wichtig, die durch die Sprache verursachte Komplexität von der durch die Einstellungen der Entwicklungstools entstehenden Komplexität zu trennen.

    JavaScript selbst wird nicht einfach dadurch schwieriger, dass ein Projekt Typüberprüfungen, einen Linter, einen Module-Bundler, ein UI-Framework, eine automatisierte Testsuite sowie mehrere Build-Plugins hinzufügt. Was komplexer geworden ist, ist die Entwicklungsumgebung, und diese Erweiterung kann durchaus Vorteile bringen.

    Aber ein aufwendiges Werkzeugset sollte nicht dazu führen, dass man annimmt, die darunterliegende Sprache sei von Natur aus kompliziert. Oft ist sie tatsächlich ziemlich einfach.

    Die wahre Fähigkeit besteht darin, die kleinste nützliche Abstraktion zu wählen

    Ein Muster durchzieht alles, was bisher besprochen wurde.

    Gutes JavaScript schreiben hat weniger damit zu tun, jede verfügbare Funktion zu beherrschen, und mehr damit, das effizienteste Werkzeug auszuwählen, um die Aufgabe zu erledigen.

    Muss man den Inhalt eines Arrays umwandeln? Nutzt man map.

    Muss man eine Menge von Werten eingrenzen? Nutzt man filter.

    Muss man asynchrone Aufgaben sequenzieren? Nutzt man async und await.

    Braucht man einen wiederverwendbaren Logikteil mit klaren Grenzen? Schreibt man eine Funktion oder ein Modul.

    Braucht man eine Sammlung, bei der die Schlüssel nicht auf Zeichenketten beschränkt sind? Schaut man sich Map an.

    Muss die Eindeutigkeit garantiert werden? Schauen Sie sich Set an.

    Muss man sicher auf ein möglicherweise fehlendes Attribut zugreifen? Verwenden Sie die optionale Kettensuche.

    Braucht man einen Standardwert nur, wenn etwas null oder undefined ist? Verwenden Sie ??.

    Braucht man einen einfachen Datenträger? Ein Objekt reicht aus.

    Braucht man ein umfangreicheres Verhalten sowie eine Identität, die mit den Daten verbunden ist? Eine Klasse könnte geeignet sein.

    Muss man eine wirklich große Anwendung strukturieren? Dann lohnt es sich, ein Framework in Betracht zu ziehen.

    Achten Sie auf diese Reihenfolge.

    Man beginnt mit der Sprache selbst.

    Dann schaut man sich an, was die Plattform bereits bietet.

    Dann kommen kleine, speziell entwickelte Abstraktionen.

    Dann externe Bibliotheken.

    Schließlich Frameworks.

    Durch Umkehrung dieser Reihenfolge entsteht viel unnötige Komplexität.

    Einfachheit bedeutet nicht das Fehlen von Ingenieurskunst

    Es gibt eine weit verbreitete, aber irreführende Annahme darüber, was „einfacher“ Code eigentlich bedeutet.

    Manche Leute setzen Einfachheit mit mangelnder Erfahrung gleich.

    Diese Gleichsetzung hält nicht stand.

    Betrachten wir jemanden, der neu im Bereich Programmierung ist und etwas wie Folgendes schreibt:

    function process(data) {
      // everything
    }
    

    Auf den ersten Blick wirkt es minimalistisch, doch es ist tatsächlich sehr schwierig, es zu warten, sobald es größer wird.

    Ein erfahrenerer Entwickler würde dagegen dieselbe Logik in fünf kleine Module aufteilen, wobei jedes Modul klare, definierte Grenzen hat.

    Dadurch gibt es zwar mehr Komponenten, doch es ist tatsächlich viel einfacher, damit umzugehen.

    Das ist eine andere, sinnvollere Art von Einfachheit.

    Ziel ist es nicht, die Anzahl der Dateien, die Abhängigkeitsliste oder die Zeilenanzahl zu verringern.

    Ziel ist es, Komplexitäten zu beseitigen, die nicht notwendig sind.

    Dieser Unterschied wird mit wachsenden Codebasen immer wichtiger.

    Ein Script, das nur zwanzig Zeilen lang ist, kann dennoch verwirrend sein.

    Eine Anwendung mit zweitausend Zeilen kann überraschenderweise leicht verständlich sein.

    Der entscheidende Faktor ist in der Regel nicht die reine Menge an Code.

    Vielmehr geht es darum, ob dieser Code das zugrunde liegende Modell für den Leser verständlich macht.

    Hören Sie auf, jede neue Funktion als zwingendes Wissen zu betrachten

    JavaScript entwickelt sich rasant, und dieses Tempo erzeugt eine eigene Art von Stress.

    Man gewöhnt sich an eine bestimmte Vorgehensweise, und schon nach ein paar Monaten dominiert ein anderer Ansatz alle Diskussionen.

    Ein neues Framework taucht auf.

    Ein neuer Laufzeitumgebung gewinnt an Bedeutung.

    Ein neues Rendering-Muster wird zum Trend.

    Ein neuer Syntax-Teil wird veröffentlicht.

    Es ist leicht, das Gefühl zu haben, ständig zurückzufallen.

    Wahrscheinlich ist das aber nicht der Fall.

    Man muss nicht sofort jede neue JavaScript-Technik erlernen, sobald jemand einen Beitrag darüber schreibt.

    Investieren Sie stattdessen in die Grundlagen.

    Werden Sie mit asynchroner Programmierung vertraut.

    Werden Sie mit dem Konzept des Scope vertraut.

    Werden Sie mit Closures vertraut.

    Werden Sie mit Objekten vertraut.

    Werden Sie mit Modulen vertraut.

    Werden Sie mit dem Event Loop vertraut.

    Werden Sie mit Promises vertraut.

    Werden Sie mit der Fehlerbehandlung vertraut.

    Werden Sie mit den APIs vertraut, die Ihr Browser oder die Laufzeitumgebung bereitstellen.

    Werden Sie mit der Nachverfolgung des Datenflusses in einer Anwendung vertraut.

    Nur danach sollten Sie neue Werkzeuge erlernen – und zwar nur, wenn ein echtes Problem sie erfordert.

    Derartige Vorgehensweisen schaffen anhaltendes Wissen.

    Die Vertrautheit mit einem bestimmten Framework kann veralten.

    Ein solides mentales Modell davon, wie Programme funktionieren, hingegen nicht.

    Der beste JavaScript-Code sieht oft langweilig aus

    Entwickler kommen irgendwann zu dieser Erkenntnis.

    Code, der am einfachsten zu warten ist, beeindruckt selten beim ersten Lesen.

    Er stapelt keine fünf Abstraktionen übereinander.

    Er erfordert kein Diagramm, damit die erste Funktion verständlich wird.

    Er nutzt nicht jede Funktion, die die Sprache in ihrer neuesten Version hinzugefügt hat.

    Oft sieht er einfach nur unspektakulär aus.

    Das ist keine Kritik, es ist eine Stärke.

    Man öffnet die Datei und der Zweck ist sofort klar:

    import { getUser } from "./users.js";
    
    export async function loadProfile(id) {
      const user = await getUser(id);  if (!user) {
        throw new Error("User not found");
      }  return {
        name: user.name,
        email: user.email
      };
    }
    

    Es gibt fast nichts zu entschlüsseln.

    Die Funktion holt einen Benutzer ab.

    Sie prüft, ob die Abfrage erfolgreich war.

    Dann gibt sie die tatsächlich benötigten Daten zurück.

    Hier versucht nichts, zu beweisen, wie klug der Autor ist.

    Es vermeidet einfach, dass der Leser unnötige Arbeit leisten muss – und das ist eine weitaus wertvollere Eigenschaft in Code.

    Modernes JavaScript ist einfacher als unsere Projekte

    JavaScript weist durchaus einige echte Unzulänglichkeiten auf. Jede Sprache hat solche.

    Aber der Großteil der Komplexität, die den Menschen dem „modernen JavaScript“ zuschreiben, stammt eigentlich nicht von der Sprache selbst.

    Sie entsteht durch die Schichten, die darauf aufgebaut sind.

    Frameworks.

    Build-Systeme.

    Abhängigkeiten.

    Konventionen.

    Abschreibungen.

    Tools.

    Architektonische Entscheidungen.

    Einige dieser Schichten sind tatsächlich gerechtfertigt.

    Andere nicht.

    Die richtige Frage ist nicht, ob ein Tool als modern gilt.

    Die richtige Frage ist, ob es tatsächlich die Komplexität für das konkrete Problem vor Ihnen verringert.

    Bevor Sie sich eine weitere Abhängigkeit besorgen, überprüfen Sie, ob die Plattform sie bereits abdeckt.

    Bevor Sie eine weitere Abstraktion einführen, prüfen Sie, ob die Duplikation, die Sie beheben wollen, tatsächlich existiert.

    Bevor Sie eine weitere architektonische Schicht hinzufügen, prüfen Sie genau, welchen Fehler sie verhindern soll.

    Bevor Sie ein weiteres Framework wählen, überprüfen Sie, welche Einschränkung in Ihrer aktuellen Konfiguration Sie tatsächlich beheben möchten.

    Und bevor Sie zu dem Schluss kommen, dass JavaScript an sich zu kompliziert ist, versuchen Sie, das Problem ausschließlich mit der Sprache sowie der Plattform zu lösen, auf der sie läuft.

    Das Ergebnis könnte Sie überraschen.

    Die Sprache ist oft der einfachste Bestandteil des Ganzen.

    Es sind die Entwickler, die ständig weitere Schichten darauf stapeln.

    Mannchmal ist der fortschrittlichste Schritt, den Sie unternehmen können, eine Schicht wegzunehmen anstatt eine hinzuzufügen.

    Der beste JavaScript-Code ist nicht der, der die meisten Tools verwendet.

    Es ist der Code, in dem jedes verwendete Tool wirklich seinen Platz verdient hat.