RSS RSS

Clean Code Developer


Navigation





Search the wiki
»

PoweredBy

Der grüne Grad


Prinzipien

Open Closed Principle

Warum?
Weil das Risiko, durch neue Features ein bisher fehlerfreies System zu instabilisieren, so gering wie möglich gehalten werden sollte.

EvolvierbarkeitKorrektheitProduktionseffizienzReflexion
++++
Single Developer

Das Open Closed Principle (OCP) besagt, dass eine Klasse offen für Erweiterungen sein muss, jedoch geschlossen gegenüber Modifikationen. Es ist ein weiteres der SOLID Prinzipien. Folgendes Codebeispiel soll verdeutlichen, wo das Problem liegt, wenn das Prinzip nicht befolgt wird:

public double Preis() {
    const decimal StammkundenRabatt = 0.95m;
    switch(kundenart) {
        case Kundenart.Einmalkunde:
            return menge * einzelpreis;
        case Kundenart.Stammkunde:
            return menge * einzelpreis * StammkundenRabatt;
        default:
            throw new ArgumentOutOfRangeException();
    }
}
Das problematische an dieser Form der Implementierung ist, dass die Klasse modifiziert werden muss, wenn eine weitere Art der Preisberechnung erforderlich wird. Die Gefahr dabei ist, dass bei dieser Modifikation Fehler gemacht werden und die bisher schon vorhandenen Funktionen nicht mehr ordnungsgemäß funktionieren. Auch wenn automatisierte Unit Tests und Integrationstests vorhanden sind besteht das Risiko, neue Bugs zu hinterlassen, weil man keine hundertprozentige Testabdeckung erreichen kann. Gesucht ist also generell ein Verfahren, welches die Klasse erweiterbar macht, ohne dass dazu die Klasse selbst modifiziert werden muss. Dies kann z.B. mit Hilfe des Strategy Patterns erreicht werden:

public interface IPreisRechner {
    double Preis(int menge, double einzelpreis);
}

private IPreisRechner preisRechner;

public double Preis() {
    return preisRechner.Preis(menge, einzelpreis);
}

public class Einmalkunde : IPreisRechner {
    public double Preis(int menge, double einzelpreis) {
        return menge * einzelpreis;
    }
}

public class Stammkunde : IPreisRechner {
    const decimal StammkundenRabatt = 0.95m;
    
    public double Preis(int menge, double einzelpreis) {
        return menge * einzelpreis * StammkundenRabatt;
    }
}
Die konkrete Berechnung des Preises wird über ein Interface in andere Klassen ausgelagert. Dadurch ist es möglich, jederzeit neue Implementierungen des Interfaces zu ergänzen. Damit ist die Klasse offen für Erweiterungen, gleichzeitig aber geschlossen gegenüber Modifikationen. Bestehender Code kann z.B. mit dem Refactoring Replace Conditional with Strategy so umgestaltet werden, dass das Open Closed Principle eingehalten wird.

Quellen

QuelleAutorKurzbeschreibung
http://www.objectmentor.com/resources/articles/ocp.pdfRobert C. MartinArtikel zum Open Closed Principle von 1996 veröffentlicht für The C++ Report


Tell, don´t ask

Warum?
Hohe Kohäsion und lose Kopplung sind Tugenden. Öffentliche Zustandsdetails einer Klasse widersprechen dem.

EvolvierbarkeitKorrektheitProduktionseffizienzReflexion
++
Single Developer

Etwas provokant formuliert, sollten Klassen keine Property Getter haben. Diese verführen den Verwender einer Klasse dazu, anhand von Werten, die ein Objekt liefert, Entscheidungen zu treffen. Statt also dem Objekt mitzuteilen, was es tun soll, wird es befragt, um dann von außen Betrachtungen über den internen Zustand des Objektes anzustellen.

Eines der Kernprinzipien der Objektorientierten Programmierung lautet Information Hiding (siehe dazu auch im gelben Grad). Keine Klasse soll Details nach außen tragen, aus denen hervorgeht, wie sie intern implementiert ist. Benötigt eine Klasse für ihre Arbeit einen internen Zustand, wird dieser typischerweise in einem internen Feld abgelegt. Wenn nun dieser Wert auch nach außen sichtbar ist, werden Verwender verleitet, diesen eigentlich internen Zustand des Objektes für eigene Entscheidungen heranzuziehen. Dadurch wird die Klasse schnell zur reinen Datenhaltung degradiert. Eine Implementierung, bei der einem Objekt mitgeteilt wird, was es tun soll, ist in jedem Fall vorzuziehen. Dadurch muss es den Verwender nicht mehr interessieren, wie die Klasse die Aufgabe intern bewerkstelligt.

