3 Punkte von GN⁺ 2024-10-01 | 1 Kommentare | Auf WhatsApp teilen
  • Riot Games betrachtet technische Schulden als Code oder Daten, für die künftige Entwickler den Preis zahlen müssen, und formuliert anhand von Beispielen aus der Entwicklung von League of Legends eine gemeinsame Sprache zur Bewertung solcher Schulden.
  • Die Bewertungskriterien sind Auswirkung, Behebungskosten und Ansteckung; insbesondere bezeichnet Ansteckung, in welchem Maß sich Schulden im Lauf der Zeit auf andere Systeme, Daten und Entwicklungspraktiken ausbreiten.
  • Die Arten technischer Schulden werden in Local Debt, das in der internen Implementierung eingeschlossen ist, MacGyver Debt, das zwei Systeme provisorisch verbindet, Foundational Debt, bei dem tiefe Annahmen in die Struktur eingebrannt sind, und Data Debt, bei dem sich Inhalte auf Defekten aufstapeln, unterteilt.
  • Jarvans Cataclysm, die parallele Nutzung von std::string und AString, die Verwendung von Lua in BlockBuilder und der block parameter naming bug dienen als konkrete Beispiele für die jeweiligen Typen.
  • Schulden mit geringer Ansteckung können unter Umständen lange bestehen bleiben, doch bei Schulden mit hoher Ansteckung wachsen Behebungskosten und Auswirkung mit der Zeit, weshalb der Ausbreitungsweg frühzeitig unterbrochen werden sollte.

Drei Achsen zur Bewertung technischer Schulden

  • Technische Schulden sind „Code oder Daten, für die künftige Entwickler den Preis zahlen müssen“
  • Um zu entscheiden, ob man eine bestimmte Schuld jetzt beheben, später angehen oder realistisch gesehen bestehen lassen sollte, braucht es gemeinsame Messkriterien.
  • Riot bewertet technische Schulden entlang von drei Achsen.
    • impact: Auswirkungen auf Spieler und Entwickler
    • fix cost: Zeitaufwand für die Behebung und Risiko beim Deployment
    • contagion: wie stark sich das Problem ausbreitet, wenn man es unverändert lässt
  • impact: sichtbare Kosten für Spieler und Entwickler

    • Für Spieler zeigt es sich als Bugs, fehlende Funktionen oder unerwartetes Verhalten.
    • Für Entwickler summiert es sich als Verzögerungen bei der Umsetzung, Behinderungen im Workflow und unnötige Details, die man sich merken muss.
    • Mit Entwicklern sind hier nicht nur Engineers gemeint, sondern auch Rollen, die an der Spieleentwicklung beteiligt sind, etwa Designer oder VFX-Artists.
    • Manche Schulden hindern Engineers daran, neuen Code zu schreiben, andere behindern Designer beim Schreiben neuer Skripte oder VFX-Artists beim Erstellen neuer Partikel.
  • fix cost: Implementierungszeit und Deployment-Risiko

    • Zu den Behebungskosten gehört nicht nur die eigentliche Entwicklungszeit, sondern auch das Risiko, das beim Ausrollen der Änderungen entsteht.
    • Ein einfacher Fehler in einer einzelnen Funktion kann in wenigen Minuten behoben werden, tiefe Annahmen, die sich durch die gesamte Codebasis des Spiels ziehen, können jedoch Wochen oder Monate dauern.
    • Selbst ein „falsches“ System kann bereits als Werkzeug dienen, um gute Spielinhalte zu erstellen; in dem Moment, in dem man es korrigiert, können bestehende Inhalte kaputtgehen.
    • Wenn man zum Beispiel die Fehlerbehandlung der Scripting-Engine oder die Berechnung von Partikel-Erzeugungszeiten ändert, kann das Auswirkungen auf mehr als 500 Fähigkeiten von über 140 Champions haben.
  • contagion: wie stark es sich mit der Zeit ausbreitet

    • contagion bezeichnet, wie stark sich technische Schulden auf andere Systeme, Daten und Entwicklungsweisen übertragen, wenn man sie unverändert lässt.
    • Die Ausbreitung geschieht über Schnittstellen zu problematischen Systemen, durch Copy-and-paste von darauf aufbauenden Daten und durch Veränderungen in der Art, wie neue Funktionen umgesetzt werden.
    • Bei gut isolierten Schulden unterscheidet sich der Aufwand einer späteren Behebung nicht stark von einer sofortigen.
    • Schulden mit hoher Ansteckung werden mit der Zeit immer schwerer zu beheben, und je mehr Systeme mit dem zugrunde liegenden Kompromiss infiziert werden, desto größer wird auch die Auswirkung.

