In the Code: Abhängigkeiten aktuell halten

Corsin Decurtins, Netcetera
Software-Updates nerven. Einerseits. Aber andererseits kann eine aggressive Update-Strategie Vorteile haben.
 
Falls 'inside-it.ch' einmal einen Wettbewerb zur Wahl des IT-Unworts des Jahres ausschreibt, werde ich mich mit dem Wort "Software-Update" bewerben. Ich glaube, dass ich damit sehr gute Chancen hätte. Updates sind zwar grundsätzlich etwas Tolles: Eine neue, schönere Benutzeroberfläche, neue Funktionen, gefixte Bugs, bessere Performance, gestopfte Sicherheitslöcher etcetera. Da schlägt das Anwenderherz höher. Aber sind wir ehrlich: In den meisten Fällen nerven Updates. Es braucht Zeit für die Installation, grosse Updates blockieren Internet-Leitung und Prozessor, Updates haben Fehler und nach der Installation funktioniert nichts mehr wie vorher. Die neue Benutzeroberfläche ist zwar schön, aber man findet sich nicht mehr zurecht.
 
Software-Projekte sind von diesem Problem leider nicht ausgenommen. Applikationen nutzen heutzutage viele Drittkomponenten wie Bibliotheken, Frameworks, Plattformen, Micro Services, APIs und andere Laufzeitabhängigkeiten. Ob kommerzielle Komponenten oder Open-Source spielt dabei keine Rolle. Für die meisten Technologie-Stacks ist es völlig normal, dass bereits ein kleines, einfaches Software-Projekt Abhängigkeiten zu Dutzenden von Komponenten hat. Auch wenn das Projekt selber nur wenige Abhängigkeiten referenziert, haben diese selber wieder Abhängigkeiten, die in das Mutterprojekt integriert werden. Micro Services, Continuous Delivery und andere Trends in der modernen Software-Entwicklung führen weiter dazu, dass Abhängigkeiten immer feiner aufgeteilt werden in kleinere, unabhängige Komponenten, die ständig angepasst und weiterentwickelt werden.
 
Software-Teams sind heute mit der Situation konfrontiert, dass Updates für Drittkomponenten mehr oder weniger im Tagesrhythmus erfolgen. Und das unabhängig davon, ob das Software-Projekt in aktiver Entwicklung ist, in einem Wartungsmodus, oder kurz vor der Dekommissionierung steht.
 
Wie gehen Software-Teams mit Updates von Drittkomponenten um?
Für die Software-Teams und -Verantwortlichen stellt sich nun die Frage, wie sie mit dieser Situation umgehen sollen. Updates auf neue Versionen von Komponenten sind nicht gratis. Neue Komponentenversionen bedeuten neue Releases der Applikation und somit Tests, Builds, Deployments etc. Häufig sind auch kleinere Anpassung an Code oder Konfiguration notwendig, damit die Software mit den neuen Versionen der Komponenten läuft. Das kostet alles Zeit und Geld und bringt Risiken mit sich.
 
Wieso sollte man sich das antun, wenn es sich nicht um sicherheitskritische Updates handelt. Insbesondere, wenn mit der alten Version der Software alles in Ordnung ist und die Benutzer zufrieden sind? Wie heisst es so schön: "Wenn nichts kaputt ist, sollte man auch nichts reparieren."
 
So wenig wie möglich, so viel wie nötig …
Eine weit verbreitete Strategie ist es deshalb, Updates von Komponenten nur zu integrieren, wenn unbedingt notwendig. Beispielsweise bei Sicherheitspatches, dringenden Performanceoptimierungen oder wenn es lange erwartete Funktionalitäten sind. Ansonsten hat die Integration von neue Versionen von Komponenten ja nicht wirklich einen direkten Nutzen für die Benutzer oder Betreiber einer Software.
 
Eine solche Strategie stellt sicher, dass die Arbeitszeit von Entwickler, Testern und Operations-Leuten möglichst effizient und effektiv genutzt wird. Statt Updates zu integrieren, können sich die Teams auf neue, innovative und wertbringende Projekte konzentrieren. Viele Updates können übersprungen werden, was Zeit und Geld spart.
 
