Freitag, 11. März 2011

Defensive programming

Die Programmiersprache Java zählt zu den sicheren Sprachen. Zeigerarithmetik, korrupte Speicherstellen und Pufferüberläufe wie in C/C++ sind in Java nicht möglich. Bei der Weiterentwicklung von Java ist die Typsicherheit hinzugekommen und Meta-Daten (Annotation: @Override),  mit denen potentielle Fehler früh zur Kompilierzeit aufgedeckt werden können.

Java Idiome, wie beispielsweise unveränderliche Objekte (Immutables), fördern die Kapselung und Datensicherheit. Ein Risiko, das bei Java Programmen nicht unterschätzt werden darf, ist die Referenzierung von Objekten. Objektreferenzen ziehen sich teilweise vom Modell bis in die Benutzerschnittstelle hinein. Sind unveränderliche Objekte sicher im Kontext der Nebenläufigkeit, trifft für Referenzen auf veränderliche Objekte das Gegenteil zu. Die Folge können Laufzeitausnahmen (ConcurrentModificationException)  bis hin zu zerstört aussehenden Oberfläche sein, die eine Anwendung fehlerhaft erscheinen lassen.

Defensive Kopien sind bei hoher Nebenläufigkeit sinnvoll, in denen keine threadsichere Collection oder ein anderes Mittel zur Synchronisation eingesetzt werden kann. Weitere Anwendungszwecke defensiver Kopien sind  die Sicherheit und Robustheit zu erhöhen, sodass Objektdaten nicht bewusst oder unbewusst verändert werden können.

Es ist nicht angebracht, grundsätzlich defensiv zu programmieren. Intensive defensive Programmierung beeinflusst das Laufzeitverhalten negativ, weil mehr Objekte erzeugt werden. Grundsätzlich verhindert eine defensive Programmierung lange Spaghetti-Referenzen, die sich in einer Anwendung  verknäulen und den Aufbau eines Fehlerbildes erschweren. Spaghetti-Referenzen haben die Eigenheit, im Fehlerfall nur sporadisch je nach Programmzustand Fehler zu erzeugen. Diese Fehler sind deshalb schwer nachzuvollziehen und können bei hoher Nebenläufigkeit nur mit sehr viel Aufwand entdeckt werden. Spaghetti-Referenzen werden partiell lange gehalten bzw. manchmal sogar niemals freigegeben. Im Extremfall führt das zu einem OutOfMemoryError. Die Programmierung auf kleinstmöglichen Level (SLA) und defensive Kopien verhindern Fehler, die ein Programm zerbrechen lassen.

Die Nutzung von „final“ bei der Übergabe von Referenzparameter an Methoden hilft nur bedingt als Markierung für hohe Aufmerksamkeit,  weil nach einem entsprechenden Methoden-Aufruf eine Setter-Methode aufgerufen werden kann, die den Objektzustand auf den die Referenz verweist ändert. Defensive Kopien nach der Übergabe eines Parameters und bei Rückgabewerten sind ein Schutz gegen das ungewollte ändern des Objektzustandes über eine Referenz.

Einfaches Beispiel für eine defensive Programmierung:

import java.util.Date;

public class DefensiveCopyStrategy {

    private final Date date;
   
    public DefensiveCopyStrategy(final Date date) {
       
        this.date = new Date(date.getTime());
    }
   
    public Date getDate() {
       
        return new Date(date.getTime());
    }
}

Eine gute Praxis ist es, konform zu Tell, don't ask, gänzlich auf Getter-Methoden zu verzichten. Dies  zeichnet Objekte aus, die ein definiertes Verhalten implementieren. Objekte, mit ausschließlich Getter-/Setter-Methoden hingegen, werden als Datenobjekte bezeichnet, die kein eigenes Verhalten implementieren.

Mutationen, die sich aus Verhaltensobjekten und Datenobjekten zusammensetzen, stehen der Erweiterbarkeit im Wege, weil die Getter-/Setter-Aufrufe dazu verleiten, Funktionalitäten in einer Applikation zu implementieren. Die Objektkapselung wird dadurch gebrochen. Die Kohäsion und die gewünschte Eigenschaft der losen Kopplung verschlechtern sich beim massiven Gebrauch von Getter-/Setter-Methoden in Objekten, die nicht als reine Datenobjekte fungieren.


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.

Freitag, 4. März 2011

OCM Certification message received

Der Aufwand das Assignment (Part 2) und das Essay (Part 3) der Java EE 5 Enterprise Zertifizierung in Kauf zu nehmen hat sich gelohnt. Angespornt von dem Ergebnis des Wissenstests, bin ich mit sehr viel Motivation an die Bearbeitung des Assignments gegangen. Das Assignment umfasste eine B2C Applikation, was mir sehr entgegenkam. Die B2C Anwendung war verbal, mit einem Domain-Modell und vier Use Cases beschrieben.

