Refactoring: Strategien zum langfristigen Verbessern von Quelltextstrukturen

13. Februar 2017

In ihrem Artikel Refactoring: Strategien zum langfristigen Verbessern von Quelltextstrukturen zeigt Franziska Trabold an einem Beispiel wie man an schwierig zu verstehenden Code Tests anbringt anschließend den Code so umbaut, das er leichter verständlich ist.

Code anderer Leute lesen

14. Dezember 2016

Michael Feathers: Technical Debt, Process and Culture

15. September 2012

In seiner jüngsten Präsentation Technical Debt, Process and Culture empfiehlt Michael Feathers, ein Klima zu schaffen, in dem das technische Defizit wirksam reduziert werden kann.

Joshua Kerievsky: Sufficient Design

10. März 2012

Interessante Präsentation Sufficient Design: Quality In Sync With Business Context anläßlich der QCon San Francisco 2011 von Joshua Kerievsky, Gründer der Beratungsfirma Industrial Logic und Autor von Refactoring to Patterns, eines meiner Lieblingsbücher zum Thema Refactoring (das Buch stand auch Pate für den vierten Teil meines Buches Software-Sanierung).

Zusätzlich gibt es noch ein ausführliches Interview mit Kerievsky auf dem holländischen Devnology Podcast (keine Angst, das Interview ist auf Englisch). Darin erzählt er über seine Erfahrungen im Umgang mit Entwurfsmustern und wie sich seine Ansichten darüber änderten, als er mit Hilfe von Ward Cunningham mit der Agilen Softwareentwicklung begann. Daneben erörtert er, wie er vom Buchautor zum Autor von E-Leraning-Material wurde.

Objekte auf der Anklagebank

28. Dezember 2011

Michael Feathers, Brian Foote, Richard P. Gabriel, Joshua Kerievsky, Eliot Miranda und Dave Ungar machen Objekten in dieser Präsentation den Prozess. Auf der Anklagebank sitzen einen Wasserball (Symbol für Smalltalk), ein Plüschpinguin (das Maskottchen von Linux), ein Becher Java-Kaffee und ein UML-Objekt. Das Publikum agiert als die Geschworenen.

Das ganze ist mäßig unterhaltsam. Was vielleicht nicht so klar herauskommt ist, dass es bis heute keinen wirklichen Konsens darüber gibt, was objektorientiert eigentlich bedeutet. Weiters ist das dominierende Paradigma eine Art komponentenbasierte, prozedurale Programmierung in der Objekte im Wesentlichen mit Datensätzen gleichgesetzt werden. So gesehen wäre es fair, wenn man Objekten erst einmal eine Chance gäbe, bevor man sie verurteilt…

Der zentrale Fehler in der agilen Softwareentwicklung

18. Dezember 2011

In seiner Präsentation anlässlich der norwegischen Entwicklerkonferenz NDC2011 kritisiert Michael Feathers unter anderem, dass oft User Story auf User Story definiert und umgesetzt wird, ohne das alle Beteiligten wissen, welche Auswirkungen das auf die Softwarearchitektur hat. Anders ausgedrückt:

Softwareentwicklung kann nicht hinter einer Schnittstelle aus User Storys versteckt werden.

Nur wenn Softwareentwickler und Fachabteilungen eng zusammenarbeiten, so Feathers, erhält man im Endeffekt brauchbare Software.

Eine kurze Geschichte der iterativen Softwareentwicklung

26. November 2011

Viele Leute glauben bis heute, dass die iterative Softwareentwicklung eine Modeerscheinung der letzten 15 oder so Jahre wäre. Tatsächlich verhält es sich genau umgekehrt: Wahrscheinlich setzte bereits der Computerpionier John von Neumann auf die inkrementelle Softwareentwicklung.

Mehr dazu im Artikel Some History about Iterative Development auf meinem englischsprachigen Blog…

Die Suche des Ingenieurs nach der universellen Methode

18. Oktober 2011