Local Debt: eine Black Box, die nur intern schmutzig ist

  • Local Debt ähnelt dem klassischen Black-Box-Programmiermodell.
  • Von außen betrachtet funktioniert das System stabil, intern kann die Implementierung jedoch furchtbar oder verwirrend sein.
    • Beispiel: Fähigkeiten, Netzwerkschicht, Scripting-Engine
  • Wenn man sich bei der Entwicklung angrenzender Systeme der internen Schulden nicht bewusst sein muss, ist die Ansteckung eher gering.
  • Analogie aus der realen Welt: das menschliche Auge

    • Das menschliche Auge nimmt Bilder konstruktionsbedingt auf dem Kopf stehend auf, und die Nerven der Netzhaut erzeugen nahe der Mitte jedes Auges einen blinden Fleck.
    • Das Sehzentrum des Gehirns dreht die Daten um und füllt den blinden Fleck auf, sodass der Rest des Gehirns mit einem „korrekten“ Bild interagieren kann.
    • Diese Besonderheiten sind auf das Auge und das Sehnervensystem lokalisiert, und andere Systeme können sie leicht umgehen; deshalb ist es „gut genug“.
  • Beispiel aus League: Jarvans Cataclysm

    • Jarvans Cataclysm wird bis heute als minion erzeugt.
    • Designer können ein Werkzeug verwenden, das „invisible minion“ erzeugt, wenn sie Gameplay-Effekte an einer bestimmten Position oder einer Menge von Positionen anbringen wollen.
    • RiotXypherous erklärt in einem Reddit-Kommentar, was mit „minion“ hier gemeint ist.
    • Solche Spielobjekte sind ein stabiler und gut verstandener Weg, Script-Logik zu verfolgen und auszuführen.
    • Damit Spieler Jarvans Wand nicht verlassen können, werden exakt 24 minion benötigt.
    • Früher waren es 12, aber da Spieler gelegentlich zwischen den Wänden hindurch entkamen, erhöhte Riot Exgeniar die Zahl auf 24.
    • Die Alternative ist eine ring-terrain-Struktur, also ein einzelnes Logikstück zur Steuerung der pathability von Cataclysm; das könnte die Logik aufräumen und die Rechenkosten leicht senken.
  • Bewertung von Cataclysm

    • impact: 1/5
      • Die Tatsache, dass die Wand als minion erzeugt wird, hat fast keine Auswirkungen auf andere Entwickler, die neue Inhalte erstellen.
      • „Jarvan Ult Hitch“ war das Ergebnis des Zusammenspiels dieser Schuld mit einem Lade-Bug, der eine fehlende Definition für auto-attack lesen wollte.
    • fix cost: 2/5
      • Derzeit lässt sich ohne neuen Code keine benutzerdefinierte Geometrie mit zusammengesetzten Formen erstellen.
      • Um einen ringförmigen „area trigger“ zu bauen, wäre dedizierter Mathematik-Code für Ring-Kollisionsberechnungen nötig.
      • Riot untersucht für andere Zwecke Constructive Solid Geometry, was die Behebungskosten deutlich senken könnte.
    • contagion: 1/5
      • Bei der Entwicklung von Features muss die Implementierung von Jarvans Wand nicht berücksichtigt werden; sie ist gut isoliert.
      • Das Ansteckungsrisiko besteht darin, dass andere Designer diese Implementierung per Copy-and-paste für neue Champions übernehmen, und das ist tatsächlich gelegentlich vorgekommen.
      • Als Implementierungsproblem ist die potenzielle Ausbreitung von Cataclysm gering und gut verstanden.
  • Umgang mit Local Debt

    • Ein typisches Merkmal von Local Debt ist ein niedriger contagion-Wert.
    • Wenn impact höher ist als fix cost, neigen Entwickler mit gutem Verantwortungsbewusstsein dazu, das Problem zu beheben, bevor zu viel Zeit vergeht.
    • Wenn wirklich keine Ansteckung besteht, ist es sicher, solche Schulden so lange bestehen zu lassen, wie nötig.
    • Einer der großen Fehler ist es, sich sofort auf Local Debt zu stürzen, nur weil sie den Perfektionismus von Engineers reizt, obwohl sie keine ausreichend breite Auswirkung hat.
    • Weil der Änderungsumfang lokal begrenzt ist, sind Verifikation der Behebung und Regressionstests normalerweise einfach.
    • Zu den jüngst behobenen Beispielen gehören Bugs rund um inhibitor, Janna’s Monsoon und Tear of the Goddess.
      • Ein Bug, bei dem inhibitor unter bestimmten Umständen Champions zum Pathing auf die Koordinaten 0,0,0 veranlassten
      • Ein Problem, bei dem Janna’s Monsoon spell shield ignorierte
      • Ein Problem, bei dem Tear of the Goddess bei Manalosen Casts Stapel erhielt

