Inhaltsverzeichnis
Prinzipien
Single Level of Abstraction (SLA)
Die Einhaltung eines Abstraktionsniveaus fördert die Lesbarkeit.
Wandelbarkeit | |
---|---|
Korrektheit | |
Produktionseffizienz | |
Kontinuierliche Verbesserung | |
Single Developer |
Eine Codezeile kann auf verschiedenen Abstraktionsniveaus liegen. Die Zuweisung eines Wertes an eine Variable liegt auf einem niedrigeren Abstraktionsniveau als etwa ein Methodenaufruf. Schließlich kann sich hinter dem Methodenaufruf weit mehr Logik befinden als in der Zuweisung einer Variable. Selbst Methodenaufrufe können auf unterschiedlichen Abstraktionsniveaus stehen. Der Aufruf einer Methode aus einem Framework steht auf einem anderen Niveau, als der Aufruf einer Methode der Anwendung.
Damit Code gut zu lesen und zu verstehen ist, sollte in einer Methode nur ein Abstraktionsniveau verwendet werden. Andernfalls fällt es dem Leser schwer, Essentielles von Details zu unterscheiden. Wenn Bitpfriemeleien erforderlich sind, sollten diese nicht mit dem Aufruf von Methoden vermischt werden.
Hilfreich als Analogie ist der Blick auf Artikel in der Tageszeitung: dort steht zu oberst das Allerwichtigste, die Überschrift. Aus ihr sollte in groben Zügen hervorgehen, wovon der Artikel handelt. Im ersten Satz des Artikels wird dies auf einem hohen Abstraktionsniveau beschrieben. Je weiter man im Artikel fortschreitet, desto mehr Details tauchen auf. So können wir auch unseren Code strukturieren. Der Name der Klasse ist die Überschrift. Dann folgen die öffentlichen Methoden auf hohem Abstraktionsniveau. Diese rufen möglicherweise Methoden auf niedrigerem Niveau auf, bis zuletzt die „Bitpfriemelmethoden“ übrig bleiben. Durch diese Einteilung kann ich als Leser der Klasse entscheiden, welchen Detaillierungsgrad ich mir ansehen möchte. Interessiert mich nur grob, wie die Klasse arbeitet, brauche ich mir nur die öffentlichen Methoden anzuschauen. In ihnen wird die Funktionalität auf einem hohen Abstraktionsniveau gelöst. Interessieren mich weitere Details, kann ich tiefer einsteigen und mir die privaten Methoden ansehen.
Literaturquellen: Clean Code, Seite 36ff.
Single Responsibility Principle (SRP)
Fokus erleichtert das Verständnis. Eine Klasse mit genau einer Aufgabe ist verständlicher als ein Gemischtwarenladen.
Wandelbarkeit | |
---|---|
Korrektheit | |
Produktionseffizienz | |
Kontinuierliche Verbesserung | |
Single Developer |
Das Single Responsibility Principle (SRP) ist eines der SOLID Prinzipien. Es lautet: Eine Klasse sollte nur eine Verantwortlichkeit haben.
Hintergrund des Single Responsibility Principle ist die Überlegung, dass Änderungen oder Erweiterungen der Funktionalität einer Anwendung sich auf wenige Klassen beschränken sollen. Je mehr Klassen angepasst werden müssen, desto größer ist das Risiko, dass sich durch die erforderlichen Änderungen Probleme an Stellen ergeben, die im Kern nichts mit der Erweiterung zu tun haben. Eine Verletzung des Single Responsibility Principle führt zu Kopplung und damit zu erhöhter Komplexität, es wird schwieriger den Code zu verstehen.
Separation of Concerns (SoC)
Wenn eine Codeeinheit keine klare Aufgabe hat, ist es schwer sie zu verstehen, sie anzuwenden und sie ggf. zu korrigieren oder zu erweitern.
Wandelbarkeit | |
---|---|
Korrektheit | |
Produktionseffizienz | |
Kontinuierliche Verbesserung | |
Team |
Übersetzt mit Trennung der Belange bedeutet dieses Prinzip, dass man nicht mehrere Belange in einer Klasse zusammenfassen soll. Was sind Belange? Belange sind „komplett verschiedene“ Zwecke. Man sagt auch, Belange seien orthogonal zu einander und vor allem orthogonal zur Hauptfunktionalität einer Funktionseinheit. Beispiele für typische Belange sind: Tracing, Logging, Transaktionalität, Caching. Diese Belange sollen nach dem Prinzip der Separation of Concerns in spezialisierte Funktionseinheiten ausgelagert werden.
Das Separation of Concerns Prinzip hängt eng mit dem Single Responsibility Prinzip zusammen. Dabei sind Concerns eine Übermenge von Responsibilities. Jede Responsibility besteht im Idealfall aus genau einem Concern, nämlich ihrer Kernfunktionalität. Oft sind in einer Responsibility jedoch mehrere Concerns vermischt. Da sich dies technisch meist nicht ganz vermeiden läßt, besagt das Prinzip nicht etwa, dass eine Responsibility nur aus einem Concern bestehen darf, sondern dass die Concerns getrennt sein sollten. Innerhalb einer Methode sollte beispielsweise klar erkennbar sein, dass es mehrere Concerns gibt. Ferner sollten die Concerns nicht irgendwie über die Methode verstreut sein, sondern so gruppiert, dass klar ist, was zu einem Concern gehört.
Im Domain Driven Design versucht man beispielsweise die Business Domain von der Infrastruktur strikt zu trennen. So darf dort eine Klasse aus der Business Domain keinerlei Infrastruktur, etwa für Datenbankzugriffe, enthalten, sondern soll ausschließlich die Geschäftslogik abbilden. Persistenz ist ein „Concern“ der nichts mit der Business Logik zu tun hat. Separation of Concerns führt zu loser Kopplung und hoher Kohäsion. Die einzelnen Komponenten sind jeweils auf eine Aufgabe, einen Concern, fokussiert und dadurch leicht verständlich. Alle Teile aus denen die Komponente besteht, sind auf diese eine Aufgabe ausgerichtet, dadurch hängen die Teile eng zusammen (hohe Kohäsion). Separation of Concerns führt darüber hinaus auch zu gut testbaren Komponenten. Denn wenn der Zweck einer Codeeinheit fokussiert ist, muss weniger breit getestet werden. In Bezug auf die zu testende Codeeinheit sind weniger Testparameterkombinationen zu prüfen. Soll die Trennung der Belange konsequent betrieben werden, muss die Objektorientierung um das Konzept der Aspektorientierten Programmierung (AOP) erweitert werden. Dadurch wird es möglich, Aspekte wie etwa Transaktionalität, Tracing oder Caching vollständig aus einer Methode herauszuziehen.
Source Code Conventions
Code wird häufiger gelesen als geschrieben. Daher sind Konventionen wichtig, die ein schnelles Lesen und Erfassen des Codes unterstützen.
Wandelbarkeit | |
---|---|
Korrektheit | |
Produktionseffizienz | |
Kontinuierliche Verbesserung | |
Team |
Wir betrachten die folgenden Aspekte als wichtig:
- Namensregeln
- Richtig Kommentieren
Damit wollen wir nicht zum Ausdruck bringen, dass andere Konventionen unwichtig sind, wir wollen nur mit diesen beiden beginnen, weil sie uns elementar erscheinen. Bei allen Code Konventionen ist uns nämlich eines ganz wichtig: es geht weniger um die konkrete Ausgestaltung, sondern um konsequentes Einhalten der Konvention. Und es geht um das Bewusstsein, dass Konventionen notwendig sind.
Namensregeln
Ohne Namensregeln muss man sich wieder und wieder auf den Stil einzelner Entwickler einstimmen.
Namensregeln sollen den Leser des Codes dabei unterstützen den Code zu verstehen. Da es z.B. hilfreich ist, Felder von lokalen Variablen zu unterscheiden, könnte dies durch eine Namensregel unterstützt werden. Wie eine solche Konvention im Einzelfall aussieht ist Geschmacksache. Manche bevorzugen „this.xyz“ andere „_xyz“. Welche Variante man wählt ist uns nicht wichtig. Uns kommt es darauf an, dass die Konvention konsequent eingehalten wird. Die Notwendigkeit einer Namensregel für z.B. Felder hängt ferner vom Kontext ab. In einer Klasse mit 400 Zeilen wäre uns eine Namensregel, die Felder gegenüber Variablen hervorhebt, sehr wichtig, in überschaubaren Klassen tritt sie dagegen eher in den Hintergrund. Mit Hilfe der Root Cause Analysis geht der Clean Code Developer der eigentlichen Ursache für die Notwendigkeit einer Namensregel auf den Grund.
Richtig kommentieren
Unnötige oder gar falsche Kommentare halten beim Lesen auf. Der Code sollte so klar und deutlich sein, dass er möglichst ohne Kommentare auskommt.
Salopp gesagt ist ein Kommentar im Code ein Hinweis darauf, dass der Code noch verbessert werden kann. Typisch für solche Fälle sind 3 Zeilen Code, die mit einem Kommentar überschrieben sind. An der Stelle hilft es wahrscheinlich, die drei Zeilen als Methode zu extrahieren (Refactoring: Extract Method) und den Kommentar als Name der Methode zu verwenden. Ganz allgemein kann der Bedarf an Kommentaren reduziert werden, in dem man gute Namen verwendet für Variablen, Methoden, Klassen, etc.
Statt
int laenge; // in mm
besser
int laengeInMM;
Statt
public double Preis() { // Berechnet den Bruttopreis ... }
besser
public Money BruttoPreis() { ... }
Kommentiert werden sollte nicht was man tut, sondern, wenn überhaupt, wieso man etwas tut.
Praktiken
Issue Tracking
Nur, was man aufschreibt, vergisst man nicht und kann man effektiv delegieren und verfolgen.
Wandelbarkeit | |
---|---|
Korrektheit | |
Produktionseffizienz | |
Kontinuierliche Verbesserung | |
Team |
Eine strukturierte Verwaltung aller „Issues“ ist schon deshalb erforderlich, damit nichts verloren geht. Und nur wenn ein Überblick über alle offenen Punkte möglich ist, können die Punkte priorisiert und in eine Reihenfolge gebracht werden. Dazu bedarf es nicht zwangsläufig ausgeklügelter Tools, ein Board mit Pappkarten kann den Zweck auch erfüllen. Vor allem sollte hier nicht das Tool im Vordergrund stehen, sondern die Tätigkeit.
Siehe auch unter Tools.
Automated Integrationtests
Integrationstests stellen sicher dass der Code tut was er soll. Diese wiederkehrende Tätigkeit nicht zu automatisieren wäre Zeitverschwendung.
Wandelbarkeit | |
---|---|
Korrektheit | |
Produktionseffizienz | |
Kontinuierliche Verbesserung | |
Single Developer |
Die fundamentale Voraussetzung für jegliche Änderungen am Code haben wir bereits im roten Grad durch den Einsatz eines Versionskontrollsystems gelegt. Wir können ohne Sorge Änderungen am Code vornehmen, ganze Dateien und Verzeichnisse löschen, durch das Versionskontrollsystem ist alles wieder abrufbar.
Wenn wir nun Änderungen am Code vornehmen, sollten wir uns sicher sein, dass wir dabei nichts kaputt machen. Und diese Sicherheit können wir nur erlangen, wenn wir nach der Änderung testen, ob die Anwendung sich noch so verhält wie zuvor. Diese Tests nach jeder Änderung per Hand durchzuführen wäre nicht praktikabel, wir müssen sie automatisieren. Ein großes Übel der Softwareentwicklung ist die Angst, bei Änderungen am Code etwas zu übersehen, ein Detail nicht zu berücksichtigen, und dadurch einen Fehler zu verursachen in Code der vorher funktionierte. Dabei spielt es in der Regel sogar nicht mal eine Rolle, ob die Änderungen dazu führen sollen, dass der Code verbessert wird (Refaktorisieren) oder zusätzliche Anforderungen umgesetzt werden sollen. Solange wir nach Durchführen einer Änderung nicht sicher sind, dass alles noch so funktioniert wie zuvor, bleibt die Angst. Diese führt dazu, dass wir Code im Zweifelsfall so belassen, wie er ist, denn er funktioniert ja. Notwendige Refaktorisierungen werden unterlassen, aus Angst Fehler zu machen.
Damit wir uns auch in schon laufenden Projekten (sogenannte Brownfield Projekte, im Gegensatz zu Greenfield „auf der grünen Wiese“) dieses Sicherheitsnetz schaffen können, benötigen wir Verfahren, die auf vorhandenen Code angewendet werden können. Dazu eignen sich automatisierte Integrationstests. Sie setzen entweder ganz oben auf der Benutzerschnittstelle auf und testen die Anwendung durch alle Layer oder setzen weiter unten auf. In jedem Fall werden mehrere Funktionseinheiten im Zusammenspiel getestet.
Bevor wir also Änderungen oder Erweiterungen am Code vornehmen, erstellen wir für die betroffenen Codebereiche Integrationstests. Dabei können Tools und Techniken wie WatiN, UI Automation, etc. verwendet werden. Wünschenswert sind natürlich auch Unit Tests, welche einzelne Funktionseinheiten isoliert testen. Dazu muss der Code allerdings Voraussetzungen erfüllen, die vermutlich nicht immer gegeben sind: der Code muss bereits das Single Responsibility Prinzip berücksichtigen. Andernfalls sind die Abhängigkeiten zwischen den Funktionseinheiten (Komponenten, Klassen oder Methoden) so groß, dass sie nicht isoliert getestet werden können. Das Fernziel ist natürlich eine Codebasis, bei der Unit Tests möglich sind. Mehr noch: wir werden in Zukunft die Tests vor der Implementierung erstellen (Test first). Aber um durch Refaktorisierungen dorthin zu gelangen, bedarf es erst der Integrationstests, um sicherzustellen, dass die Anwendung sich noch so verhält wie vor der Refaktorisierung.
Siehe auch unter Tools.
Read, Read, Read
Lesen bildet!
Wandelbarkeit | |
---|---|
Korrektheit | |
Produktionseffizienz | |
Kontinuierliche Verbesserung | |
Single Developer |
Lesen bildet – wir sind jedenfalls fest davon überzeugt, dass dies auch für Software-Entwickler gilt. Die Softwaretechnik entwickelt sich nach wie vor weiter. Neben den großen Entwicklungsschritten wie Prozedurale Programmierung, Objektorientierte Programmierung, Funktionale Programmierung, Aspektorientierte Programmierung, etc. gibt es ständig Entwicklungen im Kleinen mit denen sich ein professioneller Software-Entwickler auseinandersetzen muss. Da wären zum einen Techniken wie etwa Dependency Injection oder Object Relational Mapper. Aber auch innerhalb dieser Techniken gibt es Entwicklungsschritte wie etwa Domain Specific Languages (DSLs) zur Konfiguration vs. XML basierende Konfiguration. Neben den technischen Aspekten der Softwareentwicklung wird auch der Prozess ständig weiterentwickelt. So hat sich die Erkenntnis durchgesetzt, dass Wasserfallmodelle nicht funktionieren, verschiedene agile Prozesse werden entwickelt. All dies muss der Clean Code Developer im Blick haben.
Wir schlagen daher vor, pro Jahr wenigstens 6 Fachbücher zu lesen. Ferner sollten Periodika regelmäßig gelesen werden und darunter verstehen wir neben Fachzeitschriften auch Blogs.
Anregungen finden Sie in der Literaturliste.
Reviews
Vier Augen sehen mehr als zwei. Wenn der eine Entwickler dem anderen seinen Code erklärt, tauchen meist Details auf, die bislang nicht bedacht wurden.
Wandelbarkeit | |
---|---|
Korrektheit | |
Produktionseffizienz | |
Kontinuierliche Verbesserung | |
Team |
Reviews kommen vereinfacht in zwei Spielarten daher: als kontinuierlicher Prozess beim Pair Programming, als eigenständiger Prozessschritt beim Code Review. Das Ziel ist in beiden Fällen das gleiche: der Code soll von einem zweiten Entwickler begutachtet werden. Dies beugt der „Betriebsblindheit“ vor. Schon die Tatsache dass ein Entwickler seinen Code einem anderen Entwickler vorstellt und beschreibt, führt zu Aha Erlebnissen.
In der Regel wird erst durch die Diskussion mit anderen Entwicklern deutlich, wo die Stärken und Schwächen einer Codebasis liegen. Gerade der Prozess der ständigen Verbesserung bedingt es, sich mit der Sichtweise anderer Entwickler auseinander zu setzen.
Selbstverständlich ist nicht nur der Quellcode eine geeignete Basis für Reviews. Sie bieten eine günstige Möglichkeit, die Ergebnisse jeder Entwicklungstätigkeit zu überprüfen, sofern sie in einem „lesbaren“ Ergebnis münden. Neben rein informellen Reviews, wie dem Pair Programming oder der Begutachtung durch eine zweite Person gibt es auch das formale Review mit einem Reviewprozess sowie entsprechenden Rollen. Weitere bekannte Arten des Review sind z.B. Walkthrough, Technisches Review, Peer Review und Inspektion.
Reviews ergänzen dynamische Tests, wie z.B. den automatischen Unit-Test oder den automatischen Integrationstest aus dem gelben Grad bzw. orangen Grad. Im Gegensatz zu diesen Tests, sind Reviews auch sehr gut geeignet, Fehler in den Anforderungen zu finden. Auch können sie bereits sehr früh im Entwicklungsprozess eingesetzt und Fehler dadurch auch sehr früh gefunden werden. Und um so früher Fehler gefunden werden, um so günstiger ist auch deren Beseitigung.
Quellen
Quelle | Autor | Kurzbeschreibung |
---|---|---|
Basiswissen Softwaretest, Aus- und Weiterbildung zum | T. Linz und A. Spillner | Das Lehrbuch zum Certified Tester Foundation Level nach ISTQB |
Certified Tester Foundation Level nach ISTQB-Standard |
An den orangen Grad schließt sich der gelbe Grad an.