William Vaughn Koen, emeritierter Professor der University of Texas und Autor des Buches Discussion of the Method: Conducting the Engineer’s Approach to Problem Solving über die Art und Weise, wie Ingenieure Probleme lösen.

Faszinierende Einsichten über die Natur des Ingenieurwesens und den Umstanden, dass auch in scheinbar exakten Wissenschaften wie der Mathematik Faustformeln (Heuristiken), und nicht glasklare Logik die Entscheidungen der Forscher bestimmen.

Der Vortrag hat nur einen kleinen Schönheitsfehler: Die Aussage „Alles ist eine Heuristik!“. Man sollte sich von der Eloquenz und Reputation des Vortragenden hier nicht blenden lassen, denn offensichtlicherweise ist ein Tisch ein Tisch, ein Sessel ein Sessel und eine Heuristik eine Heuristik. Diese triviale Einsicht bin ich persönlich nicht bereit aufzugeben! Prof. Koen hätte hier auf Boëthius hören sollen: Wenn du geschwiegen hättest, wärest du Philosoph geblieben.

Ansonsten ist der Vortrag äußerst unterhaltsam und lehrreich und alle beschriebenen Erkenntnisse gelten natürlich auch uneingeschränkt für Softwareentwickler.

Code für andere Leute schreiben, Teil 8

28. September 2011

Der vorhergehende Teil dieser Artikelserie war der Identifikation von Analogiemustern im Quellcode gewidmet. In diesem letzten Teil wird die für Thomas Mullen fundamentale Regel des Softwareentwurfs beschrieben und Schlussfolgerungen aus all dem gezogen.

Die fundamentale Regel des Softwareentwurfs

… more what you’d call ‘guidelines’ than actual rules

Capt. Barbossa in „Pirates of the Caribbean: The Curse of
the Black Pearl“ (dt.: „Der Fluch der Karibik“).

Die „4 minus Analogien“ -Regel

Wie wir im 6. Teil gesehen haben sind Analogien die Bausteine der Wahrnehmung. Identifizieren der Analogien eines Problems ist der erste Schritt zur Lösung. Code-Strukturen zu identifiziren, die Analogien repräsentieren, sind Teil der Kenntnisse eines erfahrenen Programmierers. Unser Gehirn hat sich dahingehend entwickelt, Netzwerke für das Langzeitgedächtnis aufzubauen (siehe Teil 3). Netzwerke, die für das effiziente Auffinden von Informationen und das minimieren des Aufwandes für das Umlernen optimiert sind. Die Konsequenz dieser Entwicklung ist, dass wir lediglich vier Code-Elemente oder weniger zu einem gegebenen Zeitpunkt verarbeiten können. Die einzige Ausnahme ist die Anzahl von Varianten einer Analogie.

Wir können eine Einkaufsliste unabhängig von ihrer Länge verstehen, da wir wissen, dass wir alle Elemente der Liste gleich behandeln können (z.B. finden und einkaufen). Wir werden also instinktiv alle Varianten als ein Element in einer Analogie gruppieren. Damit ergibt sich die fundamentale Regel des Softwareentwurfs als:

Software sollte in Chunks aus maximal vier Elementen Gruppiert sein, wobei eine beliebige Anzahl von Varianten von Analogien erlaubt sind.

Da wir die Definition der Psychologen für Chunking verwenden (siehe Teil 2), ist die Forderung nach geringer Kopplung und hoher Kohäsion (gemäß dem Einzelzuständigkeitsprinzip) automatisch erfüllt. Das bedeutet natürlich nicht, dass beispielsweise eine Klasse nur vier Methoden haben darf. Es ist auch erlaubt, Methoden in Chunks von vier Methoden oder weniger zusammenzufassen, dabei ist wieder eine beliebige Anzahl von Varianten von Analogien erlaubt.

