6 Punkte von GN⁺ 2025-01-01 | 2 Kommentare | Auf WhatsApp teilen
  • Vorschläge für Systemdesigns, die mit „let’s just“ beginnen, werden meist komplexer als erwartet; gute Architekturentscheidungen hängen stark von Erfahrungsregeln und Kontextverständnis ab
  • Plugin-Architekturen, zusätzliche APIs und Abstraktionsschichten wirken plausibel, müssen in der Praxis aber Verhaltenskompatibilität, Wartbarkeit, Sicherheit, Performance und Anforderungen des Ökosystems zugleich tragen
  • Asynchrone Verarbeitung, Zugriffskontrolle und Datensynchronisierung wirken wie vertraute Themen, führen in Produktumgebungen aber leicht zu schwer reproduzierbaren Bugs, einer Neugestaltung des Sicherheitsmodells und Synchronisationsproblemen
  • Cross-Platform und native Escape Hatches können bei einfachen frühen Produkten sinnvoll sein; sobald Plattformfunktionen und interner Zustand auseinanderlaufen, wird es jedoch schwer, Qualität und Konsistenz zu wahren
  • Diese Muster sind nicht immer falsch, doch meistens sind sie unnötig oder es gibt Alternativen; statt zu einem fehleranfälligen Ansatz zu greifen, sollte man das Problem ausgehend von First Principles neu lösen

Warum „let’s just“ gefährlich ist

  • Vorschläge, die auf „let’s just“ folgen, werden in 9 von 10 Fällen zu weitaus komplexeren Aufgaben, als man im Konferenzraum erwartet hatte
  • Engineering hat auch eine sozialwissenschaftliche Seite: Was funktioniert, ist kontextabhängig
  • Wenn man sagt, dass ein Ansatz nicht funktioniert, wird das von Engineers leicht als Herausforderung verstanden, sofort ein Gegenbeispiel zu beweisen
  • Ein großer Teil von Engineering Management und Softwarearchitektur besteht aus einer Kombination von aus Erfahrung gewonnenen Erfahrungsregeln (rules of thumb) und mühsam gelernten Lektionen

„Machen wir es doch einfach plugin-fähig“

  • Wenn eine einzelne Implementierung unzureichend erscheint, wirkt es so, als könnten API-Aufrufer Verbesserungen oder neue Funktionen erhalten, ohne sich ändern zu müssen, indem man eine neue Implementierung in dieselbe Architektur einsetzt
  • Doch weil „die API nicht die Header-Datei oder Dokumentation ist, sondern das Verhalten selbst“, gibt es kaum Plugins, die einfach nur funktionieren
  • Die Komponenten, die in moderner Software Plugins am nächsten kommen, sind Device Driver
    • Früher war die Verhaltensqualität von Treibern so schlecht, dass sie heute entweder nicht mehr akzeptiert wird oder moderne Betriebssysteme dazu übergehen, eigene Treiber zu bauen
  • Wer eine wirklich plugin-fähige Struktur schaffen will, muss neben der Basisimplementierung gleichzeitig auch eine zweite Implementierung entwerfen; erst dann gibt es zumindest einen Nachweis, dass es einmal funktioniert

„Fügen wir doch einfach eine API hinzu“

  • Nachdem ein Produkt oder Unternehmen einen gewissen Erfolg erreicht hat, wird häufig eine API hinzugefügt, mit dem Argument, man müsse „zur Plattform werden und Entwickler anziehen“
  • API-Anbieter müssen ständig zwischen neuen Funktionen und Kompatibilität sowie Interoperabilität abwägen; bestehendes Verhalten und Performance-Eigenschaften schränken die Änderungsfreiheit stark ein
  • Nur weil es eine API gibt, heißt das nicht, dass jemand sie auch unbedingt nutzen möchte
    • Neue APIs entstehen oft dann, wenn ein Produkt eine bestimmte Funktion will, ihr intern aber keine ausreichend hohe Priorität einräumt
    • Der Zielmarkt ist klein, vertikal spezialisiert oder auf eine bestimmte Domäne begrenzt, und man erwartet, dass externe Partner die Lücke per API schließen
    • Auch diese Partner haben jedoch ihr eigenes Geschäft und ihre eigenen Kunden und wollen ein Problem womöglich nicht dadurch lösen, dass sie ein weiteres Produkt einführen
  • Eine Plattform zu werden ist ein Geschäft mit realer Nachfrage; nur ein paar APIs bereitzustellen schafft für Dritte nur selten eine wirtschaftliche Grundlage

