35 Punkte von GN⁺ 2024-12-23 | 4 Kommentare | Auf WhatsApp teilen
  • Moderne Software wird durch Continuous Deployment (CD) und automatisierte Tests (CI) häufig aktualisiert, aber „Software, die langfristig genutzt wird“, erfordert einen anderen Ansatz
    • Beispiele: Kernkraftwerke, Flugzeuge, Herzschrittmacher, Wahlsysteme usw.
      • In Bereichen, in denen Zuverlässigkeit und Stabilität entscheidend sind, werden Stabilität und vorhersehbare Änderungen gegenüber kontinuierlichen Veränderungen bevorzugt

Kernprinzipien der langfristigen Softwareentwicklung

Abhängigkeiten (Dependencies)

  • Die Abhängigkeiten einer Software sind ein wichtiger Faktor für langfristigen Erfolg
  • Software muss die Interaktion mit der Außenwelt berücksichtigen, und grundlegende Entscheidungen wie die Programmiersprache sind wichtig
  • Verständnis der Ebenen von Software-Abhängigkeiten
    • Außenwelt: Client-Software, die wir nicht kontrollieren können (z. B. Browser).
    • Grundlegende Entscheidungen: Elemente wie Programmiersprachen, deren Änderung eine Neuschreibung des gesamten Stacks erfordern würde.
    • Frameworks: Spring Framework, React usw., die eng mit der Codebasis gekoppelt sind. Änderungen sind möglich, aber mit sehr hohen Kosten verbunden.
    • Datenbanken: Meist austauschbar, aber nur mit Feinarbeit und Aufwand.
    • Hilfsbibliotheken: Austauschbare Bibliotheken, die bestimmte Funktionen bereitstellen.
  • Mit der Zeit verändern sich Abhängigkeiten und die Außenwelt:
    • Änderungen an Abhängigkeiten können Code-Anpassungen oder Verhaltensänderungen verursachen.
    • Neue Major-Versionen können Kompatibilitätsprobleme auslösen.
    • Es besteht das Risiko, dass ein Projekt eingestellt wird oder verschwindet.
    • Sicherheitsrisiken: Abhängigkeiten können von böswilligen Akteuren kompromittiert werden (npm, PyPI usw.).
    • Kommerzialisierung: Neue Eigentümer aus dem Venture Capital (VC) machen ein Projekt kostenpflichtig.
    • Konflikte zwischen Abhängigkeiten.
  • Was bei der Auswahl von Abhängigkeiten für eine langfristige Nutzung geprüft werden sollte:
    • Technisches Niveau: Ob sich die Qualität anhand des Quellcodes beurteilen lässt.
    • Nutzerbasis: Wer es tatsächlich verwendet.
    • Entwicklungszweck: Wer die Entwickler sind und welche Ziele sie verfolgen.
    • Finanzierung: Ob es finanzielle Unterstützung gibt und woher sie stammt.
    • Wartung: Ob regelmäßig Security-Releases erscheinen.
      • Ob die Community die Wartung übernehmen könnte.
      • Ob man die Wartung selbst übernehmen könnte.
      • Ob bei Bedarf finanzielle Unterstützung nötig ist, um die Nachhaltigkeit des Projekts zu sichern.
    • Abhängigkeiten der Abhängigkeiten:
      • Auch die Sicherheitsgeschichte nachgelagerter Abhängigkeiten prüfen.
  • Ein realistischer Ansatz
    • Abhängigkeiten begrenzen:
      • Projekte mit mehr als 1600 Abhängigkeiten neigen dazu, dass sich der Code schnell verändert und instabil wird.
      • In Projekten mit zahllosen Abhängigkeiten ist es oft kaum noch nachvollziehbar, welcher Code überhaupt ausgeliefert wird.
    • Vorsichtig hinzufügen:
      • Beim Hinzufügen von Abhängigkeiten bewusst eine technische Hürde einbauen, um natürliche Prüfzeit zu schaffen.
      • In langfristigen Projekten sollten unnötige Abhängigkeiten vermieden werden.

