Aus Versehen tragend geworden
(jefftk.com)- Strukturen oder Code, die ursprünglich unbedeutend oder nur vorübergehend wirkten, können mit der Zeit eine systemtragende Rolle übernehmen; deshalb sollten vor Änderungen auch die aktuellen Abhängigkeiten geprüft werden
- Chesterton's Fence ist ein nützliches Prinzip: Vor dem Entfernen sollte man verstehen, warum etwas überhaupt errichtet wurde. Wenn man jedoch nur auf die ursprüngliche Absicht schaut, kann man später entstandene Rollen übersehen
- Bei der Renovierung eines Badezimmers im Haus erwies sich ein wie ein Hindernis wirkender vertikaler Ständer als teilweise tragend für die Last des Obergeschosses, nachdem sein ursprünglicher Zweck als Schranktrennwand weggefallen war und fehlerhafte bauliche Änderungen erfolgt waren
- Auch in komplexen Computersystemen sind Änderungshistorie und Designdokumente nur der Ausgangspunkt; man muss zusätzlich betrachten, wie eine Komponente aktuell in das System integriert ist
- Wer alte Komponenten entfernt oder verändert, ohne sowohl die historischen Designgründe als auch ihre heutigen verborgenen Rollen zu prüfen, kann unerwartete Ausfälle verursachen
Was Chesterton's Fence übersehen kann
- Chesterton's Fence steht für die Idee, dass man vor dem Ändern oder Entfernen von etwas zuerst verstehen sollte, warum es geschaffen wurde
- Bei menschengemachten Dingen wie Zäunen oder Türen ist es sehr wahrscheinlich, dass es einmal einen Grund gab, warum jemand sie für nützlich hielt
- Selbst ein Design, das völlig sinnlos wirkt, kann bedeuten, dass die Person, die es ändern will, einen Aspekt des Problems übersehen hat
- Allerdings kann diese Sichtweise dazu verleiten, sich nur auf die vom ursprünglichen Erbauer beabsichtigte Rolle zu konzentrieren und dadurch später entstandene neue Abhängigkeiten zu übersehen
Zufällig entdeckte Lastabtragung bei einer Haussanierung
- Als vor einigen Jahren ein Badezimmer im Haus neu gebaut wurde, gab es einen vertikalen Ständer, der die Arbeiten behinderte
- Dieser Ständer war ursprünglich Teil einer Schranktrennwand, und diese Funktion schien nicht mehr gebraucht zu werden
- Aus der Perspektive von Chesterton's Fence hätte sein Entfernen unproblematisch gewirkt, tatsächlich war er im Lauf der Zeit jedoch zu einem tragenden Bauteil geworden
- Durch andere fehlerhafte bauliche Veränderungen hatte dieser Ständer begonnen, einen Teil der Last des Obergeschosses zu tragen
- Man muss also nicht nur prüfen, warum etwas ursprünglich geschaffen wurde, sondern auch, welche zusätzlichen Rollen es später übernommen hat
Lehren für komplexe Computersysteme
- Beim Ändern komplexer Computersysteme tritt dasselbe Problem immer wieder auf
- Es ist weiterhin nützlich, die Änderungshistorie zu betrachten, die ursprünglichen Designdokumente zu lesen und zu verstehen, warum eine bestimmte Komponente so gebaut wurde
- Sicher wird es aber erst, wenn man auch betrachtet, auf welche Weise diese Komponente derzeit im System verbunden und genutzt wird
- Komponenten übernehmen mit der Zeit leicht zusätzliche Rollen, die von ihrem ursprünglichen Zweck abweichen
- Sichere Änderungen beginnen damit, die frühere Designabsicht und die heutige tatsächliche Rolle gemeinsam zu prüfen
1 Kommentare
Meinungen auf Hacker News
Endlich fühlt es sich so an, als gäbe es einen Namen dafür
Ich mache viel Support für Steuerungssysteme, und es ist nicht selten, dass PLC-Code, der irgendein physisches Gerät auf ungewöhnliche Weise behandelt, unbeabsichtigte Probleme verursacht
Ich wiederhole oft den Satz: „Jedes Mal, wenn man ein elektrisches/mechanisches Problem mit Software behebt, wird ein Gremlin geboren“
Selbst wenn ich die eigentliche Ursache eines Bugs entdecke oder eine programmierte Einschränkung finde, die ich entfernen möchte, lehne ich das immer ab, bis ich weiß, warum dieser Code existiert. Kein Code ist ohne Grund hineingeraten, also muss man zuerst herausfinden, warum ein Timer oder Override nötig war
Oft ist es erfreulich, wenn es Code ist, der ein Problem gelöst hat, das es längst nicht mehr gibt; aber häufig wurde der Code auch eingebaut, um einen Unfall zu verhindern, und durch Personalwechsel ist der ursprüngliche Zweck verloren gegangen. Ohne Dokumentation wird man sehr vorsichtig, die Arbeit anderer einfach zurückzudrehen
Es geht auch darum, Kolleginnen und Kollegen zu vertrauen. Normalerweise tut niemand etwas völlig grundlos; wenn Code da ist, sollte man davon ausgehen, dass er einen Zweck hat, und darauf vertrauen, dass er anfangs ausreichend geprüft wurde. Wenn dieses Vertrauen zerbricht, wird jede Entscheidung schwierig
Wenn der Grund mit etwas außerhalb der Codebasis zu tun hat — Betriebssystem, Dateisystem, Datenbank, HTTP-Endpoint, Hardware —, kommentiere ich das zu 100 %, sofern es nicht bloß ein einfacher API- oder Library-Aufruf ist
Wenn ich wegen eines Rate Limits eines anderen Dienstes ein
sleepeinbaue, schreibe ich dazu, wer es verlangt, welche Grenzwerte nach damaligem Wissensstand gelten, falls ich es nicht genau weiß, dass es „eine Vermutung ist, aber zu funktionieren scheint“, und wie das System aussehen kann, wenn man darüber liegtWenn ich für eine Kleinigkeit, die auch mit dem Dateisystem machbar wirkt, eine Datenbank verwende, weil in dieser Umgebung der nötige Zugriff auf das Dateisystem unter Last wegen ausufernder Systemaufrufe Ressourcen erschöpft, hinterlasse ich einen Kommentar
Auch Workarounds für Bugs in weit verbreiteten Libraries, die Ubuntu in LTS-Releases nicht behebt, sind ein Fall für Kommentare. Ich habe wirklich viele Kommentare nach dem Muster geschrieben: „Ich weiß, dass das unschön ist, aber hier ist der Grund“
Ich kommentiere auch, wenn ich Code schreibe, der in großem Maßstab nicht gut funktionieren wird, weil es mir gerade zu lästig ist, ihn skalierbar zu machen, und es nach aktueller Einschätzung auch nicht nötig ist. Vielleicht dient das eher dem Schutz des eigenen Stolzes, aber es geht in die Richtung: „Ich weiß, dass hier die ganze Datei in den Speicher gelesen wird, aber es ist ein seltener und planbarer Batch-Job, und die Dateien sollen klein sein, also ist das okay. Wenn der Speicher knapp wird, zuerst hier nachsehen. Wenn das in einen On-Demand-Aufruf geändert werden soll, neu schreiben“
Der Punkt des Artikels ist, dass das allein nicht reicht. Man muss nämlich auch wissen, was sonst noch auf der Annahme aufgebaut wurde, dass dieser Code existiert
„Ich weiß nicht, warum dieser Rung nötig ist, aber lösch ihn und finde es selbst heraus“
Ich habe ihn nicht angerührt und es auch nicht herausgefunden
Elektro- und Maschinenbauingenieure haben Software historisch weniger ernst genommen als elektrische und mechanische Systeme, und als Folge entsteht in von EE/ME dominierten Engineering-Kulturen leicht chaotischer Code
Selbst unter Leuten, die nach außen hin Professional Engineers sind, ist inakzeptabel unreife Softwaretechnik immer noch verbreitet
Undokumentierter Code ist eine Sache, aber meistens ist es vor 30 Jahren maßgefertigte Hardware, sodass es nicht einmal Schaltpläne für die Anlage gibt, an der man arbeitet
Ich hatte etwas Ähnliches
Vor ein paar Jahren habe ich ein altes Haus gekauft, und die früheren Eigentümer hatten seit den 1960ern das meiste selbst gemacht
Eine Zinkrinne war vermutlich jahrzehntelang undicht und hatte einen Teil der Dachkonstruktion beschädigt; getragen wurde das Dach von Holzpaneelen, die in den 70ern angebracht worden waren, um die Innenseite zu verkleiden. Mit anderen Worten: Diese Holzpaneele trugen tatsächlich Last
In diesem Haus habe ich noch viel mehr entdeckt. Zum Beispiel war am Dachrand die Breite der Dachziegel zu knapp; statt zusätzliche Ziegel zu kaufen, hatten sie es mit Zement und Scherben zerbrochener Keramik-Blumentöpfe aufgefüllt
Heute erwarten wir, dass Gipskarton und Sperrholz diese Rolle bei Erdbeben und Stürmen übernehmen
Wenn man sieht, wie ein Haus bis auf das Ständerwerk entkernt und neu aufgebaut wird, wird man einige zusätzliche Aussteifungen sehen. Nicht, damit die Wände nicht einstürzen, sondern damit sie rechtwinklig und eben bleiben, bis die Wände wieder aufgebaut sind
Auf den ersten Blick sieht es aus, als hätte jemand das Garagentor gerammt und stark verbeult, aber bei genauerem Hinsehen hängt das Dach gerade so an der Schiene, an der das Tor befestigt ist, und steht kurz vor dem Ende
Anfangs wollte ich nur die Enden der Sparren anstückeln und das Garagentor ersetzen, nach dem Motto: Auf der anderen Seite hat das früher auch jemand so gemacht, also wird es schon funktionieren; inzwischen glaube ich, dass das ganze Dach neu muss
Wirklich Sorgen macht mir die verdächtige Verkabelung im gesamten Keller. Relativ neue Leitungen, alte stoffummantelte Leitungen und Isolierband, das sie zusammenflickt, sind bunt gemischt. Zum Glück scheint keine der Leitungen Last zu tragen
Dieser Beitrag und fast alle Kommentare scheinen das eigentliche Problem zu verfehlen. Der Kern ist zu wenig Tests.
Software unterscheidet sich von allen anderen Produktionsmitteln dadurch, dass man Änderungen tatsächlich testen kann, bevor sie in der Realität wirksam werden.
Wenn es gute Tests gibt, ist es egal, was die Absicht war oder ob für diese Funktion neue Verwendungen oder neue Nutzer entstanden sind. Man behebt etwas, lässt die Tests laufen, und sie sagen einem, ob die Änderung gut ist.
Mit guten Tests braucht man keine Software-Archäologie, keine abgeklärten Veteranen, die jeden Riss kennen, keine Wunderkinder, die komplexe Systeme im Kopf modellieren, keine umfassenden Anforderungsdokumente und keine vorsichtigen Rollout-Systeme, die Teile der Nutzerschaft zu Versuchskaninchen machen.
Mit guten Tests könnte man ein System sogar zufällig verändern und aufhören, sobald eine Verbesserung herauskommt. Genau so, wie Google berichtet hat, dass KI Verbesserungen beim Alignment „entwickelt“ habe.
Trotzdem werden Testentwickler höchstens halb so gut bezahlt, Testabteilungen sind vergleichsweise klein, QA wird in einen festen und knappen Zeitplan gepresst, und technische Helden mit QA-Hintergrund gibt es kaum. Vielleicht, weil die Arbeit abgeleitet und reaktiv wirkt.
Man entfernt ungenutzten Code samt Tests, obwohl er in Wahrheit noch verwendet wird.
Nach einer Änderung schlägt ein Test fehl, aber weil der Test fragil ist, passt man ihn an die neue Situation an; später stellt sich heraus, dass irgendetwas vom alten Verhalten abhing.
Tests sind großartig, und in hinreichend in sich abgeschlossenen Systemen können sie ausreichen. In größeren Systemen braucht man manchmal aber auch Telemetrie oder stufenweise Rollouts.
Ursprünglich war der Code dazu gedacht, die Mehrwertsteuer einer Einkaufsliste zu berechnen, wurde aber nach und nach zu einem Mittel, um den Mehrwertsteuer-Cache nach Produktkategorien zwangsweise zu aktualisieren, und kann in Kontexten aufgerufen werden, die anfangs niemand erwartet hatte.
Dasselbe gilt für Kommentare. Sie behandeln die ursprüngliche Absicht und Nebenwirkungen, aber nicht, wo diese Methode oder Klasse viel später verwendet wird oder was sie tatsächlich zu tun begonnen hat.
In einer idealen Welt würden Kommentare aktualisiert, wenn sich die umgebende Welt ändert, aber in der Praxis passiert das fast nie, solange sich der interne Code nicht mitändert.
Tests verrotten in der Regel. Es wirkt, als hätten auch Tests ein Verfallsdatum, und irgendwann beginnen einige Tests zu sterben.
Abhängigkeitsprobleme, veränderte API-Erwartungen, Sicherheitsupdates, abgelaufene Konten und Zugangsdaten, Änderungen an Maschinen-Endpunkten und Zuständen vermischen sich so, dass Testergebnisse nicht mehr auf die Korrektheit des Programms hinweisen.
Der Grenznutzen, einen einzelnen kaputten Test zu reparieren, ist meist sehr gering, weshalb man ihn oft einfach abschaltet oder ihn dazu zwingt, „bestanden“ zu melden, obwohl es eigentlich ein Fehler sein müsste.
Nach 10 oder 20 Jahren wiederholt sich das so lange, bis sich schnell „Tests, denen man tatsächlich vertraut“ und „Tests, für deren Reparatur oder Aufräumen man zu beschäftigt ist“ trennen.
Welche Tests gut oder schlecht sind, wird zu Stammeswissen, das durch Job- und Rollenwechsel verschwindet, und irgendwann wird der ganze Haufen aus „Tests, die lügen, dass es funktioniert“ und „Tests, bei denen niemand mehr prüft, ob ein Fehlschlag wahr ist“ selbst zufällig tragend.
Besser wäre es zu sagen, dass Programmierer Tests schreiben, sie zusammen mit dem Code aufbewahren und im Build-Prozess automatisch ausführen sollten.
Aber auch richtig betriebene testgetriebene Entwicklung kann meiner Ansicht nach gutes Design und gute Praktiken nicht ersetzen. Nicht einmal eine sehr einfache Spezifikation lässt sich durch Tests ersetzen.
Wenn nur spezifiziert ist, dass
f(S)einen String zurückgibt, der an sich selbst angehängt wurde, ist es schwierig, allein mit offensichtlichen Tests, diefals Blackbox behandeln, zu verifizieren, dassfkorrekt ist. Auch formale Spezifikationen sind wichtig.Man kann ein paar Stellen stichprobenartig prüfen, aber wenn ein einzelner magisch falscher Wert katastrophal wäre, zeigen Tests das nicht.
Man kann Software-Archäologie, erfahrene Veteranen, Wunderkinder, die Systeme im Kopf modellieren, umfassende Anforderungsdokumente und Rollout-Systeme, die einige Nutzer zu Versuchskaninchen machen, satirisch betrachten, aber all das sind Reaktionen darauf, dass Software schwierig ist. Und Software ist wirklich schwierig.
In der Regel müssen Softwareteams die Qualität ihrer eigenen Arbeit direkt verantworten und können Probleme nicht über das Organigramm hinweg weiterreichen.
Dass ein zunächst unwichtiger Stud später Last trägt, kann ich nachvollziehen, aber aus meiner Erfahrung wirkt es wie ein Zeichen für faules Design.
Zumindest beim Bau von Software kann man erkennen, dass man gerade versucht, einen Teil des Hauses auf einem dekorativen Stud abzustützen; und wenn man sich dann entscheidet, es einfach so zu lassen, statt eine bessere neue Struktur zu schaffen, macht das das Entwicklerteam später ziemlich unglücklich.
Ich stimme dem Text zu, aber es ist deutlich angenehmer, an einem Ort zu arbeiten, an dem man erwarten kann, solche Entdeckungen nicht allzu oft zu machen.
Der Punkt des Textes ist weniger, dekorative Studs nicht als tragende Elemente zu verwenden, sondern zu erkennen, dass jemand vor deiner Zeit genau das getan haben könnte.
Das ist sogar noch konservativer als die grundlegende Interpretation von Chestertons Zaun, und selbst diese grundlegende Interpretation wird von vielen als zu einschränkend abgetan.
Für mich trifft der Text ins Schwarze. Programmiersprachlich gesprochen habe ich tatsächlich erlebt, dass ich eine „dekorative“ Leiste entfernt habe und mir daraufhin die Decke auf den Kopf gefallen ist.
Ob ein System robust ist oder gefährlich nicht skalieren kann, hängt vom Kontext ab.
Man kann jederzeit Gedankenexperimente machen wie: „Was, wenn das Sales-Team doppelt so groß wird und Kunden so schnell wie möglich verkauft und onboardet, bis wir 100 % des Marktes haben?“ Und selbst unter solchen Bedingungen kann es in Ordnung sein, eine Datenbank wie eine Message Queue zu verwenden.
Wenn das Entwicklerteam dadurch gelitten hat, war es ein Fehler: Die Wartung war schwierig, oder der Betrieb wurde zur Hölle.
Aber dekorative Studs in Software als tragende Elemente zu verwenden, führt nicht zwangsläufig dazu. Es gibt viele Systeme, die unauffällig und zufrieden ihren Zweck erfüllen und dabei die Monate sparen, die eine „richtige“ Lösung gekostet hätte.
Der restliche Codebestand schickt niemals ungültige Eingaben, also ist dieser Zweig toter Code und trägt keine Last.
Dann kommt irgendwann ein Bug hinein, der ungültige Eingaben schickt, und dieser Zweig verarbeitet sie pflichtbewusst und erholt sich davon. In diesem Moment wird der Zweig zu einem lasttragenden Zweig.
Mein liebstes Beispiel für ein „versehentlich lasttragendes“ Artefakt, das ich gesehen habe, war ein falsch konfiguriertes
sudo.Für den Befehl
findwar passwortlosessudoerlaubt, sodass über-execbeliebige Codeausführung als Root leicht möglich war; mehrere wichtige Support-Skripte des Produkts waren so geschrieben, dass sie genau das nutzten.Im Grunde war das lasttragende Rechteausweitung.
Vor einigen Jahren haben wir unsere Küche umgebaut.
An einem Ende der alten Küche verlief ein großer Träger, der bei einem Umbau vor unserem Hauskauf hinzugefügt worden war, um ein zweites Stockwerk zu tragen. Um die Küche zu erweitern, mussten wir ihn entfernen.
Als wir die Decke öffneten, stellte sich heraus, dass dieser Träger etwa zwei Fuß rechts von der Stelle lag, an der er die Wand im Obergeschoss hätte tragen sollen.
Am Ende haben wir es korrigiert und den Träger in die Wand im Obergeschoss verlegt, und alles ging gut. Als ich aber nach der ursprünglichen Position fragte, sagte der Bauunternehmer sinngemäß:
„Da gab es jemanden, der es richtig machen wollte, und jemanden, dem es egal war. Qualität richtet sich am Ende nach der niedrigsten Einstellung.“
Ein Vorteil von Software gegenüber physischen Systemen ist, dass man die Absicht im Code leicht mit Kommentaren und Typen dokumentieren kann, um sie klarer zu machen.
Gerade in dynamischen Sprachen wie Python ist das nicht perfekt, hilft aber enorm.
Die Entsprechung eines lasttragenden Studs könnte ein Hackathon-Projekt sein, von dem niemand dachte, dass es je in Production landen würde.
Tatsächlich besteht ein großer Teil unserer Arbeit darin, etwas zusammenzuhacken, bis es gerade so läuft, und dann zur nächsten Sache überzugehen.
Denn Wartungsplanung muss wissen, welche „Last“ jedes austauschbare Teil oder jede Baugruppe trägt.
Heutiges Systems Engineering hat sich leider sehr weit vom ursprünglichen Ziel entfernt, aber so war die ursprüngliche Idee.
Einer der Gründe, warum Systems-Engineering-Abteilungen heute relativ wenig Macht haben, ist, dass Finance in die Wartungsplanung eingezogen ist. Bestandsabschreibungen sind brutal, und „was halten wir als Ersatzteil vor?“ ist zumindest meiner Erfahrung nach nur noch selten eine Entscheidung des Systems Engineering.
Das Ergebnis ist vorhersehbar, wird aber teilweise dadurch ausgeglichen, dass die Standards des Wartungspersonals in der Luft- und Raumfahrt sehr hoch sind. Verglichen etwa mit Waschmaschinen-Reparateuren sind sie ziemlich beeindruckend.
Natürlich würde Finance auch diese Standards gern um ein paar Stufen senken.
Aber ich bin immer wieder erstaunt, wie oft man Systeme sieht, in denen eine „dekorativ“ wirkende vorgelagerte Komponente in Wirklichkeit eine Geschwindigkeitsbegrenzung setzt, sodass der Rest durchdreht, wenn man sie entfernt.
Das erinnert mich an Fälle, in denen Nutzer einen Software-Bug unbewusst ausnutzen und ihn in ihren normalen Arbeitsablauf integrieren.
Wenn man den Bug dann behebt, bricht der Arbeitsablauf, und es gibt Beschwerden.
Es hieß: „Es war leicht zu erkennen, warum er dort war. Er war Teil der Trennwand eines Schranks.“ Aber im Lauf der Zeit wurde er versehentlich lasttragend, und durch andere fehlerhafte Strukturänderungen half dieser Stud nun dabei, das zweite Stockwerk des Hauses zu tragen.
Allerdings war offensichtlich nicht leicht zu erkennen, warum er dort war. Außerdem überzeugt mich nicht, dass er versehentlich lasttragend wurde.
Es scheint durchaus möglich, dass man ihn aus Gründen, die dir falsch erscheinen, den damaligen Leuten aber nicht, absichtlich lasttragend gemacht hat.
Nur sagt einem das Wissen, warum er ursprünglich dort war, nicht, was er heute tut.
Ein Postdoc der Physik, mit dem ich früher zusammenarbeitete, brachte manchmal ein Schild an Geräteaufbauten an:
„Nicht berühren. Verborgene Gefahr.“
Das Labor war voller kluger Leute, die gewohnt waren, etwas anzusehen und selbst vernünftig zu entscheiden, ob man es verändern könne.
Dieses Schild war eine Warnung, dieses Urteil nicht zu vorschnell zu fällen.