Suche
Schließen Sie dieses Suchfeld.

Empirische technische Verschuldung

Bestimmung der Kosten und des Standorts der technischen Schulden anhand von Geschwindigkeitsmessungen in einer DevOps-Organisation. Durch die Messung der Zeit, die für Änderungen benötigt wird, und der Orte, an denen Änderungen am schwierigsten sind, können wir die Zinsen, die wir für technische Schulden zahlen, abschätzen und den besten Ort finden, um mit der Rückzahlung des Kapitals zu beginnen.

Bestimmung der Kosten und des Standorts der technischen Schulden anhand von Geschwindigkeitsmessungen in einer DevOps-Organisation.

Karikatur zur technischen Schuld

Technische Schulden werden (über Wikipedia) definiert als:

Die impliziten Kosten zusätzlicher Nacharbeit, die dadurch entstehen, dass man sich jetzt für eine einfache Lösung entscheidet, anstatt einen besseren Ansatz zu wählen, der länger dauern würde.

Viele gehen davon aus, dass diese Schuld ausschließlich im Code zu finden ist, und messen die zyklomatische Komplexität, die Testabdeckung und/oder verwenden einen Linter, um festzustellen, wie viel Schuld vorhanden ist und wo sie sich befindet. Diese Maßnahmen funktionieren gut - aber sie zeigen nicht auf, welche Art von Schuld (Komplexität, fehlende Tests, schlechte Dokumentation) die größten Probleme verursacht oder wo man am besten ansetzen sollte. Sie messen auch nicht die Arten von Schulden, die schwieriger zu erkennen sind, zum Beispiel:

Duplizierung von Merkmalen
Der gesamte Baustein wird vervielfältigt, aber anders geschrieben, vielleicht in einer anderen Sprache
Kein Entwurf für Fehlersuche
Die Fehlersuche ist schwierig, wenn ein Fehler in unmittelbarer Nähe (beim Kunden, in der QA-Abteilung) auftritt, weil die Protokollierung, die Fehlerberichterstattung oder die Freigabe/Debug-Variante unzureichend sind.
Langsame Testsuite
Die Ausführung der Testsuite nimmt viel Zeit in Anspruch, was zu einem höheren Durchsatz führt.
...

Diese ziehen möglicherweise die Standarddefinition von technischer Schuld ein wenig in die Länge, daher verwenden wir bei Cristie die folgende:

Die Kosten für zusätzliche Arbeit, die dadurch entstehen, dass man sich jetzt für eine einfache Lösung entscheidet, anstatt einen besseren Ansatz zu wählen, der länger dauern würde.

Damit haben wir eine Möglichkeit, dies herauszufinden:

  1. Wenn wir davon ausgehen, dass es besser ist, sich häufiger zu engagieren
    (...und wir sagen, dass es so ist, und ermutigen die Leute, es zu tun...)
  2. dann ist der einzige Grund, warum Sie sich nicht häufiger engagieren, der, dass Sie es nicht können
  3. und den Grund dafür, dass Sie das nicht können, definieren wir als unsere technische Schuld, weil
  4. wenn wir ein Re-Factoring, eine Architektur oder eine andere Verbesserung vornehmen würden, könnten Sie das tun.

Wir können technische Schulden also wie folgt messen:

Die Zeit, die für Commits über α Standardabweichungen aufgewendet wird, die eine Datei enthalten, die α Standardabweichungen vom Üblichen entfernt ist.

Das heißt, die Menge an zusätzlicher Zeit für Commits, die Dateien enthalten, die routinemäßig in Commits erscheinen, die länger dauern.

Unsere Commit-Historie herausfinden

Wir haben ein Python-Skript geschrieben, das Subversion- und Git-Protokolle verarbeitet und erzeugt:

  • Der Autor der Übergabe
  • Die Geschäftszeit seit dem letzten Commit des Autors in irgendeinem Repo
  • Die Dateien in dieser Übertragung

Anschließend haben wir diese Übertragungen für jeden Autor normalisiert und dieses Diagramm erstellt:

Normalisierte Commit-Historie

Einige Versuche und Irrtümer (es gibt definitiv einen besseren Weg, es zu tun... aber das statistische Wissen wurde während des Experiments gelernt) festgestellt, dass die Anwendung lambda x: np.log(x) ** 2 als zwei gleichzeitige Box-Cox-Transformationen ergibt eine sehr und die umgekehrte Anwendung dieser Methode ergibt die am besten passende Linie im obigen Diagramm:

Veränderter Commit-Verlauf

Technische Schulden

Wir können nun die technische Schuld ermitteln, indem wir den z-Score (ein Maß dafür, wie "normal" eine Population ist, die normal sein sollte) für jede Datei berechnen:

[code lang=”python”]
def zscore(commits):
"Z-Score per-file for all files in commits"
perpath = collections.defaultdict(list)
for commit in commits:
for path in commit.paths:
perpath[path].append(commit.interval)
return {path : sum(perpath[path]) / math.sqrt(len(perpath[path]))}
[/code]

Das heißt, wir können die Zinsen berechnen, die wir für technische Schulden zahlen:

[code lang="python"]
def debt(commits, deviation=1):
"Gesamtschulden für diese Reihe von Commits"
scores = zscore(commits)
debt = 0
for commit in commits:
for path in commits.paths:
if scores[path] > deviation:
debt += commit.duration -
inverse(0) -
inverse(deviation)
return debt * 100.0 / sum(c.duration for c in commits)
[/code]

Durch die Verwendung eines gleitenden Fensters können wir ein Diagramm der technischen Schulden über die Zeit erstellen und die Dateien finden, die dazu beitragen. Die folgende Abbildung zeigt die Anzahl der Commits für Dateien mit hohem Interesse im Vergleich zu normalen Dateien:

Hochverzinsliche Akten im Vergleich

Ergebnisse

Die technischen Schulden aus dem Jahr 2018 liegen bei rund 20 %:

Zinsen auf Schulden, 2018

Noch wichtiger ist, dass wir dadurch eine Liste von Dateien erhalten, an denen wir arbeiten müssen (zum Schutz der Unschuldigen editiert):

Z-Score% ZusagenMultiplikatorDateiname
3.181.66 %2.44xDockerdatei der Appliance
0.824.76 %2.14xLizenzierungscode für neues Produkt
2.940.98 %2.15xEntfernte Python-REST-API

In den oben genannten Fällen waren wir uns im Team einig, dass es sich um problematische Bereiche handelte. Unsere Diagnose lautete in der Reihenfolge:

Dockerdatei der Appliance
Die Größe der Appliance war zu groß, so dass das Herunterladen der benötigten Pakete von Nexus über das Netzwerk zu einer großen Verzögerung beim Roundtrip führte.
Um dem entgegenzuwirken, haben wir die Größe der Appliance deutlich reduziert.
Lizenzierungscode für neues Produkt
Dieser Code wurde eingekauft und hatte eine Reihe von Problemen:

  • Nicht übereinstimmende Kompilierung von 32/64 Bit und Big/Little-Endian-Architekturen
  • Duplizierte, aber ähnlich benannte Funktionen, z.B. CheckLicence gegen check_licence
  • Irreführende Namen (dsUint32_t ist eigentlich nur ein int...)

Wir haben diesen Code umfangreich überarbeitet und Tests hinzugefügt, um dieses Problem zu beheben.

Entfernte Python-REST-API
Dies hatte eine hohe zyklische Komplexität und eine geringe Dokumentation zur Folge, vor allem aber eine hohe Durchlaufzeit der Integrationstests.
Dies wurde verbessert, indem einige der Phasen des Integrationstests für jeden Test entfernt wurden.

Argumente gegen diesen Ansatz

Was ist mit Dingen, die nicht in die Versionskontrolle gehören?

Zunächst einmal sollte alles unter Versionskontrolle stehen! Ganz einfach. Natürlich gibt es immer Aktionen, die nicht in der Versionskontrolle sind - aber diese sind entweder unabhängig von der aktuellen Übergabe (und beeinflussen die Daten nicht, wenn sie in großen Mengen gemacht werden) oder sie beeinflussen die aktuelle Übergabe - in diesem Fall sind sie technische Schulden.

Was ist, wenn manche Menschen mehr Sorgfalt walten lassen als andere?

Wir normalisieren auf der Basis der einzelnen Autoren, um zu versuchen, unsere Daten auf den Code und den Prozess und nicht auf die Person zu beziehen.

Die Zinsen werden nur gezahlt, wenn der Code gerade aktiv ist, so dass ein Rückgang der Zinsen nur bedeuten kann, dass der derzeit aktive Code besser ist.

Das ist sehr wahr... und definitiv ein Problem. Inaktiver Code mit hohen technischen Schulden stellt ein Risiko dar, behindert aber nicht wirklich die Entwicklung... noch nicht. Hier können sekundäre Metriken (Komplexität, Abdeckung und Linting) helfen - insbesondere, wenn eine Verbindung zum empirischen Maß der Verschuldung gefunden werden kann. ...beobachten Sie diesen Bereich.

Kontakt

Vielen Dank, dass Sie uns kontaktiert haben. Wir haben Ihre Anfrage erhalten.