Blätter wachsen aus einem Besen, der ein Programmfenster fegt

Nachhaltige Software durch Clean Code: Methoden der effizienten Programmierung

Lesezeit
13 ​​min

Der weit verbreitete Einsatz von Software kann nicht nur das alltägliche Leben der Menschen, sondern auch deren Umwelt beeinflussen. In Anbetracht der Klimakrise ist es umso wichtiger, ökologisch nachhaltige Software zu entwickeln. Doch gleichzeitig soll die Entwicklung und der Einsatz eines Softwareprodukts oft auch positive wirtschaftliche Effekte haben.  Dieser Blogartikel behandelt die Frage, inwiefern Clean Code die Nachhaltigkeit der effizienten Softwareentwicklung beeinflusst und welche Folgen dabei entstehen. Um dies zu beantworten, werden sowohl die Kriterien zum Definieren und Entwickeln nachhaltiger Software herausgestellt als auch die ökologischen und ökonomischen Auswirkungen hinsichtlich der Ziele von Clean Code untersucht. Hierfür wurden entsprechende Messungen zu konkreten Implementierungen durchgeführt, ausgewertet und Handlungsempfehlungen vorgestellt. Dabei wurden Untersuchungen zu folgenden Szenarien gemacht:  Ein Decorator Design Pattern vs.  Decorator Anti Design Pattern, das Anwenden und nicht Anwenden von Caching, Inlining vs. Anti-Inlining, Rekursion vs. Iteration sowie Loop vs. Loop Unrolling.

Nachhaltige Software