Laufzeitabhängigkeiten (Runtime Dependencies)

  • Bisher bezog sich die Diskussion nur auf Build-/Kompilier-Abhängigkeiten.
  • Moderne Projekte enthalten jedoch oft auch Laufzeitabhängigkeiten:
    • Beispiele: Amazon S3, Google Firebase.
    • Einige gelten praktisch als De-facto-Standard (z. B. S3).
    • Die meisten Laufzeitabhängigkeiten bedeuten jedoch eine starke Bindung (Lock-in) an einen bestimmten Dienst.
  • In 10 Jahren Alternativen für heute genutzte Dienste zu finden, kann extrem hohe Kosten verursachen.
  • Die Liste der Drittanbieter-Dienste minimieren oder auf null bringen:
    • Besonders in der cloud-native Softwareentwicklung ist der Einsatz vieler fortgeschrittener Drittanbieter-Dienste üblich.
    • Für langfristige Projekte bringen solche Abhängigkeiten hohe Risiken mit sich.
  • Auch Build-Time-Service-Abhängigkeiten sind ein wichtiger Faktor:
    • Beispiel: Wenn npm install nicht mehr funktioniert, ist schon der Build der Software unmöglich.
    • Das kann die Wiederverwendbarkeit des Projekts massiv beeinträchtigen.
  • Laufzeitabhängigkeiten gründlich prüfen:
    • Potenzielle Lock-in-Probleme erkennen und Abhängigkeiten reduzieren oder entfernen.
  • Langfristige Wartbarkeit sicherstellen:
    • Frühzeitig bedenken, ob Cloud- oder Drittanbieter-Dienste ersetzbar sind.

Testen, testen und nochmals testen

  • Die Notwendigkeit von Tests ist ein Grundprinzip, dem alle zustimmen:
    • So viele Tests wie möglich schreiben.
    • Nicht alle Tests sind gleich wertvoll, aber man bereut Tests selbst fast nie.
  • Gerade in Projekten mit vielen Abhängigkeiten sind Tests unverzichtbar:
    • Wenn Abhängigkeiten sich ändern oder driften, helfen Tests, Probleme früh zu erkennen.
  • Die Rolle von Tests
    • Unterstützung bei der Problemlösung:
      • Änderungen können schnell nachvollzogen und angepasst werden.
    • Unterstützung beim Refactoring:
      • Sie geben Sicherheit, wenn Code-Abhängigkeiten entfernt oder geändert werden.
    • Nützlich für die langfristige Wartung:
      • Selbst nach mehr als 3 Jahren Entwicklungsstillstand lässt sich mit Tests prüfen, ob das System noch funktioniert.
      • Ebenso, ob es mit neuem Compiler, neuer Runtime oder neuem Betriebssystem weiter funktioniert.
  • Tests sind keine Kosten, sondern eine Investition
    • Mehr Tests schreiben:
      • Tests sind die Grundlage für Wartbarkeit und Stabilität.
      • Bei Änderungen oder Erweiterungen des Codes sind sie eine große mentale Stütze.

Komplexität: der Endgegner der Softwareentwicklung

  • Komplexität ist der ultimative Feind der Softwareentwicklung:
    • Selbst die besten Entwickler oder Teams können an ihr scheitern.
    • Durch Entropie und menschliches Verhalten nimmt Komplexität immer zu.
    • Wenn sie nicht bewusst gesteuert wird, kann ein Projekt unwartbar werden.
  • Zusammenhang zwischen Komplexität und Code-Menge
    • Code-Menge und Komplexität:
      • Bei wenig Code bleibt selbst gewisse Komplexität noch beherrschbar.
      • Je mehr Code es gibt, desto einfacher muss er bleiben, um kontrollierbar zu sein.
      • Beherrschbare Komplexität muss innerhalb der Fähigkeiten des Teams und des „grünen Dreiecks“ bleiben.
    • Grenzen der Komplexität:
      • Selbst mit mehr Teammitgliedern oder außergewöhnlich starken Entwicklern gibt es Grenzen bei der Bewältigung von Komplexität.
      • Werden diese überschritten, wird das Projekt unwartbar.
  • Warum sich Code im Diagramm immer nach rechts oben bewegt:
    • Mehr Funktionswünsche.
    • Unnötige Optimierungsversuche.
    • Beim Beheben von Bugs wird oft neuer Code hinzugefügt, statt bestehende Komplexität zu reduzieren.
  • Die Kosten schlechter API-Entwürfe:
    • Beispiel: Die Funktion CreateFile erstellt in den meisten Fällen gar keine Datei.
    • Solche Verwirrung erhöht die kognitive Last und die Fehlerwahrscheinlichkeit.
  • Strategien zum Umgang mit Komplexität
    • Früh und häufig refactoren:
      • Unnötigen Code entfernen und Zeit in Vereinfachung investieren.
    • In Tests investieren:
      • Je mehr Tests vorhanden sind, desto leichter wird die Reduktion von Komplexität.
    • Die Bedeutung von Komplexitätsmanagement:
      • Wenn nicht frühzeitig aktiv vereinfacht wird, drohen langfristige Projekte am Ende in einem „unwartbaren Zustand“ zu landen.