MacGyver Debt: Zwei Systeme mit Gaffa zusammengeklebt

  • MacGyver Debt ist eine nach der TV-Show MacGyver aus der Mitte der 1980er benannte Kategorie
  • Im Kontext technischer Schulden bedeutet es, dass zwei kollidierende Systeme an den Schnittstellen im gesamten Codebase mit „Gaffa“ zusammengehalten werden
  • Analogie aus der realen Welt: Seattle

    • In Seattle gab es früher zwei konkurrierende Siedlungen, jede mit ihrem eigenen Raster
    • Als die beiden Siedlungen zur heutigen Emerald City heranwuchsen, wurden die leicht unterschiedlichen Raster zusammengeführt, wodurch seltsam geformte Blöcke und Gebäude sowie eine ineffiziente Flächennutzung entstanden
  • Beispiel aus League: std::string und AString

    • Im League-Codebase existieren sowohl C++ std::string als auch Riots benutzerdefinierte Klasse AString nebeneinander
    • Beide sind Wege, Strings zu speichern, zu verändern und weiterzugeben
    • Riot ist der Ansicht, dass std::string viele „versteckte“ Speicherallokationen und Performance-Kosten verursacht und es leicht macht, schlechten Code zu schreiben
    • AString wurde mit Blick auf sorgfältiges Speichermanagement entworfen
    • Die Ersatzstrategie bestand darin, beide Systeme parallel bestehen zu lassen und Konvertierungen zwischen ihnen über .c_str() und .Get() zu ermöglichen
    • Zu AString wurden Verbesserungen hinzugefügt, um die Benutzbarkeit zu erhöhen, und Engineers wurden ermutigt, beim Ändern von Code eigenständig std::string zu ersetzen
    • Auf diese Weise verschwindet std::string langsam Schritt für Schritt, und auch die „Gaffa“-Schnittstellen zwischen den beiden Systemen werden mit der Bereinigung des Codes weniger
  • Bewertung von std::string vs. AString

    • impact: 2/5
      • Die meisten Allokationen mit hoher Auswirkung, die von std::string ausgingen, wurden bereits durch Profiling entfernt
      • Die derzeitigen Hauptkosten sind die kleinen mentalen Umschaltkosten beim Konvertieren von einem System ins andere
    • fix cost: 3/5
      • Die Umstellung auf AString ist kein simples Find-and-Replace
      • Für AString gibt es zweckgebundene Varianten wie AStackString für initiale Allokationen im Stack-Speicher, ARefString für statische String-Referenzen und heap-allokationsbasiertes AString
      • Für den richtigen Ersatz muss ein Mensch jede Stelle direkt ansehen und beurteilen, und das schrittweise Abschaffen des bestehenden Systems wird langwierig und langsam sein
    • contagion: -2/5
      • Indem AString leichter nutzbar als std::string gemacht wurde, wurde contagion in eine günstige Richtung gedreht
      • Jedes Mal, wenn ein Engineer Änderungen am Game-Code eincheckt, steigt die Wahrscheinlichkeit, dass sich AString weiter verbreitet
  • Wie man MacGyver Debt behebt

    • Ein großer Kostenfaktor von MacGyver Debt sind oft die kognitiven Kosten, wenn man über Grenzen hinweg den Modus wechseln muss
    • Wenn Bugs oder Features im „falschen“ System hängen bleiben, ist die Arbeit, das Ziel in das „richtige“ System zu verlagern, meist recht direkt
    • Die relative contagion des neuen und des bestehenden Systems ist die zentrale Kennzahl
    • Wenn man das Gleichgewicht so kippt, dass das neue System ansteckender wird, gewinnt das bessere System am Ende
    • Das global bessere System muss auch auf lokaler Ebene attraktiver gemacht werden
    • Wenn Engineers unter Zeitdruck bei der täglichen Arbeit greedy optimieren und dabei dennoch den gewünschten Endzustand wählen, bewegt man sich in die richtige Richtung
    • Ein anderer Ansatz ist ein groß angelegtes brute-force-Refactoring; je nachdem, wie eng die Systeme aufeinander abbildbar sind, lässt sich ein Teil oder sogar alles mit cleveren Regex-Ausdrücken beheben