Als Ergebnis des Tell don't ask Prinzips entstehen Objekte mit Verhalten statt "dummer" Datenhaltungsobjekte. Das Zusammenspiel der Objekte ist lose gekoppelt, da die Objekte keine Annahmen über die kollaborierenden Objekte machen müssen. Aber nicht nur das! Wenn Objekte ihren Zustand nicht veröffentlichen, behalten sie die Entscheidungshoheit. Die Kohäsion des entscheidenden Codes wächst damit, weil er an einem Ort zusammengelegt wird.


Law of Demeter

Warum?
Abhängigkeiten von Objekten über mehrere Glieder einer Dienstleistungskette hinweg führen zu unschön enger Kopplung.

EvolvierbarkeitKorrektheitProduktionseffizienzReflexion
+++
Single Developer

Beim Law of Demeter geht es darum, das Zusammenspiel von Objekten auf ein gesundes Maß zu beschränken. Man kann es vereinfacht umschreiben mit "Don't talk to strangers". Nach dem Law of Demeter soll eine Methode nur folgende andere Methoden verwenden:
  • Methoden der eigenen Klasse
  • Methoden der Parameter
  • Methoden assoziierter Klassen
  • Methoden selbst erzeugter Objekte

Allerdings: Es ist zu berücksichtigen, dass ab und zu auch reine Datenhaltungsklassen Sinn ergeben. Auf diese muss man das Law of Demeter natürlich nicht anwenden. Es kann z.B. durchaus sinnvoll sein, die Konfigurationsdaten in mehrere Klassen hierarchisch zu verteilen, so dass sich am Ende folgender Zugriff auf einen Wert ergeben könnte:

int margin = config.Pages.Margins.Left;

Würde man hier das Law of Demeter anwenden, wäre nur der Zugriff auf config.Pages gestattet.


Praktiken

Continuous Integration

Warum?
Automatisierung und Zentralisierung der Softwareproduktion machen produktiver und reduzieren das Risiko von Fehlern bei der Auslieferung.

EvolvierbarkeitKorrektheitProduktionseffizienzReflexion
+++++
Single Developer

Oft wird die Integration der Softwarekomponenten zeitlich nach hinten geschoben und erfolgt aufwendig und fehleranfällig "per Hand". Eigentlich sollte die Software aber zu jedem Zeitpunkt vollständig lauffähig sein. Mit Continuous Integration bezeichnet man einen Prozess, der dafür sorgt dass der gesamte Code nach der Übermittlung von Änderungen übersetzt und getestet wird.

Der Continuous Integration Prozess ist vor allem für Teams wichtig, denn er sorgt dafür, dass nach der Übermittlung von Änderungen der gesamte Code übersetzt und getestet wird, nicht nur der Teil an dem ein Entwickler gerade gearbeitet hat. Die automatisierten Tests sollten von jedem Entwickler ausgeführt werden bevor er Änderungen in die zentrale Versionskontrolle übermittelt. Daran ändert sich durch Continuous Integration nichts. Um sicherzustellen, dass die Tests tatsächlich ausgeführt werden und Fehler frühzeitig erkannt werden, laufen sie in jedem Fall auf dem Continuous Integration Server. Dies entbindet den Entwickler nicht davon die Tests vor dem Commit auszuführen, schließlich behindert fehlerhafter Code der in die Versionskontrolle eingecheckt wurde das gesamte Team, möglicherweise sogar weitere Teams. So sorgt der Continuous Integration Prozess dafür dass teamübergreifend sichergestellt wird dass Fehler so früh wie möglich erkannt werden.

Für den Continuous Integration Prozess stehen zahlreiche Softwaretools zur Verfügung. Neben dem kontinuierlichen Build und Test, der sofort erfolgt, wenn Änderungen in die Versionskontrolle übertragen werden, können durch Continuous Integration auch länger laufende Prozesse, wie z.B. Datenbanktests, automatisiert werden. Diese werden dann z.B. nur nachts ausgeführt. Im grünen Grad wird lediglich der Build- und Testprozess berücksichtigt. Das kontinuierliche Setup und Deployment der Software folgt erst später im blauen Grad.