Schreibe langweiligen und einfachen Code. Noch einfacher. Und noch langweiliger.

„Debugging ist doppelt so schwer wie das Schreiben eines Programms. Wenn du also beim Schreiben so clever wie möglich bist, wie willst du es dann debuggen?“ - Brian Kernighan

  • Extrem langweiliger und klarer Code:
    • Naiver, aber intuitiv verständlicher Code ist zu bevorzugen.
    • „Vorzeitige Optimierung ist die Wurzel allen Übels.“
  • Nur optimieren, wenn es wirklich nötig ist:
    • Wenn etwas zu einfach ist und später Probleme macht, lässt sich Komplexität nachträglich leicht hinzufügen.
    • Vielleicht kommt dieser Moment aber nie.
  • Komplexen Code vermeiden:
    • Warten, bis er wirklich notwendig ist.
    • Es ist sehr unwahrscheinlich, dass man bereut, einfachen Code geschrieben zu haben.
  • Hochperformanter Code oder bestimmte Features funktionieren oft nur unter speziellen Bedingungen.
    • Beispiele:
      • LMDB: Bis zum stabilen Einsatz bei PowerDNS gab es viele Schwierigkeiten.
      • RapidJSON: Eine SIMD-beschleunigte JSON-Bibliothek. Sehr performant, aber mit anspruchsvollen Einsatzbedingungen.
  • Selbst wenn man überzeugt ist: „Ich kann diese Einschränkung überwinden“:
    • Dieses Jahr mag das stimmen, aber in 5 Jahren könnten man selbst oder die Nachfolger daran scheitern.
    • Komplexe Programmiersprachen folgen demselben Prinzip.
  • Fazit:
    • Code vereinfachen:
      • Wirklich einfach. Und noch einfacher.
    • Optimierung auf später verschieben:
      • Komplexität kann bei Bedarf später ergänzt werden, aber zu frühe Komplexität erschwert die Wartung.

Softwareentwicklung auf LinkedIn-Basis

  • Realität vs. Ideal
    • Der ideale Ansatz: Abhängigkeiten sollten gründlich bewertet und geprüft werden (mithilfe der obigen Checkliste).
    • Der realistische Ansatz: Man probiert manchmal einfach attraktive Technik aus und behält sie, wenn sie funktioniert.
  • Warum das verlockend ist
    • Technik, die von bekannten Persönlichkeiten oder Influencern auf LinkedIn empfohlen wird.
    • Das „neueste Framework“, das in Communities wie Hacker News begeistert gefeiert wird.
  • Trend-Technologien sind langfristig oft nicht ausreichend erprobt:
    • Für „Softwareprojekte, die länger als 10 Jahre bestehen sollen“ sind sie möglicherweise ungeeignet.
    • Neue Technologien bergen in frühen Phasen häufiger Probleme bei Stabilität und Wartbarkeit.
  • Empfehlungen
    • Nur in experimentellen Bereichen einsetzen:
      • Neue Technologien zunächst in kleinen Projekten oder nicht-kritischen Bereichen testen.
    • Den Lindy-Effekt berücksichtigen:
      • Die Lebensdauer einer Technologie steht tendenziell im Verhältnis zu der Zeit, die sie bereits genutzt wurde.
      • Je älter eine Technologie ist, desto eher darf man langfristige Stabilität erwarten.
  • Neue Technologien sind attraktiv, aber für langfristige Projekte sind bewährte stabile Technologien besser geeignet.