Software zu Chunks zusammenzufassen ist eine durchaus offensichtliche Form der Strukturierung von Code und wird daher in zahlreichen Quellen beschrieben. Ein Erklärungsmodell, das erlaubte Ausnahmen beschreibt hingegen nicht. Das klassische Beispiel ist das eines einzelnen, einfachen Verhaltens basierend auf unterschiedlichen Datentypen, das aber keine eigene Klasse benötigt. In diesem Fall ist eine einfache Switch-Anweisung besser geeignet.

switch (operand) {
        case MULTIPLY:
            return first * second;
        case DIVIDE:
            return first / second;
        case ADD :
            return first + second;
        case SUBTRACT :
            return first - second;
    }

Diese Switch-Anweisung braucht auch dann nicht in eine eigene Klasse ausgelagert zu werden, wenn sie mehr als vier Case-Anweisungen enthält. Schließlich sind alle Case-Anweisungen Varianten der Switch-Analogie. Da unser Gehirn alles, was es als Analogie erkennt, zusammenfasst, führt das zu keiner kognitiven Überlastung.

Wie wir im vorhergehenden Teil gesehen haben, ist das die Struktur einer Analogie, also folgt es der „4 minus Analogien“ -Regel. Mehr als vier Elemente einzufügen belastet den Leser damit, dass er selbst Elemente zu Chunks zusammenfügen muss. Ohne die Anleitung, die der Autor dem Leser durch die Strukturierung des Codes zur Verfügung stellt könnte der Leser zu einer anderen Aufteilung kommen oder er könnte überhaupt Schwierigkeiten haben, eine Aufteilung zu finden. Das kann zu Missverständnissen führen und diese führen nicht selten zu Fehlern.

Chunking und Analogien mögen einen Einblick in die Kunst des Programmierens geben, vermögen aber nicht alles zu erklären. Beispielsweise ähnelt Joshua Bloch’s Erbauermuster [mehr noch Fluent Interfaces, wie sie Martin Fowler beschreibt] der Struktur nach geschriebener Sprache und dass trotz der Einschränkungen eines stark beschränkten Vokabulars.

    customer.newOrder()
            .with(6, "TAL")
            .with(5, "HPK").skippable()
            .with(3, "LGV")
            .priorityRush();

Die Struktur des Codes, die ein Fluent Interface ermöglicht, kommt geschriebener Sprache sehr nahe.

Schlussfolgerungen

Code verständlich zu gestalten ist das primäre Ziel für einen Großteil der Entwurfsprinzipien in der Softwareentwicklung. Um zu verstehen, was „Einfachheit“ im Zusammenhang mit Softwareentwicklung bedeutet, müssen wir die Prozesse und deren Beschränkungen verstehen lernen, die es uns ermöglichen Quellcode zu verstehen. Das kognitive Modell kann auch von Neulingen verstanden und gelernt werden, denn man kann zur Veranschaulichung Beispiele „aus dem richtigen Leben“ verwenden, die auch Nichtprogrammieren vertraut sind.

Die Konzepte der Kognition sind die Fundamente hinter den Entwurfsprinzipien. Das erlernen des kognitiven Modells ist allerdings kein Ersatz dafür, Entwurfsprinzipien zu erlernen und zu verstehen, aber es reduziert die nötige Erfahrung, die der Neuling benötigt, um herauszufinden, unter welchen Umständen er welches Prinzip umsetzen soll. Die daraus entstehende Verbesserung unseres Codes wird uns helfen, die Kosten für Wartung und Support zu reduzieren.

Code für andere Leute schreiben, Teil 7

27. September 2011

Im letzten Teil haben wir erörtert, was man in der Psychologie unter dem Begriff Analogie versteht und auf welche Art und Weise wir Analogie auswählen. In diesem Teil wollen wir gängige Analogiemuster im Quellcode aufspüren.

Analogiemuster in Software

Die offensichtlichste Art Analogien in Software zu implementieren ist die Verwendung von Interfaces und abstrakten Klassen (siehe einfache Analogien und Regelanalogien in der Aufzählung unten im vorhergehenden Teil), allerdings kann man Analogien auch auf andere Art darstellen.