Martin Fowler hat einen sehr guten Artikel zu diesem Thema verfasst, nachzulesen unter http://www.martinfowler.com/articles/continuousIntegration.html

Siehe auch unter Tools.

Statische Codeanalyse (Metriken)

Warum?
Vertrauen ist gut, Kontrolle ist besser - und je automatischer, desto leichter ist sie.

EvolvierbarkeitKorrektheitProduktionseffizienzReflexion
++++
Single Developer

Wie definiert sich eigentlich die Qualität einer Codeeinheit, z.B. einer Klasse oder Komponente? Reicht es, dass sie funktional die Anforderungen des Kunden erfüllt? Reicht es, dass er schnell genug und skalierbar genug ist? Automatische Tests und schließlich Tests durch den Kunden geben darüber ja Auskunft. Ohne solche Anforderungskonformität hat Software natürlich keine relevante Qualität. Wenn sie dem Kunden nicht nützt, erübrigt sich jede weitere Frage.

Auf der anderen Seite reicht es, entgegen immer noch weit verbreiteter Annahme, allerdings auch nicht, anforderungskonform zu sein. Hohe Qualität ergibt sich nicht allein aus Funktionalität und z.B. Performance. Denn neben den funktionalen und nicht funktionalen Anforderungen gibt es auch noch eine meist unausgesprochene verborgene Anforderung: Kunden wollen auch immer, dass Software nicht nur heute ihre Anforderungen erfüllt, sondern auch noch morgen und übermorgen. Kunden wollen Investitionsschutz durch Evolvierbarkeit.

Für Kunden ist diese Anforderung meist implizit. Sie glauben, es sei selbstverständlich, dass ein immaterielles Produkt wie Software sich quasi unendlich und auf Knopfdruck an neue Anforderungen anpassen ließe. Auch Führungskräfte, die nicht aus der Softwareentwicklung stammen, glauben das oft. Und sogar Softwareentwickler selbst!

Größer könnte das Missverständnis über Software jedoch kaum sein. Evolvierbarkeit ist weder selbstverständlich im Sinne eines von jedem Softwareentwickler ohnehin verfolgten Zieles, noch ergibt sie sich durch irgendetwas quasi von selbst. Evolvierbarkeit ist vielmehr harte Arbeit und muss ständig gegen andere Werte abgewogen werden.

Wenn sonstige Anforderungskonformität sich nun durch (automatisierte) Tests feststellen lässt, wie steht es dann mit der Evolvierbarkeit? Lässt sich die Qualität von Code im Hinblick auf seine (Über)Lebensfähigkeit auch automatisch messen? Zum Teil. Nicht alle Aspekte, die Software evolvierbar machen, sind automatisch prüfbar. Ob zum Beispiel Software offen für Erweiterungen durch ein Add-In-Konzept gehalten wird, ist nicht automatisiert erkennbar.

Dennoch gibt es Metriken, deren Wert für eine Software sich "ausrechnen" lässt. Tools helfen dabei. Diese Tools sollten daher in jedem Softwareprojekt zum Einsatz kommen.
  • Für Legacy Code können die Tools den Status Quo erheben und somit eine Grundlinie definieren, mit der die weitere Entwicklung des Codes (zum Besseren) verglichen werden kann.
  • Für neuen Code, der mit Evolvierbarkeit im Blick geplant wurde, zeigt solch statische Codeanalyse, ob er das Ideal der Planung erfüllt.

CCD sind nicht damit zufrieden, Code nur automatisiert zu testen. Sie haben auch immer ein Auge auf seine Evolvierbarkeit, denn sie wissen, dass Kunden daran genauso interessiert sind - egal, ob sie es explizit gesagt haben oder nicht.

Siehe auch unter Tools.

Inversion of Control Container

Warum?
Nur, was nicht fest verdrahtet ist, kann leichter umkonfiguriert werden.

EvolvierbarkeitKorrektheitProduktionseffizienzReflexion
+++
Single Developer

Bereits im gelben Grad hat der CCD das Dependency Inversion Principle kennengelernt. Dabei wurden die Abhängigkeiten noch "von Hand" aufgelöst. Der nächste logische Schritt besteht nun darin, das Auflösen der Abhängigkeiten zu automatisieren. Dazu stehen zwei Verfahren zur Verfügung:
  • Locator
  • Container