Logging, Telemetrie, Performance

  • Wenn Software nicht kontinuierlich aktualisiert oder ausgerollt wird:
    • Möglicherweise bekommt man kein unmittelbares Feedback, wenn eine Website kaputtgeht.
    • Zwischen Deployment und tatsächlicher Problemlösung kann viel Zeit vergehen.
  • Sorgfältiges Logging und Telemetrie bereits ab dem ersten Release implementieren:
    • Performance, Fehler und Aktivitäten der Software aufzeichnen.
    • Über die Zeit angesammelte Daten sind äußerst nützlich, um seltene Bugs zu beheben.
  • Probleme durch unzureichendes Logging:
    • Eine Benutzeroberfläche wurde ausgerollt, und ein Nutzer mit 3000 Ordnern meldete ein Problem.
    • Der Nutzer sagte nur: „Es funktioniert nicht“, und die Ursache zu finden dauerte Monate.
    • Mit Performance-Logging und Telemetrie hätte sich das Problem viel schneller lösen lassen.
  • Logging und Telemetrie sind unverzichtbar:
    • Software sollte so entworfen sein, dass ihre Aktivitäten gründlich überwacht werden können.
    • Bei langfristigem Deployment und langfristiger Wartung helfen sie enorm bei unerwarteten Problemen.

Dokumentation

  • Die Bedeutung von Dokumentation:
    • Es reicht nicht, nur gute API-Dokumentation zu schreiben; man muss auch erklären, „warum es so entworfen wurde“.
    • Ideen und Philosophie zur Funktionsweise des Systems festhalten.
    • Warum Lösungen getrennt wurden und was hinter nicht-intuitiven Designentscheidungen steckt, sollte dokumentiert werden.
  • Nützliche Materialien zusätzlich zu Architektur-Dokumenten:
    • Interne Blogposts: freie Diskussionen von Entwicklern über das Systemdesign.
    • Team-Interviews: Gesprächsprotokolle zum Hintergrund von Designentscheidungen.
    • Solche Dokumente ermöglichen auch nach langer Zeit die Weitergabe von Wissen im Team.
  • Kommentare im Code hinterlassen:
    • Trotz des Trends „Guter Code braucht keine Kommentare“ sind Kommentare, die das ‚Warum‘ des Codes erklären, unverzichtbar.
    • Wichtig ist, warum eine bestimmte Funktion überhaupt existiert.
  • Commit-Messages schreiben:
    • Commit-Messages sind zentral für die Arbeitshistorie. Sie helfen, die Gründe für Codeänderungen nachzuvollziehen.
    • Eine Umgebung schaffen, in der Nutzer Commit-Messages leicht einsehen können.
  • Zeit für Dokumentation einplanen:
    • An Tagen, an denen die Entwicklung nicht gut vorankommt, Zeit für nützliche Kommentare und Aufzeichnungen verwenden.
    • Im Team regelmäßig Zeitfenster für Dokumentation reservieren.
  • Dokumentieren, warum etwas so entworfen wurde:
    • Für ein neues Team in 7 Jahren sind Unterlagen, die Philosophie und Hintergrund vermitteln, von unschätzbarem Wert.
  • Hinterlasse Geschichte durch Kommentare und Commit-Messages:
    • Nicht nur während der Entwicklung, sondern auch für die langfristige Wartung essenziell.

Teamaufbau

  • Die Beständigkeit des Teams und der langfristige Erfolg von Software:
    • Manche Software ist auf Support über 80 Jahre ausgelegt. In solchen Langfristprojekten ist Teamkontinuität entscheidend.
    • In modernen Entwicklungsumgebungen gelten im Schnitt etwa 3 Jahre schon als lange Betriebszugehörigkeit.
    • Gute Dokumentation und Tests können Teamwechsel teilweise abfedern, aber nur bis zu einem gewissen Grad.
  • Vorteile langer Betriebszugehörigkeit:
    • Teammitglieder 10 Jahre oder länger halten:
      • Sie tatsächlich fest anstellen und Entwickler gut führen.
      • Das gilt als entscheidender „Hack“ für den Erfolg langfristiger Projekte.
  • Probleme mit externer Vergabe:
    • Externe Entwickler übergeben den Code oft an das System und verschwinden dann.
    • Wenn das Ziel Softwarequalität ist, die über 10 Jahre tragfähig bleibt, ist das sehr ineffizient.
  • Rahmenbedingungen schaffen, in denen Teammitglieder langfristig zusammenarbeiten können.
  • Strategien sind nötig, um die Abhängigkeit von externen Beratern zu minimieren und die Nachhaltigkeit des internen Teams zu erhöhen.