„Fügen wir noch eine Abstraktionsebene hinzu“

  • In Butler Lampsons Aussage „Alle Probleme der Informatik lassen sich durch eine weitere Indirektionsebene lösen“ steckt echte Wahrheit
  • Scheitern zeigt sich vor allem auf zwei Arten
    • Zu früh eingeführte Abstraktionen bleiben ohne konkreten Nutzungsplan als Überabstraktion in der Architektur zurück
    • Später hinzugefügte Abstraktionen können Wartung, Sicherheit und Performance-Optimierung stark verkomplizieren
  • Windows NT enthielt von Anfang an viele Überabstraktionen, die tatsächlich nie genutzt wurden
  • In der Evolution von Mac OS gab es Fälle, in denen Abstraktionen, die zunächst seltsam wirkten, zwei Releases später nützlich wurden; der Unterschied war das Vorhandensein eines Plans
  • Wenn nachträgliche Abstraktionen nur in Teilen des Codes genutzt werden, bleibt viel Code übrig, der die neue Abstraktion nicht verwendet, wodurch die Wartungslast steigt

„Machen wir es asynchron“

  • Ein erheblicher Teil der ersten 25 Jahre der Informatik wurde darauf verwendet, asynchrones Verhalten zu verstehen und umzusetzen
  • In Graduiertenkursen der 1980er-Jahre behandelte man lange Themen wie die speisenden Philosophen, Producer-Consumer und den schlafenden Barbier
  • Viele Engineers arbeiten heute dank Regeln der Datenschicht und Web-Frameworks mit asynchronen Problemen in weitgehend abstrahierter Form
  • Wenn man außerhalb des Frameworks oder der Datenschicht Asynchronität direkt verwaltet, kann es zunächst gut wirken, bis ein Jahr später schwer reproduzierbare Bugs auftreten
  • Man kann nur hoffen, dass diese Bugs keine Datenkorruption verursachen

„Zugriffskontrolle fügen wir später hinzu“

  • Der Ort der Zugriffskontrolle war Gegenstand theoretischer Debatten, doch heutige Systeme stehen unter ständigem Angriff und bewegen sich daher in einer deutlich komplexeren Umgebung
  • Allen ist bewusst, dass Sicherheit von Anfang an nötig ist, aber wegen der Geschwindigkeit des Markteintritts gibt es kaum Systeme, deren Zugriffskontrolle und Sicherheitsmodell von Beginn an vollständig entworfen wurden
  • Ohne von der Perspektive der Kunden und Angreifer auszugehen, ist es schwer, ein zum Produkt passendes Zugriffskontroll-Design zu entwickeln
  • Zugriffskontrolle später anzuflanschen scheitert entweder oder führt später dazu, dass das Produkt neu geschrieben werden muss
  • Dieses Neuschreiben wird für alle Beteiligten, einschließlich der Kunden, zu einer schlechten Erfahrung

„Synchronisieren wir die Daten“

  • In Umgebungen mit mehreren Geräten, SaaS-Apps und Datenspeichern kommt häufig der Vorschlag auf: „Synchronisieren wir doch einfach die Daten“
  • Wie Ray Ozzie, ein Pionier von Client/Server und Datensynchronisierung, betonte, ist Synchronisierung ein schwieriges Problem
  • Ein schwieriges Problem in der Informatik bedeutet eines, das viele Herausforderungen enthält, die man nur durch Erfahrung lernt, und das sehr anspruchsvoll ist
  • Selbst bei Datenspeichern mit vollständiger Semantik und Transaktionen ist Synchronisierung schwierig; mit Blobs, unstrukturierten Daten und Datentransformationen steigt die Schwierigkeit sprunghaft
  • Eine Lösung auf Datensynchronisierung aufzubauen, ist fast nie die gewünschte Wahl; genau deshalb gibt es auch Unternehmen im Milliarden-Dollar-Bereich, die allein auf Synchronisierung beruhen