Beide verwenden einen sogenannten Inversion of Control Container (IoC Container). Vor der Verwendung des Containers müssen die verwendeten Klassen im Container hinterlegt werden. Anschließend kann der Container Instanzen der hinterlegten Klassen liefern. Beim Locator geschieht dies explizit. Dies hat den Vorteil, dass die Abhängigkeiten nicht alle im Konstruktor der Klasse aufgeführt werden müssen. Bei Querschnittsaufgaben wie beispielsweise Logging ist dies ein übliches Vorgehen. In der Regel werden die Abhängigkeiten jedoch als Parameter des Konstruktors aufgeführt. Dies hat den Vorteil dass alle Abhängigkeiten sichtbar sind. Der Container ist damit in der Lage die Abhängigkeiten implizit aufzulösen in dem er rekursiv alle benötigten Objekte über den Container instanziert.

IoC Container werden wichtig, sobald die Anzahl der Klassen wächst. Wenn man Separation of Concerns beherzigt, entstehen viele kleine Klassen mit überschaubaren Aufgaben. Das Zusammensetzen von Instanzen dieser Klassen wird entsprechend aufwendiger. Genau hier setzt der IoC Container an, er hilft beim Instanziieren und Verbinden der vielen kleinen Objekte.

Ein weiterer Vorteil von IoC Containern ist die Tatsache, dass der Lebenszyklus eines Objektes per Konfiguration bestimmt werden kann. Soll es zur Laufzeit nur eine einzige Instanz eines Objektes geben (Singleton) kann der Container angewiesen werden, immer ein und dieselbe Instanz zu liefern. Auch andere Lebenszyklen wie z.B. eine Instanz pro Session werden unterstützt.

Um bei Verwendung eines Locators nicht in Abhängigkeit zu einem bestimmten IoC Container zu geraten, kann der Microsoft Common Service Locator (siehe Tools) verwendet werden. Dieser bietet eine vereinheitlichte Schnittstelle zu den gängigen IoC Containern.

Zum Verständnis der Mechanik die hinter einem IoC Container steckt, ist es nützlich die Funktionalität einmal selber zu implementieren. Dabei soll kein vollständiger Container implementiert werden sondern lediglich die Grundfunktionen.

Siehe auch unter Tools.

Erfahrung weitergeben

Warum?
Wer sein Wissen weitergibt, hilft nicht nur anderen, sondern auch sich selbst.

EvolvierbarkeitKorrektheitProduktionseffizienzReflexion
++
Single Developer

Zu professioneller Arbeit gehört selbstverständlich ein ständig akuelles Wissen. Das bedeutet natürlich nicht, dass irgendjemand alles zum Thema Softwareentwicklung und sei es auch nur auf der .NET-Plattform wissen kann und soll. Aktuelles Wissen bezieht sich auf die eigenen Spezialgebiete - welche das auch immer sein mögen. Bestandteil anderer Grade ist deshalb die Praktik der regelmäßigen Informationsaufnahme über verschiedene Medien.

Aus mehreren Gründen sollte solche Informationssammlung jedoch nur eine von zwei Seiten der Medaille "Lernen" sein. Die andere ist die Informationsweitergabe, die Wissensvermittlung. Zur wahren Professionalität gehört unserer Ansicht nach nicht nur "Forschung", sonder auch "Lehre". Denn erst mit der "Lehre" findet wahre Reflektion und Durchdringung eines Gegenstandes statt.

Etwas Gehörtes/Gelesenes anwenden, ist eine Sache. Natürlich bemerkt man dabei auch Verständnislücken. Die "Erforschung" eines Gegenstandes ist dabei jedoch durch den Einsatzzweck natürlich begrenzt. Wer nur soweit forscht, wie er eine Technologie/Konzept gerade braucht, der taucht nicht unbedingt tief ein.

Ganz anders ist das hingegen, wenn das Lernen mit dem Vorzeichen des Weitersagens stattfindet. Wer nicht nur für sich, sondern auch immer für andere lernt, der lernt tiefer. Das wird klar, wenn man versucht, (angeblich) Gelerntes anderen zu vermitteln. Wenn man das nicht beim Lernen im Blick hat, tauchen schnell Fragen auf, die man sich selbst nie gestellt hat. Andere haben eben immer ganz andere Blickwinkel.