Open Source in Betracht ziehen

  • Vorteile von Open Source:
    • Die Codequalität durch externe Prüfung hochhalten:
      • Der Blick von außen fordert von Entwicklern höhere Standards.
    • Ein starkes Instrument, um bessere Code-Standards zu bewahren.
  • Die Realität der Vorbereitung auf Open Source:
    • Unternehmen oder Behörden behaupten oft, die Vorbereitung auf eine Open-Source-Veröffentlichung dauere Monate bis Jahre.
    • Gründe:
      • Intern wird häufig Code geschrieben, den man ungern öffentlich zeigen würde.
      • Vor der Veröffentlichung muss der Code aufgeräumt werden.
  • Prüfung der Anwendbarkeit:
    • Open Source ist nicht immer eine mögliche Option.
    • Wenn es möglich ist, ist es ein guter Weg, Codequalität und Transparenz zu erhöhen.
  • Open Source ist eine wichtige Strategie, die man nutzen sollte, wenn es möglich ist.
  • Der Blick von außen und höhere Standards helfen, ein Projekt in die richtige Richtung zu lenken.

Gesundheitscheck für Abhängigkeiten

  • Das Problem von Änderungen bei Abhängigkeiten:
    • Abhängigkeiten können sich mit der Zeit anders als erwartet verändern oder auseinanderlaufen.
    • Wenn man das ignoriert, kann es zu:
      • Bugs
      • Build-Fehlern
      • anderen enttäuschenden Ergebnissen kommen.
  • Regelmäßige Gesundheitschecks empfohlen:
    • Regelmäßige Abhängigkeitsprüfungen:
      • Sie bieten die Chance, Probleme frühzeitig zu entdecken.
      • Man kann auch neue Funktionen in Abhängigkeiten entdecken, die den Code vereinfachen oder andere Abhängigkeiten überflüssig machen.
    • Die Bedeutung präventiver Wartung:
      • Wenn man sich nicht selbst Zeit für Prüfungen einplant, wird man sich diese Zeit später aufzwingen lassen, wenn Probleme auftreten.
  • Eine Analogie aus der Wartung:
    • Ein Spruch von Mechanikern:
      • „Plane die Wartungszeit selbst. Sonst plant die Ausrüstung sie für dich.“
  • Regelmäßige Abhängigkeitsprüfungen sind eine wesentliche Aktivität für langfristige Softwarestabilität und Effizienz.
  • Sie bieten die Chance, Probleme vorab zu lösen und positive Veränderungen zu entdecken.

Wichtige Literaturempfehlungen

Zum Schluss

Wichtige Empfehlungen für langfristige Softwareentwicklung:

  • Einfachheit bewahren:
    • Einfach halten, und noch einfacher! Da sich Komplexität bei Bedarf später hinzufügen lässt, sollte man es anfangs nicht unnötig kompliziert machen.
    • Um Einfachheit zu bewahren, sind regelmäßiges Refactoring und das Löschen von Code nötig.
  • Abhängigkeiten sorgfältig bedenken:
    • Je weniger Abhängigkeiten, desto besser. Sorgfältig prüfen und auditieren.
    • Wenn man 1600 Abhängigkeiten nicht auditieren kann, sollte man den Plan überdenken.
    • Entscheidungen nach Trends oder Hypes (z. B. Entwicklung auf LinkedIn-Basis) vermeiden.
    • Regelmäßige Abhängigkeitsprüfungen: den Zustand der Abhängigkeiten kontinuierlich überwachen.
  • Testen, testen und nochmals testen:
    • Veränderungen bei Abhängigkeiten rechtzeitig erkennen.
    • Sicherheit beim Refactoring geben und helfen, Einfachheit zu bewahren.
  • Dokumentation:
    • Nicht nur den Code, sondern auch Philosophie, Ideen und den Hintergrund dokumentieren, also warum etwas so gemacht wurde.
    • Für zukünftige Teammitglieder ist das ein wertvoller Schatz.
  • Ein stabiles Team erhalten:
    • Für Investitionen in langfristige Projekte langfristige Beschäftigung in Betracht ziehen.
    • Teammitglieder dabei unterstützen, sich über lange Zeit dem Projekt zu widmen.
  • Open Source in Betracht ziehen:
    • Wenn möglich, über Open Source höhere Code-Standards sichern.
  • Logs und Performance-Telemetrie:
    • Sie spielen eine wichtige Rolle, um Probleme früh zu erkennen und zu lösen.
  • Diese Empfehlungen sind vielleicht nicht neu, aber da erfahrene Entwickler sie immer wieder betonen, lohnt es sich, gründlich darüber nachzudenken.