Foundational Debt: Wenn tief verankerte Annahmen in die gesamte Struktur eingebettet sind

  • Foundational Debt bedeutet, dass eine Annahme tief im System so eingebrannt ist, dass sie die gesamte Funktionsweise prägt
  • Für erfahrene Nutzer des Systems kann das schwer zu erkennen sein, weil es für sie einfach „schon immer so“ ist
  • Analogie aus der realen Welt: United States Customary Units

    • Wer in den USA aufgewachsen ist, lernt Umrechnungen wie 1 Meile = 5.280 Fuß, 1 Quart = 2 Pints und 1 Gallone = 4 Quarts auswendig
    • Die US-Regierung hat eine Umstellung auf das metrische System mehrfach erwogen, gehört aber weiterhin zu den sieben Ländern, die das Système International nicht als offizielles Maßsystem übernommen haben
    • Diese Schuld ist in Straßenschilder, Rezepte, Grundschulen und die Köpfe der Menschen eingebrannt
  • Fallbeispiel aus League: BlockBuilder und Lua

    • Zu den großen Fällen von Foundational Debt, mit denen Riot zu tun hatte, gehören Determinism in League of Legends und Game Data Server
    • Auch die Verwendung der Lua scripting language in League ist ein Beispiel für Foundational Debt
    • League-Designer erstellen mit einem Tool namens BlockBuilder komplexe Verhaltensweisen, indem sie Funktionsblöcke miteinander verbinden
    • Zu den Funktionsblöcken gehören das Berechnen der Distanz zwischen Punkten, das Erzeugen von Minions, die Verarbeitung von Damage sowie verschiedene Arten von script flow control
    • Die Menge der Operationen, die Designer auswählen können, ist vielfältig, aber begrenzt, und auch die Parameter der einzelnen Operationen sind eingeschränkt
    • In der Anfangszeit von League of Legends wurde entschieden, Blöcke und Parameter nicht in einem einfachen, eingeschränkten und auf die Daten abgestimmten Format zu speichern, sondern in Arrays und Tables der mächtigen, für diesen Zweck aber übermäßig komplexen Sprache Lua
    • In den rund zehn Jahren Spielentwicklung danach baute alles auf dieser Grundlage auf, und die Manipulation von Lua-Objekten wurde zu einer der häufigsten Aufgaben in der Engine
  • Bewertung von BlockBuilder-Lua

    • impact: 4/5
      • Die Diskrepanz zwischen Lua und diesem Problemraum verursacht hohe Kosten
      • In jedem Frame der BlockBuilder-Logik wird der Callstack durch etwa sechs Marshalling-Stack-Frames verschmutzt
      • Marshalling ist im Hinblick auf die CPU-Auslastung des Servers nicht billig
      • Diffs von script-Änderungen zu lesen ist unnötig schwierig
      • Um script-Dateien zu parsen oder zu durchsuchen und ihre Funktion zu verstehen, braucht man ein ziemlich tiefes Verständnis der Sprache Lua
    • fix cost: 4/5
      • Lua ist tief in der Engine verankert und daher schwer zu entfernen
      • Ein aktuell vorgeschlagener Ansatz ist, eine Wrapper-Klasse zu bauen, die sich wie ein Lua-Objekt verhält, intern aber eine deutlich einfachere Struct ist, um das Innere des Scripting schrittweise in eine passendere Form zu überführen
      • Welcher Ansatz auch gewählt wird, er muss vorsichtig und mit Bedacht umgesetzt werden
    • contagion: 4/5
      • Jedes System, das mit dem Scripting in Berührung kommt, wird von den Operationen und Anforderungen des Lua-Backends geprägt
      • Scripting ist eine zentrale logische Einheit in LoL
      • Riot fügt im Durchschnitt etwa alle 3 bis 4 Tage einen neuen Building Block hinzu, und jeder Building Block manipuliert Lua-Objekte direkt
      • Je länger Lua nicht ersetzt wird, desto schwieriger wird es, Lua zu ersetzen
  • Wie Foundational Debt reduziert wird

    • Foundational Debt erzielt in der Regel hohe Werte auf allen drei Achsen: impact, fix cost und contagion
    • Hohe fix cost führen dazu, dass ein unvollkommenes System weiterverwendet wird, und manchmal ist genau das die richtige Entscheidung
    • Wegen des hohen impact und der hohen contagion kann die Behebung schwerwiegender Foundational Debt jedoch große Vorteile bringen
    • Die häufigste bei Riot beobachtete Strategie zur Behebung besteht darin, ein neues System neben das bestehende zu stellen
    • Wenn möglich, wird bestehende Foundational Debt in MacGyver Debt umgewandelt, und durch conversion operations wird schrittweise zwischen neuem und altem System hin- und hergewechselt, während die Portierung langsam voranschreitet
    • Dieser Ansatz begrenzt das Risiko, während man in einzelnen Bereichen bereits Vorteile erzielt
    • Wenn eine solche Umstellung nicht möglich ist, kann man einen compile time switch oder, wenn möglich, einen loading time switch einführen, um Vertrauen in das neue System aufzubauen
    • Der Ansatz mit compile time switch wird beim GDS-Übergang verwendet
    • Der Ansatz mit loading time switch hat sich bei Determinism als wirksam erwiesen