Ausserdem ist auch das Risiko von Ausfällen kleiner, wenn möglichst wenig Anpassungen an einwandfrei laufender Software vorgenommen wird. Zu guter Letzt sind die Benutzer auch dankbar, wenn sie nicht alle paar Tage wieder ein Update für ihre Software erhalten.
 
Wie bereits erwähnt ist diese Strategie weit verbreitet. Sie ist mehr oder weniger Standard für Software, die nicht mehr direkt in einer aktiven Entwicklungs- oder Ausbauphase ist. Bei Software in einem Wartungsmodus versucht man, alle Aufwände zu minimieren, möglichst wenige Anpassungen zu machen oder diese zumindest alle in ein paar wenige Wartungsfenster alle paar Monate oder Jahre zu bündeln. Diese Strategie bringt aber auch Gefahren mit sich.
 
Stillstand ist Rückschritt
Software altert anders als physische Systeme. Es gibt keine Abnutzung, Reibung oder Verschleiss. Programme, die beispielsweise in den 1960er Jahren geschrieben wurde, funktionieren heute grundsätzlich immer noch genau gleich wie damals. Trotzdem gibt es bei Software vergleichbare Alterungs-Effekte. Beispielsweise existiert die notwendige Hardware für ihr Programm aus den 1960er Jahren vielleicht gar nicht mehr. Oder die Software läuft zwar noch, aber es können keine Anpassungen vorgenommen werden, weil das Programm in einer Sprache geschrieben wurde, die heutige Programmierer nicht mehr beherrschen.
 
Physische Systeme altern, weil sie Abnützungs- und Verfallsprozessen unterworfen sind. Software altert, weil sich die Welt um die Software herum weiterentwickelt. Software-Teams wenden sich anderen Projekten zu, Entwicklerinnen und Entwickler wechseln die Firma, gehen in den Ruhestand, Firmen gehen unter und neue Firmen werden gegründet, Hardware wird ersetzt, es gibt neue Architekturen, Programmiersprachen, Bibliotheken, Plattformen und APIs.
 
Bei Softwaresystemen bedeutet Stillstand Rückschritt. Software die bezüglich ihrer Abhängigkeiten nicht ständig auf dem neuesten Stand gehalten wird, akkumuliert technische Schulden. Und diese Schulden können die Betroffenen teuer zu stehen kommen.
 
Ein Gordischer Knoten von Updates droht
Eine neue Version einer Abhängigkeit in eine Software zu integrieren, die aktuell ist bezüglich ihrer Abhängigkeiten, ist in den meisten Fällen eine relativ kleine Sache. Es muss nur eine Abhängigkeit ausgetauscht werden. Wenn die Applikation aber bezüglich Updates im Rückstand ist, kann das sehr schnell kompliziert werden. Die neue Version der Bibliothek A setzt beispielsweise voraus, dass sie die neueste Version der Laufzeitumgebung B verwenden. Um das Update von A zu integrieren, müssen sie also auch gleich noch ein Update von B machen. Das bedingt aber auch ein Update von Framework C, wo ihre Version nicht mit der neuesten Version von B läuft. Ausserdem ist Bibliothek D auch nicht für die neueste Version B erhältlich. Den Hersteller von D gibt es aber nicht mehr und sie müssen die Komponente D komplett durch E ersetzen ...
 
Aus einem kleinen Update für eine Bibliothek von Version 1.5.2 auf 1.5.3 kann daraus schnell einmal ein riesiger Gordischer Knoten von Updates und Abhängigkeiten werden, der ein Team für Tage oder Wochen beschäftigt. Einsparungen durch ausgelassene und aufgeschobene Software-Updates können durch solche Situationen schnell wieder weg sein.
 
Wirklich problematisch wird es, wenn die Upgrades dringend sind und nicht aufgeschoben werden können. Stellen sie sich vor, sie haben eine Sicherheitslücke in einer ihrer Abhängigkeiten und müssen diese sofort stopfen. Oder ihre alte Hardware ist ausgefallen und sie finden keine Ersatzteile mehr und müssen auf eine neue Hardware-Generation migrieren.
 
In diesem Fall haben sie nicht nur Opportunitätskosten, weil ihr Team mit dem Zerschlagen des Gordischen Update-Knotens beschäftigt ist. Sie haben auch Kosten und Reputationsschäden, weil ihre Software über Tage oder Wochen nicht verfügbar ist.
 