Deshalb meinen wir, dass wirklich solide nur lernt, wer sich auch immer wieder dem Lehren, dem Weitersagen, der Wissensvermittlung aussetzt. Nur wer Gelerntes nicht nur anwendet, sondern es mit eigenen Worten für ein Publikum formuliert, bemerkt in dem Prozess, wie tief sein Wissen wirklich ist. Denn wenn sich die Fragezeichen bei den "Schülern" häufen, dann stimmt irgendetwas noch nicht.

Ein reales Publikum ist dafür natürlich am besten. Jeder CCD sollte also möglichst regelmäßig Gelegenheiten suchen, um sein Wissen mündlich weiterzugeben (z.B. bei Veranstaltungen im Kollegenkreis oder User Group Treffen). Unmittelbares Feedback ist ihm dabei gewiss. Alternativ bzw. in Ergänzung taugen aber auch schriftliche Kompetenzäußerungen. Ein Blog ist in 5 Minuten aufgesetzt und Fachzeitschriften suchen ständig nach neuen Autoren. Feedback kommt hier zwar nicht so direkt zurück, dennoch ist die textuelle Ausformulierung von Kenntnissen eine sehr gute Übung.

Clean Code Developer ab dem grünen Grad lernen daher nicht nur "passiv" durch Informationsaufnahme, sondern "aktiv" durch Weitergabe ihres Wissens mittels Präsentationen oder Texten. Das mag ungewohnt sein - ungewohnt ist aber auch womöglich Continuous Integration. In jedem Fall ist aktive Wissensvermittlung eine gute Übung zur Vertiefung der eigenen Kompetenzen frei nach dem Motto: "Tue Gutes und sprich darüber" ;-)

Dass das "Lehren" auch noch einen Nutzen für die Zuhörer/Leser hat, ist selbstverständlich. Vorteile für andere sind aber nicht so motivierend wie eigene Vorteile. Deshalb betonen wir hier vor allem den Nutzen der Wissensvermittlung für den Clean Code Developer.

Messen von Fehlern

Warum?
Nur wer weiß, wie viele Fehler auftreten, kann sein Vorgehen so verändern, dass die Fehlerrate sinkt.

EvolvierbarkeitKorrektheitProduktionseffizienzReflexion
+++++
Single Developer

Während der Softwareentwicklung passieren Fehler. Die passieren in allen Phasen: falsch verstandene oder unklar formulierte Anforderungen führen zu Fehlern genauso wie fehlerhafte Implementierungen. Am Ende ist alles ein Fehler, was dazu führt, dass der Kunde eine Software erhält, die nicht seinen Anforderungen entspricht. Iteratives Vorgehen und Reflexion sind zwei Bausteine, die dazu dienen, den Prozess zu verbessern. Um jedoch zu erkennen, ob tatsächlich eine Verbesserung eintritt, muss eine Messgröße vorliegen, an der man eine Entwicklung zum Besseren überhaupt ablesen kann.

Das Messen der Fehler kann durch Zählen oder durch Zeitnahme erfolgen. Dabei steht nicht die Präzision im Vordergrund, solange die Messmethode vergleichbare Daten liefert. Die Entwicklungstendenz über mehrere Iterationen hinweg soll ersichtlich werden. Ferner geht es nicht darum, die Verantwortlichkeit für einen Fehler zu klären. Am Ende ist es egal, wer den Fehler verursacht hat, so lange das Team daraus lernt und seinen Prozess verbessert.

Welche Fehler sind zu messen? Es sind nicht die Fehler, die während der Entwicklung auftreten. Die sind nicht zu vermeiden und führen hoffentlich dahin, dass am Ende einer Iteration ein fehlerfreies Produkt ausgeliefert wird. Vielmehr geht es um die Fehler, die nach einer Iteration zurückgemeldet werden vom Kunden bzw. seinem Stellvertreter (z.B. Productowner oder Support). Das sind Fehler, die die Umsetzung neuer Anforderungen behindern. Zu messende Fehler sind also die, die auftreten, wenn man glaubt, dass es sie nicht geben dürfte ;-) Wann im Prozess ein Team diesen Punkt erreicht und flucht, weil da wieder so ein Fehler der sonstigen Arbeit dazwischenfunkt, ist teamindividuell zu bestimmen.

Weiter geht es beim blauen Grad.

ScrewTurn Wiki version 3.0.3.555. Some of the icons created by FamFamFam.