Data Debt: Ein Zustand, in dem sich große Mengen an Inhalten auf Fehlern auftürmen

  • Data Debt entsteht, wenn sich viele Inhalte auf technischen Schulden anderer Kategorien ansammeln
  • Der Ausgangspunkt kann ein Bug im Scripting-System sein, ein für ein Item ungeeignetes Dateiformat oder zwei Systeme, die nicht gut zusammenpassen
  • Wenn auf diesem Codefehler große Mengen an Inhalten wie Art, Scripts und Sounds erstellt werden, wird das Beheben der ursprünglichen technischen Schuld sehr riskant
  • Mit der Zeit wird es quälend schwer herauszufinden, was kaputtgehen könnte
  • Analogie aus der realen Welt: DNA

    • Das Genom eines Organismus häuft sich über Hunderte Millionen Jahre langsam durch Mutationen, Transkriptionsfehler und evolutionären Druck an
    • Manche Kopierfehler sind nutzlos, aber nicht schädlich, manche sind schädlich und manche bringen starke Vorteile
    • Es ist extrem schwer herauszufinden, was ein bestimmtes DNA-Stück tatsächlich tut
    • Wir verstehen vollständig, was ein Basenpaar bedeutet und wie Mengen von Basenpaaren in Aminosäuren für die Proteinkonstruktion übersetzt werden
    • Auch über einige nicht-kodierende Rollen der DNA beginnen wir mehr zu verstehen
    • Aber bei den mehr als 3 Milliarden Basenpaaren des menschlichen Genoms gibt es noch viele Bereiche, die wir kaum verstehen
    • Die CRISPR-Episode von Radiolab behandelt eines dieser kürzlich gelösten Rätsel
  • Fall aus League: Bug bei der Benennung von Block-Parametern

    • In League of Legends hat Data Debt den größten Einfluss, wenn sie aus etwas, das ursprünglich eine kleine Änderung gewesen wäre, eine mühsame Aufgabe macht
    • Game Engineers bauen tiefes Wissen darüber auf, wie die Spielsysteme implementiert sind, und werden geübt darin vorherzusagen, welche Codeänderungen welche Daten kaputtmachen könnten
    • Data Debt ist eine der wichtigsten Überlegungen bei Änderungen an der LoL-Engine
    • Ein vor einigen Jahren behobener Fall von Data Debt war ein Bug im Zusammenhang mit Block-Parametern in der BlockBuilder-Scripting-Sprache
    • Wenn man im toy example die Rüstung des Owners mit einer Variablen und einer Konstante erhöhen will, wäre der erwartete Wert 25 Bonus-Rüstung aus Variable Delta 20 plus Konstante 5
    • Wenn der Variablenname mit dem Parameternamen identisch war, lag das Ergebnis früher bei 40
    • Warum es nicht 45 wurde, weiß laut Autor auch der Autor selbst nicht
  • Der tatsächliche Ablauf der Behebung

    • Als der Engineer NoopMoney aus dem Champions-Team dieses Verhalten korrigieren wollte, bestand die eigentliche Codeänderung nur aus dem Löschen von 4 Zeilen
    • Doch hochgradig ansteckende Schulden erforderten selbst bei kleinen Änderungen eine gründliche Planung
    • An jeder Stelle in den 400.000 Zeilen Script von LoL konnte jeder numerische Parameter durch diesen Bug verdoppelt worden sein
    • Das größere Problem war, dass das Spiel hinsichtlich Balance und Tuning an diese potenziell verdoppelten Werte angepasst war, sodass die betreffenden Scripts „korrekt“ funktionierten
    • NoopMoney musste den Fix so bauen, dass er auf Live gegen unerwartete Bugs umschaltbar war
    • Um zu identifizieren, welche Scripts von diesem Bug abhingen, führte er umfassende Regex-Suchen und einen QA-Sweep durch
    • Am Ende waren die durch die Änderung verursachten Probleme vergleichsweise klein, und nur eine kleine Zahl von Champion-Scripts musste angepasst werden
    • Wegen der Data Debt war es schwer, die Folgen vorherzusagen
  • Bewertung des Parameter-Naming-Bugs

    • impact: 2/5
      • Die Auswirkungen beim Auftreten waren gering
      • Übergebene Werte konnten verdoppelt und Konstanten verworfen werden
      • Für Designer und Engineers, die davon wussten, wurde es zu einem weiteren nutzlosen Stück tribal knowledge, das sie im Kopf behalten mussten
      • Developer mindshare ist eine zu wertvolle Ressource, um so verschwendet zu werden
    • fix cost: 2/5
      • Insgesamt war die Behebung selbst geradlinig
      • Durch einen Live-Feature-Toggle ließ sich das Vertrauen in die Sicherheit des Fixes erhöhen
      • Der teuerste Teil war das anfängliche Screening, um den Umfang des Problems festzulegen und Testziele zu bestimmen
    • contagion: 4/5
      • Unglücklicherweise zielte dieser Bug auf ein sehr logisches Verhalten
      • Wenn man einer Unit Schaden zufügen will, ist es völlig logisch, den Wert in einer Variablen namens „Damage“ zu speichern
      • Der ApplyDamage-Block nahm den Betrag über einen Parameter mit demselben Namen entgegen, wodurch der Bug ausgelöst wurde
      • Wenn jemand anders einen ähnlichen Spell per Copy/Paste erstellt, verbreitet sich der Bug weiter
  • Warum Data Debt so ansteckend ist

    • Data Debt wird im Allgemeinen mit hohen Behebungskosten bewertet, weil sie die Bewertung der Änderungsfolgen erschwert
    • Noch besorgniserregender ist, dass Daten aufgrund ihrer Natur fast immer hochgradig ansteckend sind
    • Es ist in der Regel erlaubt, neue Daten zu erzeugen, indem bestehende Daten per Copy/Paste übernommen werden
    • Wenn man beim Erstellen eines neuen Skillshot-Spells mit Ezreal’s Mystic Shot beginnt, spart das viel Zeit, und Probleme in den bestehenden Daten werden auch auf deren Nachkommen übertragen
    • Da Daten kaum technische Prüfungen ähnlich einem Code Review durchlaufen, ist es selbst dann schwer, die Verbreitung schlechter Praktiken zu bemerken und zu stoppen, wenn diese weithin bekannt sind
    • Um Probleme in Daten zu beheben, muss in der Regel ein Mensch mit Augen und Gehirn direkt prüfen; Compiler und formale Logik allein reichen nicht aus
  • Zwei Ansätze zum Beheben von Data Debt

    • Der erste Ansatz ist die Do-it-right-Checkbox
      • Dabei wird für Data Creator ein Toggle zwischen dem bestehenden „kaputten“ Verhalten und dem neuen „korrigierten“ Verhalten geschaffen
      • Idealerweise ist die korrigierte Version der Standard, während alter Content die kaputte Version nutzt
      • Danach kann man wie bei MacGyver Debt mit langsamer, stetiger Ersetzung auf die neue Version migrieren
      • Der Nachteil ist ein dauerhafter Kostenfaktor, weil der Editing-UI immer mehr unnötige Elemente hinzugefügt werden
    • Der zweite Ansatz ist just fix the damn thing
      • Diesen Ansatz nutzte NoopMoney beim Parameter-Naming-Bug
      • Nach dem Beheben des Bugs versucht man, alle Daten zu reparieren, die sinnvollerweise davon betroffen sind
      • Weniger beängstigend wird das durch Techniken wie viel grep und Regex-Suche, um den theoretischen Einfluss zu verstehen, gezieltes Testen und einen Toggle, um nach dem Shippen zum alten Verhalten zurückzukehren, falls schlimmere Auslassungen entdeckt werden
      • Determinism hilft bei solchen Änderungstests sehr, weil sich damit prüfen lässt, ob der Server vor und nach der Änderung dasselbe Ergebnis erzeugt