Nach der Anforderungsanalyse, für die ich mir zwei Tage Zeit genommen hatte, habe ich die funktionalen und nicht funktionalen Anforderungen verstanden. Einen weiteren Tag verbrachte ich mit Nachdenken, wie die Menge der Anforderungen technisch zu lösen wären. In in dieser Phase notierte ich meine Gedanken, um diese in der Folge schrittweise verfeinern zu können.

Mit dem Entwurf des Klassendiagramms hat die Arbeit an dem Entwurf begonnen. In einer Tages- und Nachtsitzung brachte ich 75% der Lösung zu Papier, sodass ich im Anschluss mit dem Komponentendiagramm beginnen konnte. Beide Diagramme waren Dreh- und Angelpunkt für das Deployment und die Sequenzdiagramme. Als Werkzeug für die UML Diagramme verwendete ich den UML Modeler 8.1 von Visual Paradigm in der Vollversion. Das Werkzeug ist sehr gut anzuwenden und bereitete keine Probleme.

Die Diagramme wurden anschließend in mehreren Sitzungen verfeinert. Parallel zu dieser Arbeit formulierte ich meine Annahmen und wesentlichen Entwurfsentscheidungen. Die drei größten Risiken der Lösung wurden in einem extra Kapitel beschrieben.

Assignment und Essay wurden von einem Gutachter bewertet. Ich rate deshalb, das Assignment mit hoher Sorgfalt zu erstellen und die UML Notation strikt einzuhalten. Der Entwurf soll von Klarheit geprägt sein, weder zu wenig noch zu viele Informationen sind passend. Es handelt sich nicht um eine Fleißarbeit, noch um die reine Anwendung der Java EE 5 APIs. Die Geschäftslogik soll transparent werden und die Anforderungen des Assignments bestmöglich erfüllen.

Es handelt sich um eine Zertifizierung im Java EE 5 Umfeld, deshalb ist es ratsam, die Java EE 5 APIs zu nutzen. Leicht rutscht man in die neuen Java EE 6 APIs ab, die mit der Java EE 5 Technologie nicht lauffähig sind!

Man bedenke, das Design soll von einem Java Entwickler verstanden und umgesetzt werden können. Wesentlich ist, dass keine Informationen verloren gehen. Achte deshalb auf die Multiplizität (Leserichtung einhalten), nutze die Aggregation/Komposition, die Abstraktion und Vererbungsbeziehungen. Achte auf die Kernpunkte, die schwierige Entwurfsentscheidungen beinhalten und entwerfe mit viel Bedacht. Sei zu jeder Zeit sorgfältig und entwerfe die Software wie in einem realen Projekt.

Setze ein Zeitlimit, damit die Gefahr sich in dem Assigment zu verfangen gebannt ist. Mein Zeitlimit nach der Anforderungsanalyse waren maximal 80 Arbeitsstunden, was normalerweise ausreichend ist.

Literatur und UML Werkzeug:

  • Sun Certified Architect for Java EE Study Guide (Second Edition) von Mark Cade und Humphrey Sheil (Kapitel 9)
  • UML 2 Glasklar von Chris Rupp, Stefan Queins, Barbara Zengler
  • Fast Track UML 2.0 von Kendall Scott
  • SCEA Forum der JavaRanch mit Referenzen zu weiteren Informationsquellen
  • Visual Paradigm for UML 8.1 Modeler Edition

Für die Vorbereitung auf das Essay, welches in einem Prometric Testcenter zu absolvieren ist, habe ich meine Notizen und den Entwurf des Assignments verwendet. Die Fragen in dem Essay haben sich auf den Entwurf bezogen und waren deshalb recht einfach zu beantworten. Ich habe das Assignment direkt nach dem Essay abgesendet und eine Bestätigung vom Oracle Zertifizierungsteam erhalten.

Ein paar Tage später ist eine weitere E-Mail eingegangen, dass das Assignment und das Essay an die Gutachter zur Bewertung versendet wurde. Die Bewertung dauerte zwei Wochen, sodass ich mit der Performanz des Bewertungsprozesses sehr zufrieden bin. Die Frage, ob sich der Aufwand für die Zertifizierung in drei Schritten gelohnt hat, beantworte ich unumstritten mit „Ja". Man  lernt sehr viel bei dieser Zertifizierung und es macht großen Spaß eine Projektaufgabe zu lösen, die sehr nahe an reellen Anforderungen ist.

Dienstag, 1. März 2011

DRY Coding

