Das Prinzip der Einfachheit im Software-Design

30. August 2025
ℹ️Hinweis zur Quelle

Dieser Blogartikel wurde automatisch erstellt (und übersetzt). Er basiert auf dem folgenden Original, das ich für die Veröffentlichung auf diesem Blog ausgewählt habe:
Do the simplest thing that could possibly work.

Das Prinzip der Einfachheit im Software-Design

Beim Entwurf von Softwaresystemen sollte man sich auf das Einfachste konzentrieren, was funktionieren könnte. Es überrascht, wie weit man mit diesem Ratschlag kommt. Dieser Ansatz kann bei der Behebung von Fehlern, der Wartung bestehender Systeme und der Gestaltung neuer Architekturen verfolgt werden.

Viele Entwickler entwerfen Systeme, indem sie versuchen, das "ideale" System zu konzipieren: etwas gut strukturiertes, nahezu unendlich skalierbares, elegant verteiltes usw. Es lässt sich argumentieren, dass dies der falsche Ansatz für Softwaredesign ist. Stattdessen sollte man die Zeit nutzen, um das aktuelle System tiefgehend zu verstehen und dann das Einfachste zu tun, was funktionieren könnte.

Junior-Entwickler wollen Tools wie App-Server, Proxys, Datenbanken, Caches und Queues nutzen. Es macht Spaß, Systeme aus vielen verschiedenen Komponenten zusammenzusetzen, und es fühlt sich befriedigend an, auf einem Whiteboard Kästchen und Pfeile zu zeichnen – so, als ob man echtes Engineering betreibt.

Echte Meisterschaft beinhaltet jedoch oft, zu lernen, wann man weniger tun sollte, nicht mehr. Im Softwarebereich bedeutet dies, dass großartiges Softwaredesign unspektakulär aussieht. Es sieht nicht so aus, als ob viel passieren würde. Man erkennt großartiges Softwaredesign daran, dass man Gedanken hat wie "Oh, ich wusste nicht, dass das Problem so einfach war" oder "Oh, gut, man muss eigentlich nichts Schwieriges tun".

Beispiele für einfaches, aber effektives Design

Einige Beispiele für großartiges Softwaredesign sind:

  • Unicorn: Ein Webserver, der alle wichtigen Garantien (Request Isolation, horizontale Skalierung, Crash Recovery) durch Unix-Primitive bietet.
  • Rails REST API: Bietet genau das, was für eine CRUD-Anwendung benötigt wird, auf die einfachste Art und Weise.

Diese Beispiele sind nicht unbedingt beeindruckend, aber sie sind beeindruckende Leistungen, weil sie das Einfachste tun, was funktionieren kann.

Die Anwendung des Prinzips

Angenommen, eine Golang-Anwendung soll um eine Art Rate Limiting erweitert werden. Was ist das Einfachste, was funktionieren könnte? Die erste Idee könnte sein, persistenten Speicher (z. B. Redis) hinzuzufügen, um die Anfrageanzahl pro Benutzer mit einem Leaky-Bucket-Algorithmus zu verfolgen. Das würde funktionieren! Aber benötigt man dafür eine völlig neue Infrastruktur? Was wäre, wenn man die Anfrageanzahl pro Benutzer stattdessen im Speicher hält? Sicher, man würde einige Rate-Limiting-Daten verlieren, wenn die Anwendung neu gestartet wird, aber spielt das eine Rolle? Ist man sich eigentlich sicher, dass der Edge-Proxy nicht bereits Rate Limiting unterstützt? Könnte man einfach ein paar Zeilen in eine Konfigurationsdatei schreiben, anstatt die Funktion überhaupt zu implementieren?

Vielleicht unterstützt der Edge-Proxy kein Rate Limiting. Vielleicht kann man es nicht im Speicher verfolgen, weil zu viele Serverinstanzen parallel laufen, sodass die strengste Rate Limit, die man auf diese Weise durchsetzen könnte, zu weit gefasst ist. Vielleicht ist es ein Dealbreaker, wenn man jemals Rate-Limiting-Daten verliert, weil Leute den Dienst hart belasten. In diesem Fall ist das Einfachste, was funktionieren könnte, das Hinzufügen von persistentem Speicher, also sollte man das tun. Aber wenn man einen der einfacheren Ansätze wählen könnte, würde man das nicht wollen?