Zusammenfassung: Ansteckung muss in die Kostenbewertung einfließen

  • Die Messgrößen für technische Schulden sind impact, fix cost und contagion
    • impact ist die Auswirkung auf Kunden und Entwickler
    • fix cost sind Zeit und Risiko
    • contagion ist das Ausmaß, in dem sich das Problem verbreitet
  • Die meisten Entwickler berücksichtigen impact und fix cost regelmäßig, aber über contagion wird vergleichsweise selten gesprochen
  • contagion kann zum schlimmsten Feind von Entwicklern werden, wenn sich ein Problem tief eingräbt und immer schwerer zu beseitigen ist
  • Umgekehrt kann man contagion zur Waffe machen, wenn man den Fix ansteckender macht als das Problem
  • Die meisten technischen Schulden, die man bei League gesehen hat, lassen sich einer von vier Kategorien zuordnen
    • Local Debt: Schulden wie eine Black Box mit unordentlichem Innenleben
    • MacGyver Debt: Schulden, bei denen zwei oder mehr Systeme mit Conversion Functions wie mit Klebeband zusammengehalten werden
    • Foundational Debt: Schulden, bei denen die gesamte Struktur auf unglücklichen Annahmen aufgebaut ist
    • Data Debt: Schulden, bei denen sich riesige Mengen an Daten auf anderen Arten von Schulden ansammeln und Korrekturen riskant und zeitaufwendig machen