Der Gordische Update-Knoten kann aber auch zum Problem werden, wenn keine dringenden Updates anstehen. Die Kosten für Anpassungen an der Software steigen massiv an. Selbst kleine Änderungen können sehr aufwändig werden und grosse Risiken mit sich bringen. Die bestehende Software wurde zu lange vernachlässigt und ist in einem so schlechten Zustand, dass sich eine Renovation praktisch nicht mehr lohnt und ein kompletter Neubau günstiger kommt.
 
Auch wenn wir Softwareentwickler uns darüber freuen, ist das keine nachhaltige und effiziente IT-Strategie. Ausserdem arbeiten wir auch lieber an neuen, innovativen Projekten.
 
Eine aggressivere Updatestrategie als Lösung?
Im Extreme Programming (XP) gibt es das Mantra: "Wenn etwas weh tut, mach es häufiger." Das scheint auch für unser Update-Problem eine gute Strategie zu sein. Immer mehr Software-Firmen und Anwender setzen auf eine solche Strategie. Neue Versionen von Abhängigkeiten werden dabei kontinuierlich und zeitnahe integriert. Das braucht zwar Investitionen, bringt aber auch viele Vorteile mit sich.
 
Eine solche Strategie setzt allerdings zwingend einen hohen Grad an Automatisierung voraus, wenn die Aufwände nicht ins Unermessliche steigen sollen. Mehrere Updates pro Woche sind bei Softwareprojekten mit vielen Abhängigkeiten absolut keine Ausnahme. Ohne eine Automatisierung von Build, Integration, Qualitätssicherung, Tests, Releases und Deployments ist eine solche Kadenz kaum aufrecht zu erhalten. Insbesondere bei Softwareprojekten, die im Wartungsmodus sind und ansonsten nicht aktiv weiterentwickelt werden.
 
Moderne Software-Entwicklungsprozesse mit ihren automatisierten Pipelines (Continuous Delivery / Continuous Deployment) kommen dieser Entwicklung sehr entgegen. Updates von Abhängigkeiten wie Bibliotheken, Laufzeitumgebungen oder Services können automatisiert und direkt in die Software-Delivery-Pipeline integriert werden. Die Build-Umgebung prüft dabei kontinuierlich, ob neue Versionen von Abhängigkeiten verfügbar sind. Falls dies der Fall ist, werden die Updates automatisch integriert und die Software Delivery Pipeline mit Build, Tests, Deployments etcetera angestossen. Nur im Falle von Fehlern müssen Softwareentwickler manuell eingreifen und notwendige Codeanpassungen vornehmen.
 
Ein solcher Ansatz garantiert, dass die Software immer aktuell ist bezüglich ihrer Abhängigkeiten. Dringende und sicherheitskritische Updates können schnell und effizient integriert und verteilt werden. Das Risiko durch Anpassungen am Code wird minimiert, weil die Anpassungen jeweils nur sehr klein sind und automatisierte Tests die Qualität der Software sicherstellen.
 
Drittkomponenten sind ein integraler Bestandteil moderner Softwareentwicklung. Ohne mächtige Bibliotheken, Laufzeitumgebungen, Services und APIs wären die heutigen Applikationen kaum vorstellbar und ganz sicher nicht finanzierbar. Drittkomponenten und Abhängigkeiten müssen aber auch kontinuierlich gepflegt und aktuell gehalten werden. Updates aufzuschieben und zu bündeln mag auf den ersten Blick nach einer valablen Option aussehen. Die Risiken und Kosten dieser Option sind allerdings nicht zu unterschätzen.
 
Die Automatisierung von Updates von Drittkomponenten und die Integration in kontinuierliche Software Delivery Pipelines ist eine sehr viel bessere Option. Auf lange Sicht gesehen, hat diese Option weniger Risiken und ist günstiger und nachhaltiger.
 
Über den Autor
Corsin Decurtins ist Chief Technology Officer bei Netcetera. Er ist zuständig für Technologiestrategie und Entwicklungsmethodiken, berät Kunden und interne Teams und arbeitet als Softwarearchitekt und technischer Projektleiter. Seine Erfahrung hat er hauptsächlich im Bereich von Java-basierten Informations- und Transaktionssystemen für sichere und geschäftskritische Umgebungen gesammelt. Corsin Decurtins hat an der ETH Zürich Informatik studiert und als Forschungsassistent gearbeitet.