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.