4 Kommentare

 
kandk 2024-12-30

Die wichtigste technische Stärke besteht darin, Schichten, in denen Stabilität entscheidend ist, von Schichten, in denen Geschwindigkeit entscheidend ist, zu trennen und zu klären, wie die Beziehung zwischen beiden gehandhabt wird.
Wenn Toss nur auf Stabilität gesetzt hätte, würde es sich wohl kaum von anderen Banken unterscheiden.

 
kandk 2024-12-30

Riskant ist es bei SpaceX ebenso. Bei Tesla auch..

 
aer0700 2024-12-25

Ist lebenslauforientierte Entwicklung das Problem?

 
GN⁺ 2024-12-23
Hacker-News-Kommentar
  • Das Toolchain aktiv zu aktualisieren, ist ein wichtiger Teil des Entwicklungsprozesses. Viele Unternehmen stufen Toolchain-Upgrades in ihrer Prioritätenliste herab, was zu Problemen wie Sicherheitslücken führt. Bei jedem Release eines aktuellen Compilers oder Build-Systems wird ein Branch erstellt, um den Build-Status zu prüfen; wenn Fehler auftreten, werden sie als Bugs betrachtet und sofort behoben. Das hilft dabei, die Codebasis schrittweise mit aktuellen Sprachfeatures zu modernisieren und zu refaktorieren.

  • Third-Party-Abhängigkeiten sind auf lange Sicht oft enttäuschend. In neuen Projekten können Third-Party-Abhängigkeiten kurzfristig helfen, Probleme zu lösen, aber langfristig ist es besser, sie durch eigenen Code zu ersetzen.

  • Es ist notwendig, Abhängigkeiten zu vendoren und sie über Code-Reviews zu verwalten. Häufig ist die Qualität von Third-Party-Code niedrig, sodass es oft besser ist, ihn selbst zu schreiben.

  • Es läuft ein Projekt mit Qt, CMake und modernem C++, das auf langfristige Skalierbarkeit abzielt. Dieser Tech-Stack liefert kontinuierlich Features und Verbesserungen.

  • Die Arbeit mit Emacs Lisp war eine erfrischende Erfahrung. Ein Vorteil ist, dass es stabil funktioniert, auch wenn Bibliotheken nicht aktualisiert werden. Die Erfahrung mit Gatsby und Node war wegen Update-Problemen schwierig.

  • Es ist wichtig, einfachen Code zu schreiben. Komplexen Code sollte man nur schreiben, wenn er wirklich nötig ist, und einfachen Code bereut man nicht.

  • Die Dokumentation von Systemen und Code ist wichtig. Je mehr Erfahrung man in der Softwareentwicklung hat, desto stärker erkennt man die Bedeutung von Dokumentation.

  • Tests spielen in der Planung eine wichtige Rolle. Man sollte sich an der Entwicklungsweise der NASA orientieren und den Fokus darauf legen, Programmierfehler zu finden. Bei der Entwicklung von medizinischer Software vermeidet man Interpretationen und verwendet keine dynamische Speicherallokation.

  • Der beste Weg, langlebige Software zu schreiben, ist, „langweiligen“ Code zu schreiben. Man sollte Abhängigkeiten vermeiden und den Grundlagen treu bleiben.

  • Es gab Erfahrungen mit Problemen durch Abhängigkeiten in Python. Das wird als „DLL Hell“ bezeichnet, und COM hat versucht, das zu lösen, war damit aber nicht erfolgreich.

  • Praktiken, die in industrieller Software angewendet werden, sind für allgemeine Software nicht robust genug. Ingenieure versuchen, Risiken zu mindern, aber wir konzentrieren uns darauf, Risiken zu mindern.