„Machen wir es Cross-Platform“

  • Cross-Platform ist eine seit Langem wiederkehrende Debatte; irgendjemand wird sagen, dass sein Code gut funktioniert, oder Unity und Spiele als Beispiele anführen
  • Wenn man verspricht, etwas Cross-Platform zu machen, ist das im Grunde fast so, als würde man versprechen, ein Betriebssystem, einen Cloud-Anbieter oder einen Browser zu bauen
  • Cross-Platform funktioniert in zwei Fällen gut
    • Wenn die Plattform neu und einfach ist, zum Beispiel wenn Cloud nur Compute und einfache Storage-Funktionen bedeutet
    • Wenn die Anwendung oder das Produkt neu und einfach ist
  • Diese Bedingungen brechen, sobald man sich von der zugrunde liegenden Plattform entfernt oder Funktionen baut, die auf den jeweiligen Zielplattformen völlig unterschiedlich dargestellt werden
  • Microsoft forked den Office-Code 1998, weil es immer schwieriger wurde, Office für Mac und Office für Windows aus demselben Code zu bauen, und kehrte danach nicht mehr zurück
  • Microsoft existierte ursprünglich als Unternehmen, das Cross-Platform-Apps baute; dieser Ansatz funktionierte jedoch in einer Zeit gut, in der die Dokumentation von OS-APIs etwa 100 Seiten umfasste und jedes Betriebssystem von CP/M abstammte
  • Verwandter Artikel: Divergent Thoughts on Cross Platform

„Wenn nötig, lassen wir einen nativen Ausweg zu“

  • Weil Cross-Platform nur für kurze Zeit gut funktioniert, bieten Frameworks und API-Abstraktionen oft einen native Escape Hatch
  • Die Idee dabei ist, dass man Funktionen, die die Plattform mit ihrer Weiterentwicklung bietet, das Framework aber noch nicht freilegt, direkt auf der nativen Plattform aufrufen kann
  • Frameworks oder APIs, die eine Abstraktion bereitstellen, halten jedoch internen Zustand oder Caches vor
  • Wenn man die native Plattform direkt aufruft, werden Datenstrukturen und Zustände verändert, von denen das Framework nichts weiß
  • Einige Frameworks bieten Mechanismen, um Daten oder Zustand zwischen Escape-Hatch-Code und Framework auszutauschen, doch das ähnelt eher einer Lösung, die in Zeiten automatischer Speicherverwaltung wieder eine Architektur wie malloc/free einführt

Man kann sie wählen, aber sie sind nicht der Default

  • Man muss auf solche Ansätze nicht immer mit „Nein“ antworten
  • In bestimmten Kontexten können sie funktionieren
  • In den meisten Fällen sind diese Muster jedoch unnötig oder es gibt bessere Wege
  • Statt zuerst zu einem Softwaremuster mit hoher Scheiternsquote zu greifen, sollte man das Problem ausgehend von First Principles lösen

2 Kommentare

 
ndrgrd 2025-01-02