DRY (Don’t repeat yourself) ist ein Grundprinzip von Clean Code. DRY ist in allen Entwicklungsphasen anwendbar. Im Design bei der Komposition von Komponenten, bei der Datenmodellierung stellvertretend durch die Normalisierung und bei der Implementierung durch die Wiederverwendung von Quellcode.

Nichts ist einfacher als mit „Copy & Paste“ gegen DRY zu verstoßen. Der Sumpf der dabei entsteht wird immer größer und führt häufig zu schlechter Evolvierbarkeit und Inkonsistenzen einhergehend mit höherer Fehleranfälligkeit. Ein Verstoß gegen DRY hat eine höhere Abhängigkeit im Quellcode zur Folge, eine geringere Isolation von Komponenten und schlechtere Testbarkeit. Zusätzliche Arbeit durch höhere  Komplexität sind die Folgen von DRY. Änderungen in einem System sollen deshalb nur einmal durchgeführt werden. Die Gefahr ein Quellcode-Duplikat zu vergessen und damit verbunden eine Änderung während der Weiterentwicklung zu übersehen ist stets gegeben.

DRY einzuhalten ist ein fester Bestandteil des TDD Mantras und wird regelmäßig als integrierter Schritt beim Schreiben von Testfällen ausgeführt. Patterns wie Template Method helfen Duplikate beim Refactoring zu eliminieren. Abstraktionen und generische Strukturen sind mächtige Mittel, um das DRY Prinzip einhalten zu können.

Die Wiederverwendung von Quellcode durch Refactoring ist nicht auf Makrokomponenten beschränkt. Grundsätzlich findet ein Refactoring zunächst auf der Mikroebene statt, mit dem Ziel die innere Struktur eines Softwarebausteins zu verbessern. Selbst einzelne Quellcodezeilen und die Bündelung kleiner Methoden können durch das DRY Prinzip die Komplexität im Quellcode verringern und dadurch die Wartbarkeit verbessern. Es ist deshalb eine gute Praxis die gängigen Java Idiome und APIs zu nutzen.

Java Syntax verhindert DRY auf dem Abstraktionslevel einer Methode:

public double calculateProduct(double factor) {
        
    product = product * factor;
       
    return product; 
}

public double calculateProduct(double factor) {
       
    product *= factor;
       
    return product;
}

Java Idiom (Static Factory Method) verhindert DRY bei der Definition einer Datenstruktur:

private Map<Double, List<Double>> products = new HashMap<Double, List<Double>>();

private Map<Double, List<Double>> products = Products.newInstance();

In Java 7 sind "Static Factory Methods" als Problemlösung für das DRY Prinzip überflüssig. Die neue Java Diamant Syntax (Diamond) ist für diesen Fall die beste Lösung.

private Map<Double, List<Double>> products = new HashMap<>();

Interessant ist, dass in der Methode "getProduct" das Attribut "product" verwendet wird. In diesem Fall wird gegen das Single Level of Abstraction (SLA) Prinzip verstoßen. Die weitläufige Verwendung von Attributen in den Methoden einer Klasse, führt wie DRY zu geringerer Isolation und darüber hinaus zu schlechterer Lesbarkeit des Quellcodes.

Im Java-Umfeld birgt der Verstoß gegen SLA die Gefahr in sich Memory Leaks zu erzeugen. Dieses Phänomen tritt durch Objektreferenzen auf, die über einen langen Zeitraum hinweg als Attribute in der Instanz einer Klasse gehalten werden. Der Garbage Collector gibt den Speicher, der durch Instanzen belegt wird, nicht frei sofern noch Referenzen auf die Instanzen verweisen. Zirkulare Referenzen können dabei eine Referenzinsel bilden, die es dem Garbage Collector besonders schwer machen Speicher freizugeben. Die Zuweisung von "null" nach der Benutzung von Referenzen hilft zwar dem Garbage Collector bei der Freigabe des Speichers, verschlechtert auf der anderen Seite aber die Lesbarkeit des Quellcodes.

Es ist deshalb eine gute Praxis SLA anzuwenden und dabei den natürlichen Mechanismus der Speicherfreigabe zu nutzen. Dieser Mechanismus tritt in Kraft, sobald eine lokale Variable, die eine Referenz auf eine Instanz hält, "Out of Scope" ist (Methode wurde beendet). SLA und damit verbunden eine Referenzvariable im kleinstmöglichen "Scope" zu verwenden fördert im Java-Umfeld deshalb nicht nur die Lesbarkeit sondern schont Resourcen.


Der Rechtshinweis des Java Blog für Clean Code Developer ist bei der Verwendung und Weiterentwicklung des Quellcodes des Blogeintrages zu beachten.