Bitte berücksichtigen Sie, dass die Codebeispiele in diesem Abschnitt Beispiele für die Strukturierung von Code darstellen. Sie zeigen, wie der Code aussehen könnte aber nicht notwendigerweise wie er aussehen sollte. Eine gute Richtlinie für das Identifizieren von Analogien ist, wenn man „erraten“ kann, wie der Code logisch fortgesetzt werden kann, wenn das Verhalten um eine neue Variante erweitert werden soll.

Der folgende Code zeigt Beispielsweise das Kontotyp-Attribut für unterschiedliche Terminbörsen als Varianten (Eurex, Liffe, etc.).

public String getAccountType(String account) {
    if(exchange.equals("EUREX"))
        return getEurexAccountType(account);
    if(exchange.equals("LIFFE"))
        return getLiffeAccountType(account);
    if(exchange.equals("MONEP"))
        return getMonepAccountType(account);

Es ist hier einfach zu erraten, wie der Code für die Französische Terminbörse MATIF erweitert werden muss:

    if( exchange.equals("MATIF") )
        return getMatifAccountType(account);

Auf den ersten Blick sieht das Erraten dieser Erweiterung wie das Ergebnis von Erfahrung in der Softwareentwicklung aus. Tatsächlich ist es aber das Ergebnis von Erfahrung im Erkennen von Analogien und diese Fähigkeit erlernen wir bereits in unserer Kindheit! Die unbewusste Annahme, dass der Leser analoge Strukturen erkennen und erstellen kann (die die Anforderungen an die Anwendung widerspiegeln) ist zweifellos eine zentrale Fähigkeit, die auch von Softwareentwicklern verlangt wird, die den Code zum ersten Mal zu Gesicht bekommen (im vorletzten Teil haben wir diese Entwickler Neulinge genannt).

Im folgenden Abschnitt werden zahlreiche Sprachmittel von Java vorgestellt, die dazu verwendet werden können, Analogien darzustellen. Diese Sprachmittel sind natürlich auch in anderen Programmiersprachen vorhanden und abhängig von den Sprachmitteln ist in vielen Sprachen natürlich noch mehr möglich.

Attribute-Only-Analogien

Die einfachste Art einer Attribute-Only-Analogie ist eine Hashtabelle (Map). In diesem Beispiel werden die einzelnen Terminbörsen als direkte Analogien (siehe letzter Teil unten) gesehen. Das einzige Attribut von Interesse ist dabei das Land, in welchem sich die Börse befindet:

static Map exchangeCountry = new HashMap();

static {
    exchangeCountry.put("EUREX","Germany");
    exchangeCountry.put("CBOT","US");
    exchangeCountry.put("LIFFE","England");
    //can guess what to do
    //to extend for NYBOT......
}

//usage
public String logMessage(String exchangeName) {
    return "Exchange " + exchangeName +
           " is in " +
            exchangeCountry.get(exchangeName);
}

Statement-Formanalogien

In diesem Fall wird dieselbe Ausführung (Form) für unterschiedliche Felder oder Elemente verwendet, um das Verhalten zu implementieren. Die Ähnlichkeit in der Ausführung (Form) des Statements ist der Schlüssel, durch den der Leser die Analogie erkennt. In diesem Beispiel ist das Überprüfen des Feldes die Analogie und jedes Feld der Klasse benötigt seine Eigene Variante zur Überprüfung desselben.

private Date expirationDate;
private Long contractNumber;
private Double price;
private Double quantity;
private String description;

private boolean isValid()
{
    if(expirationDate == null ||
       !(expirationDate.getTime() > 0))
        return false;

    if(price == null ||
       !(price.doubleValue() > 0))
        return false;

    if(quantity == null ||
       !(quantity.doubleValue() > 0))
        return false;

    //can guess what the code line is
    //for the String parameter
    //"description" ......

    return true;
}

Switch-Analogie

Ähnlich wie bei der Statement-Formanalogie sind die Implementierungen zu Chunks gruppiert, allerdings mit einer formelleren Definition des Typs der Variante. Das Beispiel gibt das Ergebnis unterschiedlicher Operationen zurück, die auf zwei Zahlen angewendet werden sollen (Addition, Subtraktion, etc.)

private int operand;

public double calculate(double first, double second) {
    switch (operand) {
        case MULTIPLY:
            return first * second;
        case DIVIDE:
            return first / second;
        case ADD :
            return first + second;
        case SUBTRACT :
            return first - second;
    }
    return 0;
}

Methodennamen-Analogie

Entweder das Präfix oder das Suffix der Methode wird verwendet, um die Analogie auszudrücken. Das Präfix hat den Vorteil, dass die Gruppierung und damit die Analogie auch dann noch erhalten bleibt, wenn die Methoden alphabetisch sortiert werden. Als Beispiel für die Präfix-Variante dient das Besuchermuster. Im Beispiel für die Gruppierung mithilfe des Suffix wird ein Objekt des Typs Double in unterschiedliche Datentypen umgewandelt. Diese Form ist nützlich, wenn alle Varianten gemeinsame Abhängigkeiten haben, die in der Klasse zu Chunks zusammengefasst werden können.

//example 1 - prefix
public void visitExpression(Node a) {};
public void visitBlock(Node a) {};
public void visitFile(Node a) {};

//example 2 - suffix
Double doubleObj = new Double(0);
double a = doubleObj.doubleValue();
int b = doubleObj.intValue();
long c = doubleObj.longValue();
float d = doubleObj.floatValue();

Methodenparameter-Analogie

Der Typ des Parameters, der an eine Methode übergeben wird, kann die verwendete Variante bestimmen. Das folgende Beispiel berechnet das Maximum zweier zahlen:

float f = Math.max(1.0F, 2.0F);
int i = Math.max(1, 2);
long l = Math.max(1L, 2L);
double d = Math.max(1.0, 2.0);

Klassen-/Interface-Analogie

Die Klassen-/Interface-Analogie erlaubt es, mehrere Elemente (Felder) zu Chunks zusammenzufassen.

abstract class Shape {
    String name; //square, circle etc
    int numSides;
    abstract double area();
}

class Rectangle extends Shape {
    double length;
    double width;

    double area() {
        return (length * width);
    }
}

Anwendungsanalogie

Die Laufenden Instanzen einer Anwendung sind direkte Analogien voneinander. Konfigurationsdateien und Systemeinstellungen sind die Eigenschaften (Merkmale) dieser Analogie.

Multiparadigmen-Design

Die Technik, Analogien in den Anforderungen zu identifizieren und daraufhin die passenden Code-Strukturen auszuwählen wurde erstmals von James Coplien beschrieben (siehe Multi-Paradigm Design for C++). Coplien benutzt allerdings den Begriff Domain für Analogieen und Sub-Domains für Varianten. Solution Domains entsprechen den Analogiestrukturen im Code (auch wenn lediglich formale Strukturelemente wie Interface, Template [das mächtigere Pendant zu Generics in C++] etc. identifiziert werden). Die „commonality and variability analysis“ ist der Prozess, mit dem die Analogien identifiziert werden. Beide, Coplien und Grady Booch (in Object-Oriented Analysis and Design with Applications) beschreiben objektorientiertes Design als „Aufteilen durch Zusammenfassen“. Strukturiertes Design ist die Disziplin des Zusammenfassens aber für Thomas Mullen ist objektorientiertes Design Identifizieren und Kodieren von Analogien. Das sind unterschiedliche aber komplementäre Fähigkeiten, die erworben werden müssen, um Software mit gutem Design zu erstellen.

Im achten und letzten Teil dieser Serie wird die fundamentale Regel des Softwareentwurfs beschrieben und Schlussfolgerungen aus all den Erkenntnissen, die in den vorangegangenen Teilen gewonnen wurden, gezogen.