Technische Schulden in Software: ein aktuelles Problem der Zukunft
Zu Beginn der Software-Ideationsphase besteht das grundlegende Ziel darin, Lösungen für die von Nutzern des Softwareprodukts aufgeworfenen Fragen zu finden. Aber die Art und Weise dieser Softwareentwicklung hat selbstverständlich Einfluss auf die Anpassung an neue Bedürfnisse und damit auch auf die Erstellung künftiger Versionen. Der Schlüssel zu einer langfristig tragfähigen Software liegt darin, dass neue Entwickler verstehen, wie das System kodiert ist und sie in der Lage sind, es zu erweitern, weiterzuentwickeln und zu aktualisieren. An dieser Stelle kommen zwei Konzepte ins Spiel: Legacy-Code und technische Schulden.
Im strengen Sinne handelt es sich bei der Definition von Legacy-Code um einen Code, der von einem Softwareentwickler erstellt wurde und anschließend von einem anderen weitergepflegt werden muss. Diese Tatsache sollte an und für sich kein Problem darstellen, solange der Code bestimmten Regeln entspricht, so dass ein Dritter ihn bei seiner Übernahme verstehen und sich an die neuen Erfordernisse anpassen kann, ohne dass dies einen allzu großen Zeitaufwand darstellt.
Der Legacy-Code ist sicherlich ein wichtiges Thema für einen Software-Ingenieur, aber die eigentliche Sorge bei einer Übernahme der Wartung oder Entwicklung von bereits bestehender Software sind die technischen Schulden.
Was aber bedeuten technische Schulden?
Im Jahre 1992 führte Ward Cunningham, Programmierer und Mitverfasser des Manifests für agile Softwareentwicklung, das Konzept der technischen Schuld ein. Cunningham wollte die Bedeutung des Refactorings, d. h. der regelmäßigen Codekorrektur, für Software veranschaulichen. Auf diese Weise kann verhindert werden, dass die Software im Laufe der Zeit aufgrund zunehmender betrieblicher und struktureller Mängel mehr Schulden macht. Technische Schulden treten immer dann auf, wenn eine Lösung mit einer kurzfristigen Vision oder durch übermäßige Einsparung von Ressourcen entwickelt wird, um auf die jeweiligen Anforderungen reagieren zu können, ohne dabei aber an eine Codestruktur zu denken, die die Lesbarkeit für zukünftige Entwickler ohne großen Wartungsaufwand möglich macht. Die Entwicklung von Software unter dieser Prämisse ist auf lange Sicht eine große Belastung bei der Softwareweiterentwicklung, da zunächst die nicht optimierten oder unsauber gelösten Teile behoben werden müssen.
Aus diesem Grund ist es unerlässlich, über ein Software-Engineering zu verfügen, das dafür verantwortlich ist, die festgelegten Anforderungen nicht nur zu lösen, sondern dies auch nachhaltig zu tun. Jede Software muss sich weiterentwickeln lassen, wobei sich einige auf den bereits erstellten Teil auswirken. Wichtig dabei ist, dass derjenige, der diese Änderungen vornehmen muss, dies mit dem vorhandenen Material auf möglichst einfache Weise tun kann. Sollte dies nicht der Fall sein, muss ein Patch für die Problemlösung gefunden werden, was das System weiter belasten würde. Die Schaffung von Systemen unter diesem Gesichtspunkt erfordert Disziplin und einen höheren Zeitaufwand, ist aber auf lange Sicht weitaus rentabler.
Zweck sollten die verschiedenen Szenarien berücksichtigt werden, in denen die Software eingesetzt werden kann. Auβerdem gilt es zu vermeiden, dass die spezifischen Anforderungen der Benutzer zu restriktiv behandelt werden.
Bei der Entwicklung eines neuen Softwaresystems ist es notwendig, dafür zu sorgen, dass die technischen Schulden im Zuge der Weiterentwicklung und des Wachstums im Laufe der Zeit gegen Null tendieren. Zu diesem Zweck sollten die verschiedenen Szenarien berücksichtigt werden, in denen die Software eingesetzt werden kann. Auβerdem gilt es zu vermeiden, dass die spezifischen Anforderungen der Benutzer zu restriktiv behandelt werden.
Zwar können wir eine Funktionalität für eine sehr begrenzte und zuvor beschriebene Umgebung lösen, aber das System wird wahrscheinlich nicht in der Lage sein, in dieser neuen Umgebung zu funktionieren, wenn sich der Anwendungsbereich des Systems später ändert. Um zum Beispiel das Problem der Konnektivität zwischen Maschine und Werkstatt zu lösen, beschließen wir, ein System zu entwickeln, das dieses spezielle Maschinenmodell mit dem Betriebssystem des in der Werkstatt verwendeten Computers verbindet. Aber was geschieht bei einem Wechsel der Computer oder des Maschinenmodells oder dem Hinzukommen weiterer Maschinen oder Computer? Wahrscheinlich muss dann die Software geändert und zusätzliche Entwicklungsarbeit geleistet werden. Für den Software-Ingenieur bedeutet dies also ein ständiges Dilemma. Auf der einen Seite geht es um die Priorisierung der erforderlichen Anstrengungen, um die Systeme so vorzubereiten, dass sie sich an künftige Situationen anpassen können und auf der anderen Seite um die Kosten, die dies mit sich bringt.
Ziel also muss es sein, die vorhersehbaren, meist auftretenden Fragen zu berücksichtigen, damit es langfristig zu einer drastischen Verringerung des Zeitaufwands für die Verbesserung bestehender Funktionen oder die Implementierung neuer Funktionen kommt.
Die Frage, die wir uns stellen, ist folgende: kann die technische Schuld, gemessen am Aufwand und damit an den finanziellen Kosten des Produkt, gegen Null tendieren? Die Antwort darauf ist: ja. Kompliziert ist, dass es keinen Legacy-Code gibt, d. h. die Menge an Code, die für Software verwendet wird, die durch die weitere Arbeit an diesem System vererbt wird. Dieser Code muss selbstverständlich immer wieder geändert und verbessert werden. Ist jedoch die Lösung und damit der Code auf Nachhaltigkeit ausgelegt, sind diese Änderungen relativ simpel und erfordern keinen großen Aufwand, um die technischen Schulden auf ein Minimum zu reduzieren. Es handelt sich hierbei um eine Investition von Zeit und Geld, die dem Erfolg des Produkts oder der Systeme entspricht, die wir pflegen und weiterentwickeln. Die technische Schuld wächst jedoch jedes Mal, wenn der Kodex nicht auf nachhaltige Weise geändert wird.
Software-Ingenieur sollte daher die Pflege und Wartbarkeit des Quellcodes eine seiner wichtigsten Aufgaben sein
Der Schlüssel liegt darin, ausreichend Zeit zu investieren, nicht nur um die Anforderungen zu erfüllen, die diese Software in Zukunft lösen muss, sondern auch um sie so zu entwickeln, dass die technische Integrität gewährleistet ist. Die Software sollte immer auch den Anschein erwecken, von derselben Hand entworfen und entwickelt worden zu sein. Konzeptionelle Integrität sorgt dafür, dass sich alle Teile des Systems ähnlich verhalten, sodass die Benutzererfahrung dieselbe ist, unabhängig davon, ob die ältesten oder die neuesten Teile verwendet werden. Da das System ständig weiterentwickelt und angepasst wird, werden mögliche Lösungen für Neuentwicklungen auf technische Ausrichtungen beschränkt sein, die sowohl auf den neuen Code als auch auf den alten Teil des Systems angewendet werden können.
Für einen Software-Ingenieur sollte daher die Pflege und Wartbarkeit des Quellcodes eine seiner wichtigsten Aufgaben sein, da die Anwendung guter Software-Engineering-Praktiken es für Dritte einfacher macht, ihn zu verstehen und in Zukunft zu erweitern. Wenn diese Arbeit riguros durchgeführt wird, kann ein Code problemlos vererbt werden, da die technische Schuld so weit wie möglich reduziert wurde.
Das bedeutet aber nicht, dass die Lebensdauer des Codes eines Systems unendlich ist. Irgendwann werden wir wahrscheinlich an einen Punkt gelangen, an dem wir wieder erneut bei fast Null anfangen müssen. Der Schlüssel liegt demnach darin, diesen Punkt so lange wie möglich hinauszuzögern. Je länger der Lebenszyklus, desto erfolgreicher das Produkt: sowohl für Stakeholder, Nutzer und Softwareentwickler.