Bei einem Plug-in ist es am wichtigsten, die zwingend nötigen Aktionen so weit wie möglich zu filtern und dann das Interface zu entwerfen.
Wenn man die Schnittstelle nur grob aus dem aktuellen Code übernimmt, wird sie zwangsläufig eine unnötige Schnittstelle, die an dieser konkreten Implementierung hängt; solche Fälle kommen wirklich häufig vor...

 
GN⁺ 2025-01-01
Meinungen auf Hacker News
  • Das Problem bei solchen Ideen liegt weniger in der Idee selbst als in dem vorangestellten „machen wir einfach mal“-Ansatz oder in den Erwartungen
    Wenn man zum Beispiel eine API nach dem Motto „fügen wir einfach eine API hinzu“ als „nur ein weiteres Feature“ des Produkts betrachtet, dürfte die Erfolgsquote ähnlich sein wie bei „fügen wir einfach eine UI hinzu“
    Um eine gute UI zu bauen, muss man sorgfältig und gründlich vorgehen, und man braucht auch Fachleute auf diesem Gebiet
    Warum sollte es bei einer anderen Schnittstelle des Produkts anders sein? Entscheidend ist weniger, ob es eine schlechte oder gute Idee ist, sondern dass es nichts ist, was man einfach mal so macht

    • Bei einem früheren Arbeitgeber gab es eine Regel
      Das Wort „einfach“ durfte nur der Entwickler verwenden, der tatsächlich dafür verantwortlich war, es zum Laufen zu bringen; sagte ein anderer Entwickler das, galt es als freiwillige Meldung, die Aufgabe selbst zu übernehmen
      Für uns hat diese Regel gut funktioniert
    • Genau. Beim Hinzufügen einer API gibt es kein „einfach“
      Eine API richtig zu bauen erfordert viel Design und Komplexität
      Ist sie schlecht entworfen, müssen Clients mehrere Aufrufe machen, wo einer reichen sollte, oder die API ist so verwirrend, dass sie falsch aufgerufen oder gar nicht genutzt wird
      Authentifizierung und Autorisierung sind ebenfalls nötig, also muss man OAuth2 einrichten oder zumindest sichere API-Tokens erzeugen, speichern und validieren können
      Auch die Daten müssen sicher verarbeitet werden, und bei schlechter Performance kann die Datenbank unter Last in die Knie gehen
      Fügt man Caching hinzu, kommen Cache-Invalidierung sowie zusätzliche Server- und Prozesskomplexität zur Cache-Unterstützung dazu
      Ohne Rate Limiting können schlampige Clients die API zuspammen, und bei mangelhafter Dokumentation ist sie praktisch nutzlos
      Je nach Fall muss man sogar SDKs für mehrere Sprachen bereitstellen, und ohne gute Fehlermeldungen wissen Erstnutzer nicht, warum ein Aufruf fehlschlägt
    • Die meisten professionellen Ratschläge ähneln Dating-Ratschlägen
      Man verallgemeinert, was bei einem selbst schiefgelaufen ist, aber wenn man nicht dieselbe Person in einer sehr ähnlichen Situation ist, lässt es sich kaum direkt anwenden
      Ratschläge, die beständig richtig sind, werden so allgemein wie „denk sorgfältig nach, versuch das Richtige zu tun und reflektiere die Ergebnisse“, dass sie fast nutzlos werden
      Mit solchen Sätzen verkauft man weder Blogposts noch Bücher besonders gut
    • Der Beruf Pre-Sales scheint fast ausschließlich wegen des Wortes „einfach“ zu existieren
      Schließlich muss man sicherstellen, dass Kunden nicht erschrecken, wenn man beginnt, den Rest des Eisbergs aus dem Wasser zu heben
    • Genau. Die Positionierung des Artikels wäre viel besser gewesen als „was tatsächlich nötig ist, um Systemideen einfach zum Laufen zu bringen
      Dann ginge es nicht um möglich oder unmöglich, sondern darum, auf welche Weise etwas gelingt oder scheitert
      Wenn man die nötigen Details kennt, wird es auch schwerer, „einfach“ zu sagen, ohne zu wissen, was man nicht weiß
      Bei DSLs stimme ich allerdings fast zu 100 % zu
      Das ist eher eine unnötige, niedlich wirkende Verkomplizierung, und wer berechtigt ist, so etwas zu bauen, sollte ungefähr jemand sein, der schon eine erfolgreiche Programmiersprache gebaut und unter Einbeziehung der Fehler eine zweite Auflage oder sogar eine zweite Sprache überarbeitet hat – und trotzdem noch vieles falsch gemacht hat
  • (1) DSLs funktionieren manchmal ausgesprochen gut. Siehe https://www.jooq.org/
    (2) Ein Elastic Load Balancer ist eine Regelschleife, die auf Workloads reagiert, und solche Dinge sind bereits eine allgemein verbreitete Technologie
    (3) In den meisten Branchen ist Unterprovisionierung weit verbreitet. Siehe https://erikbern.com/2018/03/27/waiting-time-load-factor-and... und https://www.amazon.com/Goal-Process-Ongoing-Improvement/dp/0...
    (4) Anomalieerkennung ist im Gegensatz zu den anderen Punkten nicht grundsätzlich ein Problem verteilter Systeme, aber wer sich einmal heftig die Finger verbrannt hat, kann sie durchaus für nötig halten
    Auch intellektuell ist das ein schwieriges Feld
    Der erste Algorithmus, bei dem ich das Gefühl hatte, dass er halbwegs clever ist, war https://scikit-learn.org/1.5/modules/outlier_detection.html#..., und manchmal funktioniert er wie durch ein Wunder
    Als ich ihn mit CNN-basierten Embeddings, wie ich sie 2018 verwendete, auf Text angewandt habe, passte er gut, mit SBERT hatte ich dagegen überhaupt kein Glück

    • Ich habe zwei DSLs geschrieben, eine davon zusammen mit einem Team, und ich würde sagen, beide waren erfolgreich
      Sie haben das Problem gelöst, und niemand hat darüber geflucht
      Der wichtigste Faktor war vermutlich, dass beide klein waren
      Sie waren einander sehr ähnlich, und wir haben auch Code wiederverwendet
      Die eine diente dazu, Regeln zur Validierung riesiger Formulare zu schreiben, die andere dazu, Entscheidungsregeln auf Basis von Formularantworten zu formulieren
      Eine gute DSL befähigt Menschen, die etwas vorher nicht konnten
      Eine DSL, die zum Zeitsparen gebaut wird, ist deutlich seltener nützlich, weil sie in der Praxis wahrscheinlich gar keine Zeit spart
      In beiden Fällen mussten komplexe Domänenlogiken in ein Programm geholt werden
      Also muss man Programmierern die Domäne beibringen, Programmierer mit Domänenexperten zusammenbringen oder Domänenexperten das Programmieren beibringen
      Wenn viel Arbeit anfällt, ist es attraktiv, den Domänenexperten die nötigen Werkzeuge direkt in die Hand zu geben
      Programmierer können dann an anderen Dingen arbeiten, und die Feedbackschleife wird kürzer
      Bei einer tiefen Domäne will man Programmierer wohl kaum erst auf die Schulbank schicken, und bei einer flachen Domäne kann es auch jemand Günstigeres erledigen
      DSLs bringen eine große kognitive Belastung mit sich
      Wenn die Alternative allerdings darin besteht, eine vollständige Programmiersprache zu lernen, wird es plausibler
      Eine DSL zum Zeitsparen richtet sich an jemanden, der bereits Code schreiben kann und nur weniger Code schreiben möchte; die Einsparung ist gering und daher meistens eher enttäuschend
      Wenn dann ein Programmierer etwas ändern will, muss er statt intuitivem Code diese ganze DSL lernen oder sich wieder daran erinnern
      Als einfachere Faustregel gilt: Eine DSL für Programmierer ist wahrscheinlich seltener eine gute Idee als eine DSL für Nichtprogrammierer
    • jOOQ ist eine Katastrophe, und ich würde es niemandem empfehlen
      Man schreibt SQL-Queries, testet sie in einem Tool wie DataGrip und verbringt dann Stunden damit herauszufinden, wie man sie in die DSL übersetzt
      Bei „exotischen“ SQL-Funktionen wie JSON-Ausdrücken wird das Problem noch schlimmer
      Debugging läuft dann nach dem Muster: „generiertes SQL ausgeben, in etwas wie DataGrip kopieren, die Query tunen und anschließend herausfinden, wie man sie wieder in die DSL hineinzwängt“
      Eine enorme Zeitverschwendung
      Das zentrale Verkaufsargument von jOOQ sind typsichere Queries, aber seit IntelliJ begonnen hat, SQL als String im Code gegen echte Daten zu validieren, hat das an Bedeutung verloren
      Der Workflow, SQL direkt zu bearbeiten und unmittelbar gegen die Datenbank zu testen, ist schlicht besser
      jOOQ untermauert die These des Originalbeitrags zu DSLs
    • Abgesehen von Dingen wie regulären Ausdrücken habe ich noch keine gute DSL gesehen, und selbst dort hört man von vielen, dass sie mit der Sprache selbst unzufrieden sind
      Beliebte Beispiele für DSLs, die man als schlecht oder nahezu gescheitert betrachten kann, sind HCL, E4X, XUL und die String-Formatierungssprache von Common Lisp
      HCL ist die Konfigurationssprache von Terraform, und es war offensichtlich, dass das sehr häufige Problem, ähnliche Ressourcen entsprechend der Anzahl von Variablen zu provisionieren, von Anfang an nicht berücksichtigt wurde
      Spätere Versuche, Funktionen dafür hinzuzufügen, waren unbeholfen und lösten das Problem nicht vollständig
      E4X war eine JavaScript-DSL für die Arbeit mit XML; in einfachen Fällen konnte man XML-Operationen knapper ausdrücken, aber sie konnte sehr schnell zu einer schwer lesbaren Wand aus Satzzeichen werden
      Ähnlich wie Microsofts LINQ gab sie dem Autor keinerlei Hinweis darauf, welche Rechenkomplexität der interne Code ungefähr hatte
      Am Ende wurde Code, der diese DSL nutzte, oft in einer weniger kompakten, dafür leichter analysierbaren Form neu geschrieben
      XUL war eine UI-Sprache für Chrome-Erweiterungen des Firefox-Browsers und für den Zweck, Firefox-Erweiterungen zu bauen, in Ordnung
      Firefox wollte sich aber auch als Basistechnologie für unternehmensinterne Anwendungen verkaufen, und in diesem Bereich reichte es bei Weitem nicht
      Für einfache Dinge brauchte man viele Tricks und Umwege
      Die String-Formatierungssprache von Common Lisp ist ähnlich: Für kleine Probleme ist sie okay, aber sie skaliert nicht
      Manche Formatierungsprobleme erfordern sehr merkwürdige Lösungen oder haben von vornherein keine Antwort, und Code, der format rekursiv aufruft, ist wirklich unschön
      Insgesamt ist das häufigste Problem dieses Ansatzes, dass er provisorisch ist und nicht gut skaliert
      Man stößt bald auf Probleme, die sich nicht sauber lösen lassen, und große Programme, die in einer DSL geschrieben sind, sind oft ein Albtraum in der Wartung
    • Immer wenn ich DSL-Hass sehe, wundere ich mich, bis mir wieder klar wird, dass die Leute nicht DSLs im Allgemeinen kritisieren, sondern DSLs, die man von Grund auf selbst schreiben muss
      Wenn man eine DSL auf Lisp aufsetzt, muss man nur die Domänenlogik schreiben, nicht die Basissprache
      Der Großteil der Arbeit ist bereits erledigt, und die Sprache ist vom ersten Tag an nützlich
      Als gehostete DSL auf Lisp kann sie tatsächlich genutzt werden; ich verstehe nicht, warum man stattdessen unbedingt eine neue Sprache von null bauen und dabei zusehen will, wie sie verkümmert und stirbt
    • DSLs funktionieren gut, wenn es eine IDE mit Autovervollständigung und eine schnelle oder unmittelbare Feedbackschleife gibt
  • Für all diese Punkte gibt es viele Erfolgsbeispiele
    Es ist schwer, das Wort „fast“ als Ausflucht zu benutzen
    Das wirkt einfach wie Pessimismus und erschöpfter Zynismus
    Ich verstehe dieses Gefühl und habe es selbst erlebt, und manchmal ist es schwer, einen begeisterten Engineer dazu zu bringen, eine schlechte Idee aufzugeben
    Aber diese Stimmung fühlt sich für mich toxisch an

    • Ich glaube, das ist einfach eine aufmerksamkeitsstarke, sagen wir clickbaitige Formulierung von „Diese Dinge sind trickreicher, als sie aussehen, wenn man sie richtig bauen oder effektiv ausrollen will“
    • Hinter vielen solcher „Erfolge“ steht ein kampferprobtes Team von Engineers, das alle Fehlschläge dieser Idee abfängt
      Control Loops, die gegen unendlich oder gegen Max-/Min-Grenzen durchgehen, Caches, die sich von verteilten Ausfällen nicht erholen, beschädigter Zustand während Live-Migrationen, Bursts, die in ungünstigen Momenten Überlast erzeugen, oder falsche Anomalieerkennungs-Alarme, die weltweit Feiertage melden
      Unter all diesen Ideen liegt ein Geflecht aus Komplexität, das fast alle unterschätzen
    • Im Großen und Ganzen stimme ich zu
      Das ist eine Liste von Systemideen, die schwieriger sind, als man zunächst denkt, und die man ernsthaft angehen muss, statt sie leichtfertig zu behandeln
      Es klingt oberflächlich ähnlich wie „sieht gut aus, funktioniert aber fast nie“, ist im Detail aber völlig anders
      Wenn man es als schwieriges Problem behandelt und entsprechend investiert, ist ein ordentlich funktionierendes Ergebnis ganz normal erreichbar
      Wenn es eine nachträglich angeflanschte Funktion ist oder man naiv annimmt, es sei einfach, geht es oft schief
    • Ich lese das nicht als „Pessimismus und erschöpften Zynismus“
      Ich lese es als Kommentar dazu, dass Engineers sich in vorzeitiger Optimierung verlieren, obwohl es keinen geschäftlichen Nutzen gibt
      Das ist in der Branche wirklich weit verbreitet: Man könnte Backups vorbereiten, die sich innerhalb weniger Stunden deployen lassen, und Server um 200 % überprovisionieren, und käme vielleicht mit weniger als einem Zehntel der Kosten davon; stattdessen macht es Spaß, ein redundantes, automatisch skalierendes Raumschiff zu entwerfen und zu bauen
      Manchmal sind solche Ideen gerechtfertigt, aber erst dann, wenn sie nötig geworden sind
      Sie gehören nicht im Voraus in die Architektur eines frühen Produkts
      Es gibt nur sehr wenige Produkte, die tatsächlich den Maßstab, die Verfügbarkeit und die Komplexität brauchen, die solche Implementierungen lösen sollen
    • Steven scheint nicht zu sagen, dass diese Dinge unmöglich sind, sondern dass sie schwierig sind und ungewöhnlich oft nicht gut ausgehen
  • Viele Leute scheinen hier nach einer feinen Entscheidungsfunktion zu suchen, die die Ausnahmen trennt, aber eigentlich ist es einfach
    Diese Ideen sind großartig, wenn ich sie umsetze, und funktionieren garantiert nicht wie beabsichtigt, wenn irgendein Idiot vor mir sie umgesetzt hat

    • Genau so klingt es
      Und manchmal bin dieser Idiot vor mir ich selbst vor ein paar Monaten
  • Ich würde hier auch Domain-driven Design hinzufügen
    Wenn man versucht, die Anwendung an der Geschäftsstruktur auszurichten und damit das Business-Design festzuschreiben, ist das ein Rezept für eine Katastrophe
    Bei einem kleinen oder stagnierenden Geschäft merkt man das Problem vielleicht nicht
    Aber wenn das Geschäft erfolgreich ist oder wächst, bereut man sehr schnell, Domains mit furchtbar deskriptiven Namen geschaffen zu haben, die an bereits veraltete Arbeitsweisen gebunden sind
    Stattdessen ist es viel flexibler, wie seit Jahrzehnten bewährt um Funktionsschichten herum zu entwerfen und Geschäftslogik möglichst in Konfiguration, Datenbankzeilen und User-Workflows zu legen

    • Beide Optionen wird man bereuen
      Als Fallen von Domain-driven Design wurden veraltete Sprache und geringe Wiederverwendbarkeit von Code und Systemen für neue Versuche genannt; umgekehrt ist aber auch ein hochabstraktes Design, bei dem Geschäftslogik überall in Konfigurationen, Workflows usw. steckt, nur dann flexibel, wenn die gesamte Organisation diese Abstraktionen, Konfigurationen und unzähligen Kombinationen ziemlich gut versteht
      Diese Kombinatorik explodiert schnell zu einem Labyrinth und erzeugt unbekanntes, unerwartetes Verhalten, auf das sich Menschen dann verlassen
      Auch die Kosten für das Onboarding neuer Entwickler und den Austausch von Entwicklungsteams werden schwer tragbar
      Die Organisation spricht am Ende zwei verschiedene Sprachen
      Die meisten scheinbar einfachen Feature Requests werden entweder zu einem riesigen System-Redesign, wenn sie die Abstraktion brechen, oder zu „lass uns diese Abstraktion einfach hacken, damit es im Moment wie eine sicherere, kleinere Änderung aussieht“
      Ersteres ist immer sehr schwierig, selbst mit hervorragenden Engineers, die das Verhalten des gesamten Systems und die Codebasis vollständig verstehen, sowie guten Engineering-Praktiken und -Prozessen; es kann Monate oder Jahre dauern
      Letzteres passiert häufiger, und deshalb wirken Projekte, die „hoch abstrahiert, funktional geschichtet, konfigurationsgetrieben und mit emergenter Geschäftslogik“ sind, anfangs perfekt und flexibel, bis sie am Ende zu „Was zum Teufel ist das?“ werden
      Nachdem das System implementiert ist, wird diese emergente Geschäftslogik zur Sprache, die alle sprechen
      Wenn eine Organisation zwei oder drei Sprachen spricht, die sich überhaupt nicht miteinander versöhnen lassen, ist das sehr schmerzhaft; und wenn es nicht mehrere Leute gibt, die fließend nach oben, unten und zur Seite zwischen ihnen übersetzen können, wird man das Gefühl haben, man hätte die Domain besser näher an der Realität ausdrücken sollen
    • „Unmögliche Zustände unmöglich darstellbar machen“ gehört ebenfalls hierher
      Wenn man einen Zustand so entwirft, dass er im Typsystem nicht darstellbar ist, muss man sicher sein können, dass dieser Zustand während der Lebensdauer dieses Designs wirklich ein unmöglicher Zustand ist
  • Den Punkt zu Control Loops, die auf Last reagieren, verstehe ich nicht ganz
    Sie sind ein grundlegender, fundamentaler Baustein unzähliger Systeme
    Auch der Fliehkraftregler einer Dampfmaschine aus dem 19. Jahrhundert oder eines Victrola-Plattenspielers aus dem 20. Jahrhundert ist ein lastreaktiver Control Loop
    Die gesamte Elektronik ist ein Netz lastreaktiver Control Loops, und Automatikgetriebe in Autos ebenso

    • Ein häufiges Problem ist, einen Control Loop hinzuzufügen, ohne das Signal ausreichend zu verstehen, oder ihn hinzuzufügen, ohne andere Control Loops zu berücksichtigen
      CPU-Auslastung ist ein interessantes Beispiel
      Man kann etwa Situationen sehen, in denen ein Cross-Region-Load-Balancer gegen Load Shedding innerhalb eines Prozesses arbeitet
      Das liegt daran, dass das Signal des Load-Balancers das Load Shedding nicht widerspiegelt oder es auf ungenaue Weise widerspiegelt
      Ein weiteres Problem ist, wenn ein Control Loop, der ein service-lokales Ergebnis optimieren soll, das Gesamtergebnis verschlechtert
      Insgesamt halte ich es für besser, wenige Control Loops an Stellen mit großer Wirkung zu haben
    • Es ist nicht ganz eindeutig, aber es könnte speziell um CPU-Last gehen
      CPU-Last hat einige Probleme, wie in https://arxiv.org/abs/2312.10172 beschrieben
  • Diese Probleme haben ein gemeinsames Muster:
    Es sind alles Querschnittsbelange, die dem sequentiellen Datenverarbeitungs-Programmiermodell, mit dem Programmierer vertraut sind, zusätzliche Einschränkungen auferlegen.
    Mit jeder zusätzlichen Einschränkung wächst die Zahl der Dinge, an die alle späteren Entwickler bei der weiteren Entwicklung in diesem System ständig denken müssen.
    Ein System gerät leicht in einen Zustand der Überbeschränkung, in dem man nicht mehr vorankommt, ohne einige Einschränkungen wieder zu lockern.
    Wenn es nicht unmöglich wird, wird es zumindest langsamer.
    Denn Entwickler müssen jedes Mal berücksichtigen, wie eine neue Funktion mit bereits zugesagten APIs, Sicherheit, Synchronisierung, Latenz, anderen Plattformen und nativem Code zusammenspielt.
    Deshalb ist es durchaus möglich, all diese Eigenschaften zu unterstützen.
    Wenn man zum Beispiel transparente Datensynchronisierung zum zentralen Wertversprechen einer Plattform macht, wird jede weitere Entwicklung diese zuerst unterstützen und den möglichen Funktionsumfang innerhalb dieser Einschränkung weiterentwickeln.
    Dieser Funktionsumfang entspricht vielleicht nicht genau dem, was Nutzer wollen, aber genau das ist der unterstützte Bereich.
    Für Kunden, für die diese Eigenschaft das wichtigste Kaufkriterium ist, wirkt das Produkt attraktiv.

  • Ich habe Projekte mit mehreren DSLs, P2P-Cache und gemischter Parallelität umgesetzt, und sie haben alle funktioniert.
    Sie zu bauen hat auch wirklich Spaß gemacht.
    Mit einer Ausnahme waren sie eine gute Investition.
    Der P2P-Cache hat sich am Ende kaum ausgezahlt, weil er nicht nötig war.
    Daher ist es eindeutig falsch zu sagen, dass solche Dinge praktisch nie funktionieren.
    Sie sind komplex, aber diese Komplexität bringt Funktionen, die auf andere Weise nur schwer zu bekommen sind.
    Die Lehre aus dem P2P-Cache-Beispiel ist, zuerst sicherzustellen, ob man diese Funktion wirklich braucht.

  • Es liest sich etwas seltsam, weil ich ziemlich viele dieser Ideen erfolgreich umgesetzt habe.

    • Entweder man weiß wirklich sehr genau, was man tut, oder man hat wirklich überhaupt keine Ahnung.
  • „Lass uns einfach die Daten synchronisieren“ ist nach meinem Maßstab der Grund, warum es harte Tage gibt.
    Ich habe zu viele Systeme gesehen, denen mit Blick auf so etwas wie „Internet-Scale“ Queues, Event-Verarbeitung usw. hinzugefügt wurden, obwohl ihr tatsächlicher natürlicher Umfang weit unter dieser Schwelle liegt.
    Solche Teams sind entweder naiv oder beschaffen sich im schlimmsten Fall Geld von Führungskräften ohne Engineering-Verständnis, um aus Spaß mit solchen Problemen herumzuspielen.

    • „Lass uns einfach die Daten synchronisieren“ habe ich so gelesen: Man liest auf der einen Seite und schreibt auf der anderen und hofft naiv, dass die zwei Quellen der Wahrheit nicht auseinanderlaufen.
      Wenn man es richtig machen will, sind Queues und Event-Verarbeitung unverzichtbar.