Ökologisch Nachhaltige Software hat geringe Hardwareanforderungen und kann langfristig eingesetzt werden. Dabei sollte der Energieverbrauch von CPU-Zeit, Arbeitsspeicher, Festplatten- sowie Netzwerkzugriffen möglichst gering gehalten werden, ohne dabei die Funktion einer Software negativ zu beeinflussen (vgl. Umweltbundesamt (2015): 31). So hat die Ausführung einer Software auf unterschiedlicher Hardware verschiedene Effekte.  Es gibt bereits einige Ansätze und Richtlinien zum Entwickeln nachhaltiger Software. So bietet der „Blaue Engel“ Vergabekriterien für ressourcen- und energieeffiziente Softwareprodukte (DE-UZ 215).  Eine als solche ausgezeichnete Software gilt als besonders transparent, frei im Umgang durch deren Anwender:innen und energiesparend bei der Nutzung von Hardware-Ressourcen. Die Vergabekriterien werden in die Bereiche Ressourcen- und Energieeffizienz, potenzielle Hardware-Nutzungsdauer sowie Nutzungsautonomie aufgeteilt (siehe https://www.blauer-engel.de/de/produktwelt/ressourcen-und-energieeffiziente-softwareprodukte). Zudem bietet das Umweltbundesamt weitere Orientierung (siehe https://www.umweltbundesamt.de/themen/wie-software-gruener-werden-kann-ergebnisse-eines).

Von ökonomisch nachhaltiger Software ist die Rede, wenn die investierten Mittel in deren Entwicklung durch den Einsatz oder den Verkauf der Software die Maximierung oder den Erhalt des daraus entstehenden Ertrags gewährleistet.

Sozial nachhaltige Software kann z. B. durch freie Software erreicht werden, wenn dadurch der Zugang zu Wissen, Kultur oder Kunst von allen sozialen Gruppen gewährleistet wird. Dies fördert die Chancengleichheit. Je mehr soziale Aufgaben bzw. Probleme sie erfüllt, desto nachhaltiger ist sie in diesem Bereich.

Clean Code

Clean Code erleichtert das Ändern, Lesen, Erweitern und Warten von Softwarecode. Ein weiteres Ergebnis ist eine stabilere und effizienter wartbare Software, die weniger investierte Entwicklungszeit bei Funktionserweiterungen und Fehlerbehebungen benötigt. Dies ist besonders deshalb relevant, da im Durchschnitt mindestens 80 Prozent der Entwicklungszeit auf Wartungen zurückzuführen sind. (Vgl. Robert C. Martin (2009): 16). Zudem unterstützt Clean Code die sichere und weniger fehleranfällige Entwicklung von Software, da die Entwicklung übersichtlicher, weniger komplex und weniger missverständlich wird (vgl. Uwe Post (2021): 14 f.). Es fördert die ökonomische Nachhaltigkeit durch Einsparung von Entwicklungszeit und Kosten und verhindert kostenintensive Katastrophen, die sonst die Folge sein können. Dabei muss beachtet werden, dass das Schreiben von sauberem Code ein Prozess ist. Je früher damit begonnen wird, desto weniger zeit- und kostenaufwändig werden Anpassungen im späteren Verlauf, auch wenn initial besonders bei komplexen Programmen der Zeitaufwand oft höher ist. (Vgl. Robert C. Martin (2009):110). Zusätzlich sorgt das Einsparen der Entwicklungszeit auch dafür, dass weniger Energie durch die Entwickler:innen selbst verbraucht wird, um das jeweilige Softwareprodukt fertigzustellen.

Effiziente Programmierung

Die Laufzeit eines Algorithmus hängt sowohl von den Eingabewerten und der jeweiligen Programmiersprache als auch von der Hardware ab, auf der er ausgeführt wird. Die Anzahl der Ausführungen sowie die Art und Weise der Implementierung von einem Algorithmus ist dementsprechend ebenso relevant. Deshalb müssen Messungen hierzu als Einzelfall-Untersuchungen betrachtet werden, die in Bezug auf einen konkreten Rechner bzw. der dementsprechenden Hardware sowie der jeweiligen Programmiersprache, Implementierung und Eingabewerte unterschiedliche Ergebnisse liefert. (Vgl. Wegener (2003): 20 f.). Die Formel für den Energieverbrauch ist Energy (J) = Power (W) x Time(s). Dies bedeutet, dass die Laufzeit den Energieverbrauch zwar beeinflusst, aber die dafür benötigte Leistung eines Computers nicht immer als Konstante betrachtet werden darf. Hierfür müssten weitere Untersuchungen erfolgen. (Vgl. Rui Alexandre Afonso Pereira et al. (2020) :11). Für die Effizienzbewertung von Algorithmen existieren auch von Implementierungs-, Hardware- und Eingabedaten unabhängige Kriterien (vgl. Wegener (2003): 21 f.). So kann neben dem Speicherplatzbedarf die benötigte Wachstumsgeschwindigkeit eines Algorithmus auch unabhängig von Zeiteinheiten mit der Anzahl durchgeführter Rechenschritte bzw. mit der Landau-Notation angegeben werden (vgl. Wegener (2003): 21, 295).

Im Folgenden werden einige praktische Beispiele für Code-Optimierungen und das Anwenden von Clean Code genannt. Diese werden durch die jeweiligen Messungen der Laufzeit auf die Auswirkungen ökologischer als auch ökonomischer Nachhaltigkeit untersucht. Hierbei muss beachtet werden, dass sich die Messungen auf ein konkretes Szenario mit einer individuellen Hardware und der Programmiersprache Java beziehen. Außerdem wird davon ausgegangen, dass die unabhängig von der Laufzeit verbrauchte Leistung für die unterschiedlichen implementierten Algorithmen konstant bleibt. Dies ist dadurch begründet, dass es sich um Algorithmen handelt, bei denen der Prozessor für die jeweilige Zeit der Ausführung vollständig ausgelastet werden kann und keine Netzwerkzugriffe stattfinden. Somit können alleine durch die Vergleiche der Laufzeitmessung Rückschlüsse auf den jeweiligen Energieverbrauch gezogen werden.

Decorator Design Pattern vs. Decorator Anti Design Pattern

Beispielhaft wurde ein Decorator Design Pattern im Sinne der Clean Code SOLID Prinzipien und dessen Anti Design Pattern implementiert. Dabei kann die Dekoration von konkreten Komponenten bei Anti Decorator Design Pattern mittels innerer Klassen oder als Unterklasse erfolgen.

Damit Laufzeitunterschiede deutlich zu erkennen sind wurde die Dekoration einer konkreten Komponente jeweils 10 Millionen-mal durch eine For-Schleife aufgerufen. Dies wurde 10 Mal hintereinander ausgeführt und die Laufzeit bei jedem Durchgang gemessen. Denn die Messergebnisse sind nicht gleichbleibend und hängen u.a. stark von anderen Aktivitäten ab, die derselbe Computer durchläuft. Die Ergebnisse in den dargestellten Grafiken zeigen, dass das Design Pattern bei jeder Ausführung deutlich mehr Laufzeit benötigt als die beiden Anti Design Pattern:

Säulendiagramm für den Aufruf der Design Pattern und Anti Design Pattern
Abbildung 1: Auswertung der Laufzeitmessungen für den Aufruf der Design Pattern und Anti Design Pattern (Quelle: Eigene Darstellung)
Säulendiagramm Zusammenfassende Auswertung der Laufzeitmessungen für den Aufruf der Design Pattern und Anti Design Pattern
Abbildung 2: Zusammenfassende Auswertung der Laufzeitmessungen für den Aufruf der Design Pattern und Anti Design Pattern (Quelle: Eigene Darstellung)

 

Zusätzlich wurden weitere Messungen mit konkreten Komponenten durchgeführt, die eine zusätzliche aufwändigere Berechnung haben. Diese führten die Fibonacci-Berechnung mit einem Eingabewert 10 aus:

Abbildung 3: Auswertung der Laufzeitmessungen für den Aufruf der Design Pattern und Anti Design Pattern mit zusätzlicher Berechnung (Quelle: Eigene Darstellung)
Säulendiagramm Zusammenfassende Auswertung der Laufzeitmessungen
Abbildung 4: Zusammenfassende Auswertung der Laufzeitmessungen für den Aufruf der Design Pattern und Anti Design Pattern mit zusätzlicher Berechnung (Quelle: Eigene Darstellung)

Der relative Laufzeitunterschied vom Experiment (Dekorierer-Pattern) zum Gegenexperiment (Anti Pattern) ist geringer, wenn eine zusätzliche Berechnung im „inneren des Pattern“ durchgeführt wird. In absoluten Werten braucht das Dekorierer-Pattern jedoch trotzdem länger. Dieses sollte also dann vorgezogen werden, wenn viele dekorierte konkrete Komponenten mit komplexen Berechnungen erforderlich sind. Dadurch ist der Energieverbrauch nicht so viel höher, aber es bietet den Vorteil hinzukommende Entwicklungszeiten und Kosten zu vermeiden.  Ein Anti Design Pattern ist dagegen dann vorzuziehen, wenn nur sehr wenige konkrete Komponenten dekoriert werden müssen. Dadurch kann im Verhältnis bei geringen hinzukommenden Entwicklungszeiten und Kosten sehr viel Energie eingespart werden.

Caching

Bei dem Einsatz von Caching handelt es sich um das Zwischenspeichern mit hohen Zugriffsgeschwindigkeiten (Vgl. Amazon Web Services (2022)). Dies kann mehr Speicherplatzbedarf erfordern, führt aber oft zu Energieeinsparungen bei CPU-Berechnungen oder Festplatten und Netzwerkaktivitäten. Hierzu wurde die Fibonacci-Funktion mit und ohne den Einsatz von Caching implementiert.  Für den Eingabewert 47 der beiden Funktionen ergab sich folgender Laufzeitvergleich:

Säulendiagramm: Vergleich des Laufzeitverhalten eines rekursiven Aufrufs der Fibonacci-Reihe
Abbildung 5: Vergleich des Laufzeitverhalten eines rekursiven Aufrufs der Fibonacci-Reihe mit dem Eingabewert 47 mit und ohne dem Einsatz von Caching (Quelle: Eigene Darstellung)

In diesem Fall ist Caching zu empfehlen, da dadurch das exponentielle Wachstumsverhalten in der Zeitkomplexität durch ein lineares ersetzt wird. Das sorgt sehr schnell für erhebliche Laufzeit bzw. Energieeinsparungen, während der dafür benötigte zusätzliche Implementierungsaufwand gering bleibt.

Iteration vs. Rekursion

Als weitere Optimierungsmethode wurde eine iterative mit einer rekursiven Fakultätsfunktion verglichen. Damit Laufzeitunterschiede deutlich zu erkennen sind, erfolgte der Funktionsaufruf mit dem Eingabewert 10 und wurde insgesamt 10 Millionen Mal aufgerufen. Anschließend kam es zu 10 hintereinander folgenden Durchführungen.

Säulendiagramm: Vergleich der Laufzeiten für einen iterativen und rekursiven Funktionsaufruf
Abbildung 6: Vergleich der Laufzeiten für einen iterativen und rekursiven Funktionsaufruf (Quelle: Eigene Darstellung)
Säulendiagramm: Zusammenfassender Vergleich der Laufzeiten für einen iterativen und rekursiven Funktionsaufruf
Abbildung 7: Zusammenfassender Vergleich der Laufzeiten für einen iterativen und rekursiven Funktionsaufruf (Quelle: Eigene Darstellung)

Die Ergebnisse zeigen, dass die Rekursion deutlich mehr Laufzeit benötigt, und weisen darauf hin, dass die Java Virtual Machine (JVM) zur Laufzeit automatisch Optimierungen vornimmt. Die für einen Menschen lesbarere und verständlichere Rekursion sollte der manuellen iterativen Variante dann vorgezogen werden, wenn automatische Compiler- oder Laufzeitoptimierungen den Unterschied des Energieverbrauchs dieser beiden Varianten ausgleichen. Die manuelle Iteration ist dagegen dann empfehlen, wenn keine oder kaum Compiler- oder Laufzeitoptimierungen stattfinden und es sich um Hotspots handelt –also dort, wo viel Energie verschwendet wird, die Zeitkomplexität sehr hoch ist und diese Algorithmen sehr oft ausgeführt werden.

Inlining vs. Anti-Inlining

Bei Inlining-Optimierungen wird der Code-Inhalt einer Funktion an Stelle der Aufrufe dieser Funktion ersetzt. Dies wurde durch eine einfache Funktion umgesetzt, die zwei verschiedene Zahlen zum Quadrat berechnet und diese dann addiert. Die dazugehörige Anti-Inline-Methode ruft dabei jeweils eine Quadrat-Funktion auf, die diesen Teil der Berechnung übernimmt. Die Messungen fanden nach demselben Verfahren wie bei Rekursion vs. Iteration statt. Hierbei ist kein eindeutiger Laufzeitunterschied zwischen dem Aufruf der Inline- mit der Anti-Inline-Funktion zu erkennen:

Säulendiagramm: Vergleich der Laufzeit für die Anwendung von Inlining und Anti-Inlining
Abbildung 8: Vergleich der Laufzeit für die Anwendung von Inlining und Anti-Inlining (Quelle: Eigene Darstellung)
Säulendiagramm: Zusammenfassender Vergleich der Laufzeit für die Anwendung von Inlining und Anti-Inlining
Abbildung 9: Zusammenfassender Vergleich der Laufzeit für die Anwendung von Inlining und Anti-Inlining (Quelle: Eigene Darstellung)

Zusätzlich wurde der Bytecode der Anti-Inlining-Methode untersucht, da Inline-Ersetzungen durch moderne Compiler auch automatisch stattfinden können. Im Bytecode ist für die Anti-Inline-Methode keine automatische Inline-Ersetzung zu sehen, da die Quadrat-Funktion aufgerufen wird. Deshalb handelt es sich dabei wahrscheinlich um eine Optimierung durch die JVM.

In diesem Fall oder bei automatischen Compiler-Optimierungen sollte auf manuelles Inlining verzichtet werden. Dadurch kann die eigentliche Funktionalität von dem jeweiligen Code bei gleichbleibendem Energieverbrauch manuell und lesbarer bzw. verständlicher zusammengefasst werden. Für die Bedingung, dass sicher keine automatischen Laufzeit- oder Compiler-Optimierungen stattfinden, müssten weitere Messungen durchgeführt werden, um die Laufzeit vergleichen zu können.

Loop vs. Loop Unrolling

Bei der Loop-Unrolling-Optimierung wird der Aufruf einer Schleife durch mehrere Kopien von dessen Inhalt ersetzt. Die Schleifenbedingung muss somit nicht wiederholt geprüft werden. Auch dies kann durch einige moderne Compiler automatisch angewandt werden.

Die Laufzeitmessungen fanden nach demselben Verfahren wie bei den Untersuchungen von Rekursion, Iteration sowie Inlining und Anti-Inlining statt. Die Ergebnisse zeigen, dass Loop Unrolling wesentlich schneller ist:

Säulendiagramm: Laufzeitmessungen für die Aufrufe der Loop- und Loop-Unrolling-Methode
Abbildung 10: Laufzeitmessungen für die Aufrufe der Loop- und Loop-Unrolling-Methode (Quelle: Eigene Darstellung)
Säulendiagramm: Zusammenfassende Laufzeitmessungen für die Aufrufe der Loop- und Loop-Unrolling-Methode
Abbildung 11: Zusammenfassende Laufzeitmessungen für die Aufrufe der Loop- und Loop-Unrolling-Methode (Quelle: Eigene Darstellung)

Auch hier deuten sich Laufzeitoptimierungen der JVM an. In diesem Fall sind die Ergebnisse des Loop Unrolling ökologisch effizienter. Ein manuelles Loop Unrolling bedeutet allerdings jede einzelne Zeile Code manuell schreiben und ggf. später refaktorieren zu müssen. Dabei kann eine hohe Anzahl an Schleifendurchläufe erhebliche Auswirkungen auf die langfristigen Entwicklungs- und Wartungszeiten haben. In der gezeigten konkreten Implementierung handelt es sich jedoch um wenige Schleifendurchläufe. Außerdem kam kein automatisches Loop Unrolling durch den Compiler zustande. Laufzeitoptimierungen wurden sowohl für die Loop- als auch die Loop-Unrolling-Funktion vorgenommen. Unter diesen Umständen ist ein manuelles Loop Unrolling zu empfehlen.

Fazit

Clean Code wirkt sich vor allem bei komplexen Softwareprojekten positiv auf die ökonomische Nachhaltigkeit aus. Ob es sich dabei auch um ökologische Nachhaltigkeit handelt, muss für jeden individuellen Fall und in dem jeweiligen Kontext einzeln betrachtet werden.  Wenn unterschiedliche Algorithmen auf ihre ökologische Nachhaltigkeit hin untersucht werden und dementsprechende Messungen ausgeführt werden sollen, empfiehlt es sich, dessen reale Komplexität z. B. mit dem Einsatz von Schleifen zu simulieren, falls der Anwendungsfall dies erlaubt. Dadurch kann die für die Messung benötigte Entwicklungszeit eingespart werden. Gleichzeitig können Rückschlüsse gezogen werden, ob das Anwenden von Clean Code oder ökologisch effizienteren Code zu einer insgesamt höheren Nachhaltigkeit führt. Dabei sollte man auch überprüfen, ob automatische Optimierungen vorgenommen werden, um eine sinnvolle Entscheidung zu treffen – besonders bei Hotspots sollte optimiert werden. Diese sind z. B. identifizierbar mit Profiler Tools.

Repository zum Projekt

Für die beispielhaften Implementierungen zu den Methoden der effizienten Programmierung wurde ein Repository bereitgestellt: https://github.com/Marjuw/Methods-of-efficient-programming.

Literaturverzeichnis

[Amazon Web Services 2022] Amazon Web Services: Caching – Übersicht. https: //aws.amazon.com/de/caching/ Version: 2022. – Last accessed 24 February 2022

[Holzbaur 2020] Holzbaur, Ulrich: Nachhaltige Entwicklung-Der Weg in eine lebenswerte Zukunft. Springer, 2020 https://link.springer.com/book/10.1007/978-3-658-29991-0. – Last accessed 06 January 2022

[Blauer Engel 2020] Blauer Engel: Ressourcen- und energieeffiziente Softwareprodukte (DE-UZ 215). https://www.blauer-engel.de/de/produktwelt/ressourcen-undenergieeffiziente-softwareprodukte. Version: January 2020. – Last accessed 13 January 2022

[Robert C. Martin 2009] Robert C. Martin: Clean Code – Refactoring, Patterns, Testen und Techniken für sauberen Code. 1. Edition. mitp, 2009. – ISBN 978-3-8266-5548-7

[Rui Alexandre Afonso Pereira et al. 2020] Rui Alexandre Afonso Pereira and Marco Couto and Francisco José Torres Ribeiro and Rui António Ramada Rua and Jácome Cunha and João Paulo Sousa Ferreira Fernandes and João Saraiva: Ranking Programming Languages by Energy Efficiency. http://repositorium.sdum.uminho.pt/handle/1822/69044. Version: Dezember 2020. – Last accessed 28 February 2022

[Umweltbundesamt 2015] Umweltbundesamt: Nachhaltige Software-Dokumentation des Fachgesprächs „Nachhaltige Software“ am 28.11.2014. https://www.umweltbundesamt.de/themen/wie-software-gruener-werden-kann-ergebnisse-eines. Version: July 2015. – Last accessed 06 January 2022

[Uwe Post 2021] Uwe Post: Besser Coden – Best Practices für Clean Code. 2. Edition. Reihnwerk Computing, 2021. – ISBN 978-3-8362-8492-9

[Wegener 2003] Wegener, Prof. Dr. I.: Komplexitätstheorie: Grenzen der Effizienz von Algorithmen. 1. Edition. Springer-Verlag Berlin Heidelberg, 2003. – ISBN 9783642555480

2 Kommentare

  1. Sehr interessante Ergebnisse! Meiner Meinung sind zwar die Effekte von Verbesserungen auf „low-level“ Code Ebene im Hinblick auf nachhaltige, energieeffiziente Software eher marginal – verglichen mit dem Entwicklungsprozess an sich oder aber dem Betrieb der Software (Stichwort „auto-scaling“, etc.) – aber dennoch nicht zu vernachlässigen. Einige der erwähnten Aspekte finden sich auch im openHPI MOOC über „Sustainable Software Engineering“ wieder – sehr empfehlenswert 😉 //Link von Redaktion entfernt

Hat dir der Beitrag gefallen?

Deine E-Mail-Adresse wird nicht veröffentlicht.

Ähnliche Artikel