Man kann eine ganze Anwendung von Grund auf auf diese Weise erstellen: Man beginnt mit dem absolut Einfachsten und erweitert es dann nur, wenn neue Anforderungen einen dazu zwingen. Es klingt albern, aber es funktioniert. Es kann als oberstes Designprinzip angesehen werden.

Was spricht gegen das einfachste Vorgehen?

Es gibt drei Hauptprobleme, wenn man immer das Einfachste tut, was funktionieren könnte:

  1. Durch das Nicht-Antizipieren zukünftiger Anforderungen erhält man ein unflexibles System.
  2. Es ist nicht klar, was "einfachstes" bedeutet.
  3. Man sollte Systeme bauen, die skalieren können, nicht nur Systeme, die im Moment funktionieren.

Einige Entwickler könnten denken, dass "das Einfachste tun, was funktionieren könnte" bedeutet, mit dem Engineering aufzuhören. Aber sind Hacks einfach? Das Problem mit einem Hack ist gerade, dass er nicht einfach ist: dass er die Komplexität der Codebasis erhöht, indem er eine weitere Sache einführt, an die man sich immer erinnern muss. Das Herausfinden der richtigen Lösung ist schwierig, weil es erfordert, die gesamte Codebasis (oder große Teile davon) zu verstehen. Tatsächlich ist die richtige Lösung fast immer viel einfacher als der Hack.

Es ist nicht einfach, das Einfachste zu tun, was funktionieren könnte. Wenn man sich ein Problem ansieht, sind die ersten paar Lösungen, die einem in den Sinn kommen, wahrscheinlich nicht die einfachsten. Das Herausfinden der einfachsten Lösung erfordert die Berücksichtigung vieler verschiedener Ansätze. Mit anderen Worten, es erfordert Engineering.

Definition von Einfachheit

Entwickler sind sich oft uneins darüber, was einfachen Code ausmacht. Hier eine grobe, intuitive Definition von Einfachheit:

  1. Einfache Systeme haben weniger "bewegliche Teile": weniger Dinge, über die man nachdenken muss, wenn man mit ihnen arbeitet.
  2. Einfache Systeme sind weniger miteinander verbunden. Sie bestehen aus Komponenten mit klaren, unkomplizierten Schnittstellen.

Wenn man sich nicht sicher ist, was "einfacher" erscheint, kann man folgenden Tie-Breaker verwenden: Welcher Zustand erfordert mehr fortlaufende Arbeit? Wenn der Vergleich zweier Zustände eines Softwaresystems ergibt, dass einer mehr fortlaufende Arbeit erfordert, ist der andere einfacher.

Warum sollte man nicht skalierbar sein wollen?

Einige Entwickler schreien jetzt innerlich "Aber In-Memory Rate Limiting ist nicht skalierbar!" Das Einfachste zu tun, was funktionieren könnte, wird nicht unbedingt das web-skalierbarste System liefern. Es wird ein System liefern, das im aktuellen Maßstab gut funktioniert. Ist das verantwortungsloses Engineering?

Die Hauptsünde des Big-Tech-SaaS-Engineering ist eine Besessenheit von Skalierung. Es wurden Systeme überentwickelt, um sich auf mehrere Größenordnungen mehr als die aktuelle Skalierung vorzubereiten. Das macht die Codebasis unflexibel. Es ist zwar schön, den Dienst in zwei Teile zu entkoppeln, damit sie unabhängig skaliert werden können, aber das macht bestimmte Funktionen sehr schwer zu implementieren, da sie nun eine Koordination über das Netzwerk erfordern.

Es ist schwierig genug, den aktuellen Stand eines Systems zu verstehen. Und in der Tat ist das die größte praktische Schwierigkeit beim guten Design: ein genaues, umfassendes Verständnis des Systems zu erlangen. Das meiste Design wird ohne dieses Verständnis durchgeführt, und das meiste Design ist daher ziemlich schlecht.

Es gibt im Großen und Ganzen zwei Möglichkeiten, Software zu entwickeln. Die erste ist, vorherzusagen, wie die Anforderungen in sechs Monaten oder einem Jahr aussehen könnten, und dann das beste System für diesen Zweck zu entwerfen. Die zweite ist, das beste System für das zu entwerfen, wie die Anforderungen tatsächlich im Moment aussehen: mit anderen Worten, das Einfachste zu tun, was funktionieren könnte.


Die Kommentare sind geschlossen.