1 Kommentare

 
GN⁺ 2024-10-01
Hacker-News-Kommentare
  • Ansteckung ist der Grund schlechthin, warum Interfaces eines der wichtigsten Elemente im Design sind und gründlich durchdacht werden müssen
    Wenn man ein suboptimales Implementierungsdetail hinter ein schönes Interface setzt, kann man es später bei Gelegenheit leicht aufräumen, aber umgekehrt gilt das fast nie

    • Stimme zu, aber um es richtig zu entwerfen, fehlt meistens eines von zwei Dingen: Zeit zum Entwerfen und das Wissen darüber, was man heute und in einem Jahr genau tun muss
      Manchmal fehlt auch beides
      In solchen Fällen kann man verhindern, dass sich die Ansteckung zu stark ausbreitet, indem man kleine Module und Single Responsibility kombinatorisch erzwingt. Das braucht weder viel Wissen über die Zukunft noch viel Zeit, und man sollte Interfaces mit großer Oberfläche im Matroschka-Stil vermeiden, die verschiedene Verhaltensvarianten über Parameter steuern. Konfiguration, Parsing und Verhaltensentscheidungen sollte man an den Rand der Logik verschieben und nicht in das gesamte zugrunde liegende Modell einsickern lassen
    • Stimme zu, aber robuste und zukunftsfeste Interfaces zu entwerfen war schon immer eines der schwierigsten Probleme in der Softwareentwicklung
      Selbst wenn man bewusst damit anfängt, technische Schulden um jeden Preis zu vermeiden, ist es schwer, es richtig zu machen, und es braucht mehr als bloß technisches Selbstvertrauen oder Architekturvision. In der Praxis landet man im Bereich der Vorhersage der Zukunft
    • Um ein gutes Interface freizulegen, muss man verstehen, wie eine gute interne Implementierung aussieht
      Wenn man eine dumme Implementierung implizit in das Interface einbrennt, lässt sich das oft nicht mehr reparieren, indem man nur die Implementierung austauscht. Das naheliegende Beispiel ist das Verhalten von Sortierung und Paginierung. Bei Junior-Entwicklern und inzwischen auch bei vielen Seniors, die es eigentlich besser wissen sollten, beginnt es oft mit Requests, die Parameter wie limit/offset verwenden, was zu schrecklichen Performance-Problemen und merkwürdigem Verhalten führt. Wie Paginierung effizient funktioniert und welche Sortieroptionen sich performanzstark unterstützen lassen, ist wesentlich an die Datenform und die Wahl des Datenspeichers gekoppelt. Wer diesen Prozess nicht auf einer tieferen Ebene selbst durchlaufen hat, wird das darüberliegende Interface wahrscheinlich nicht richtig hinbekommen, wenn er sich nicht zuerst ausreichend tief mit der Implementierung befasst
    • Deshalb mag ich Sprachen wie OCaml oder Ada, die Interfaces sehr explizit machen
      In den meisten Fällen will ich die Implementierung gar nicht sehen, sondern nur ein sauber dokumentiertes Interface. Wenn man das Verhalten eines Interfaces nicht in einfachen Worten beschreiben kann, stimmt etwas nicht
    • In der Geschichte scheint es ziemlich viele Gegenbeispiele zu geben
      QWERTY ist bekanntlich kein optimales physisches Interface, und das Lenkrad könnte ein ähnlicher Fall sein. In der Computerwelt ist x86 das Paradebeispiel für ein oberflächlich nicht optimales Interface
  • Es überrascht mich ziemlich, dass dieser Artikel von einem Engineering Manager geschrieben wurde
    Unter den Managern, mit denen ich gearbeitet habe, gab es niemanden, der mit diesem Grad an technischem Detail über unsere Codebasis sprechen konnte. Das galt auch für Leute, die früher einmal Engineers waren
    Fairerweise muss man sagen, dass wir keine intern beförderten Manager hatten, und weil interne Leute, mich eingeschlossen, nicht mit Engineering aufhören wollen, haben wir die schlechte Gewohnheit, Manager extern einzustellen

  • Mir scheint die häufigste Schuldenart zu fehlen, die ich gesehen habe: Founder Debt
    Schulden, die Gründer machen, um schnell wertvolle Technologie auszuliefern. Dinge, die wie low-hanging fruit aussahen, werden plötzlich zum Fundament des ganzen Systems
    Auch die Gründungsdokumente vieler Länder fallen in diese Kategorie lol (aber nicht USA! USA! USA!)
    MacGyver Debt und Foundation Debt kommen dem am nächsten, treffen das Phänomen aber beide nicht ganz genau

  • Aus technischer Sicht ein großartiger Artikel
    Ich würde allerdings sagen, dass das eher eine Nomenklatur als eine „Taxonomie“ ist. Es ist weder absichtlich vollständig noch wechselseitig ausschließend, wobei ich mich irren kann. Die physischen Beispiele zu jedem Punkt waren besonders gut und haben Stoff zum Nachdenken geliefert
    Wie immer gibt es eine philosophische Kleinigkeit, an der ich hängen bleibe. Die „drei Achsen“ am Anfang sehen für mich nach den Erträgen und Investitionen eines traditionellen RoI aus, ergänzt um eine bestimmte zukunftsorientierte, bedingte Unterkategorie von Erträgen. Ich vermute, dass diese Entscheidung praktisch gut funktioniert hat, und Praktiken in der Videospielentwicklung müssen nicht absolut wissenschaftlich sein, aber ein bisschen mehr philosophische Präzision würde auch nicht schaden

  • Wurde damals auch diskutiert:
    A Taxonomy of Technical Debt - https://news.ycombinator.com/item?id=16810092 - April 2018 (113 Kommentare)
    Und das gibt es auch noch:
    A Taxonomy of Tech Debt (2018) - https://news.ycombinator.com/item?id=39782923 - März 2024 (1 Kommentar)

  • Die Erklärung, dass man technische Schulden als Code oder Daten definiert, für die künftige Entwickler die Kosten zahlen werden, gehört fast zu den besten, die ich gesehen habe
    Wie bei allen Schulden muss man beim Eingehen von Schulden einen Schwellenwert anwenden, der den unmittelbaren Bedarf gegen die künftigen Kosten abwägt. Mein Eindruck ist, dass die meisten Menschen, nicht nur Entwickler, den unmittelbaren Bedarf überschätzen und die künftigen Kosten unterschätzen
    Ich persönlich hasse fast jede Art von Schulden geradezu pathologisch. Manchmal verbringe ich einen zusätzlichen Tag damit, etwas abzuspalten, das in Zukunft nützlich sein könnte. Ich liege damit vielleicht nur in ungefähr 50 % der Fälle richtig, aber jedes Mal, wenn ich so arbeite, stärkt das die Gewohnheit und beschleunigt auch meinen normalen Workflow

  • Ich habe bisher bei drei „Startups“ gearbeitet und bin immer erst eingestiegen, nachdem sie genug Umsatz machten, um Gehälter nahe am Marktmaximum zu zahlen
    Am häufigsten habe ich gesehen, dass mehrere Gründer ihre Idee, das tatsächlich Gebaute und den Teil der Implementierung, der wirklich funktioniert, unscharf miteinander vermischen

  • Seit ich diesen Artikel zum ersten Mal gelesen habe, verwende ich beim Erklären technischer Schulden das Wort Ansteckung, und es passt ziemlich gut

  • Ich bin mir nicht sicher, ob man „lokale Schulden“ im normalen Sinn überhaupt technische Schulden nennen sollte
    Realistisch gesehen gibt es irgendwo immer schmutzige Ecken, und es ist normal, sie zu kapseln und so zu verstecken, dass niemand zu Schaden kommt. Solange sich die Anforderungen nicht ändern, muss man sie fast nie anfassen, und wenn sie sich doch ändern und man ohnehin jede Implementierung anfassen müsste, ist das okay
    Wenn die 24 Minion-Instanzen aus dem Beispiel nicht nur unelegant, sondern wirklich problematisch sind, scheint mir das eher Foundation Debt zu sein, insofern als „Minion“ zur einfachsten Grundeinheit geworden ist und es vielleicht etwas Leichteres hätte geben können

    • Im Artikel wird es etwas angesprochen, aber die Kosten sind in Wirklichkeit die kognitiven Kosten, wenn man tatsächlich daran arbeiten muss, und ich würde noch die Kosten hinzufügen, die Werkzeuge gleich zu halten
      Wenn man Entwickler dazu ermutigt, auch alte und ausgereifte Module zu verändern, die eigentlich nicht angerührt werden müssten, ist das ein guter Weg, damit sich so etwas nicht bis zu einem problematischen Maß aufstaut
  • Ein wichtiger Aspekt ist, dass man bewusst technische Schulden aufnimmt, um kurzfristig einen Nutzen zu erzielen
    Dann wird auch dieser Nutzen zu einer weiteren Achse, die man mit abwägen muss

    • Genau wie bei echten Schulden
      Du willst jetzt ein neues Gebäude errichten und die Arbeit erledigen, statt 15 Jahre zu warten, bis das Kapital da ist? Dann nimmst du einen Kredit auf
      Schulden sind ein Werkzeug, aber ein mächtiges und gefährliches. Wenn man nicht anerkennt und respektiert, dass man es benutzt, wird man verletzt. Oder jemand wird verletzt, dem du die Granate weitergereicht hast. Genau wie bei echten Schulden
    • Ich habe gehört, dass manche so etwas statt „technische Schulden“ taktische Schulden nennen
    • Meistens geht es um Geschwindigkeit