Training - Beratung - Projektarbeiten

www.David-Tielke.de

Webinar - Softwarequalität mit Visual Studio 2012

Developer Media - Heute fand das erste Webinar in einer zweiteiligen Reihe zum Thema “Softwarequalität mit Visual Studio 2012” statt. Knapp 50 Teilnehmer informierten sich in 50 Minuten was genau Softwarequalität ist und wie man einzelne Merkmale identifizieren kann. Dazu wurden mit der defensiven Programmierung und Codierrichtlinien zwei Maßnahmen vorgestellt und an Beispielen erläutert.

Wie angekündigt finden Sie hier nun sowohl die Folien als auch das erstellte Projekt im Rahmen der defensiven Programmierung.

Ich bedanke mich bei allen Teilnehmern und hoffe auf ein baldiges Wiedersehen im nächsten Webinar Teil 2 oder in dem Workshop “Softwarequalität mit Visual Studio 2012” in Berlin von developer-media.de.

Links
Folien
Projekt
Webinar Teil 2
Workshop Softwarequalität in Berlin

Richtiges setzen von Werten in Eigenschaften mit PropertyChanged

Wie in dem letzten Beitrag “OnPropertyChanged 2.0” gezeigt, gibt es qualitativ besser Möglichkeiten die INotifyPropertyChanged – Schnittstelle zu implementieren, als mit einer eventauslösenden Methode mit einem normalen string-Parameter.

Die im letzten Beispiel gezeigte Auslösung, war wie folgt implementiert:

public int X
{
    get { return _x; }
    set
    {
        _x = value;
        OnPropertyChanged(() => this.Y);
    }
}

Unnötiges Setzen von Feldern

In Praxisprojekten ist diese Art der Implementierung allerdings nicht wirklich optimal, da zuvor eine Prüfung erfolgen muss, ob sich der Wert des zugrunde liegenden Feldes überhaupt geändert hat. Ist dies nicht der Fall, macht natürlich weder das Sssetzen des Feldes noch ein Auslösen des PropertyChanged-Events Sinn. Daher bietet sich eine Implementierung wie folgt an:

public int X
{
    get { return _x; }
    set
    {
        if (_x == value)
        {
            return;
        }
        _x = value;
        OnPropertyChanged(() => this.Y);
    }
}

Damit ist sichergestellt, das dass PropertyChanged-Event nur dann ausgelöst wird, wenn der gesetzte Wert sich auch tatsächlich geändert hat.

Nun gibt es in einer Entitätsklasse normalerweise mehr als nur eine Eigenschaft, welche den Benachrichtigungsmechanismus von INotifyPropertyChanged nutzt und implementiert. Folgt man dem DRY-Prinzip muss der Überprüfungscode und das Auslösen des Events in eine separate Methode ausgelagert werden. In Schritt 1 lagern wir Logik zur Überprüfung von Gleichheit aus:

private void SetValue<T>(ref T field, T value)
{
    if (field.Equals(value))
    {
        return;
    }

    field = value;
}

Dabei gibt er erste Parameter das zu setzende bzw. zu überprüfende Feld an und der zweite Parameter den von außen gesetzten Wert. Wichtig hierbei ist es, den ersten Parameter als ref zu kennzeichnen, da wir in der Lage sein müssen, den Wert des zugrunde liegenden Feldes zu verändern. Sowohl für Werte- als auch für Referenztypen erreicht man dieses Verhalten bei Methodenaufrufen mit dem ref-Schlüsselwort.

Benachrichtigungsmechanismus

Als nächstes kümmern wir uns um die Benachrichtigung mit dem PropertyChanged - Event:

private void SetValue<T>(ref T field, T value, 
	[CallerMemberName] string propertyName = null)
{
    if (field.Equals(value))
    {
        return;
    }

    field = value;
    OnPropertyChanged(propertyName);
}

Erneut nutzen wir das CallerMemberName-Attribut und ermitteln den Aufrufer. Dieser wird dann an die OnPropertyChanged-Überladung aus dem "OnPropertyChanged 2.0"-Beispiel übergeben. Aber wie dort schon gezeigt, ist es oftmals erforderlich die Eigenschaft, für die das PropertyChanged-Event ausgelöst wird, explizit anzugeben. Daher überladen wir die SetValue-Methode mit einem Expression-Objekt anstatt dem string-Paramter:

private void SetValue<T>(ref T field, T value, Expression<Func<T>> propertyExpression)
{
    if (field.Equals(value))
    {
        return;
    }

    field = value;
    OnPropertyChanged(propertyExpression);
}

Nun existiert die Logik zur Überprüfung in beiden Methoden, wir refaktorisieren das Ganze und extrahieren daraus eine Methode. Die Methoden zum gleichzeitigen Auslösen des Events werden mit dem Suffix AndNotify-versehen. Zum einen würde der Name SetValue nicht die tatsächliche Aktion beschreiben und weiterhin würden wir so Probleme bekommen, da wir dann nicht mehr an die Überladung mit dem optionalen string-Parameter kommen würden. Damit erhalten wir folgende drei Methoden:

private void SetValue<T>(ref T field, T value)
{
    if (field.Equals(value))
    {
        return;
    }

    field = value;
}

private void SetValueAndNotify<T>(ref T field, T value, 
	Expression<Func<T>> propertyExpression)
{
    SetValue(ref field, value);
    OnPropertyChanged(propertyExpression);
}

private void SetValueAndNotify<T>(ref T field, T value, 
	[CallerMemberName] string propertyName = null)
{
    SetValue(ref field, value);
    OnPropertyChanged(propertyName);
}

Diese können wir nun nutzen um Werte in Eigenschaften mit Überprüfung zu setzen und gleichzeitig das PropertyChanged-Event auszulösen:

public int X
{
    get { return _x; }
    set
    {
        SetValueAndNotify(ref _x, value);
        OnPropertyChanged(() => this.Y);
    }
}

Gemeinsame Basisklasse

Da es für gewöhnlich ja mehr als eine Entitätsklasse in einem Projekt gibt, wäre die Duplizierung der o.g. Methoden mal wieder ein Verstoß gegen das DRY-Prinzip. Daher können die Methoden in eine gemeinsame Basisklasse ausgelagert werden. Da Entitäten normalerweise keine Basisklassen verwenden, kann dieses Verfahren in nahezu jedem Projekt eingesetzt werden:

internal class AbstractEntityBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var del = PropertyChanged;
        if (del != null)
        {
            del(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyExpression)
    {
        var body = propertyExpression.Body as MemberExpression;
        var expression = body.Expression as ConstantExpression;
        OnPropertyChanged(body.Member.Name);
    }

    private void SetValue<T>(ref T field, T value)
    {
        if (field.Equals(value))
        {
            return;
        }

        field = value;
    }

    protected void SetValueAndNotify<T>(ref T field, T value, Expression<Func<T>> propertyExpression)
    {
        SetValue(ref field, value);
        OnPropertyChanged(propertyExpression);
    }

    protected void SetValueAndNotify<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        SetValue(ref field, value);
        OnPropertyChanged(propertyName);
    }
}

Da diese Basisklasse nur Abhängigkeiten zum Framework selbst enthält, bietet es sich an, die Klasse in eine Bibliothek auszulagern, die in anderen Projekten wiederverwendet werden kann.

Zusammenfassung

Das stupide Setzen von zugewiesenen Werten in Eigenschaften sollte aus Performancegründen unterlassen werden und stattdessen eine Überprüfung durchgeführt werden, ob der gesetzte Werte sich überhaupt von dem Wert im zugrundeliegenden Feld unterscheidet. Diese Überprüfung und die eigentliche Zuweisung verstoßen gegen das DRY-Prinzip und sollten nach Möglichkeit ausgelagert werden. Wird dieser Aufwand eh betrieben, kann auf diesem Wege auch direkt der Benachrichtigungsmodus mit eingebunden werden. Gibt es mehr als eine Entitätsklasse, greift auch hier wieder das DRY-Prinzip und die beschriebenen Methoden können in eine gemeinsame Basisklasse ausgelagert werden.

Softwarearchitektur: Komponenten Teil 1 - Was wir von anderen Industrien lernen können.

imageWenn man erfahrene Entwickler fragt, wie man große und Komplexe Softwareprojekte umsetzt, bekommt man meistens zwei Dinge zu hören: Schichten und Komponenten. Beiden ist gemeinsam, dass sie dazu dienen, innerhalb meines Softwaresystems klare Grenzen zwischen Funktionalitäten zu ziehen und sie in Module aufzuteilen mit all den einhergehenden Vorteilen wie besserer Wiederverwendbarkeit, Wartbarkeit, Testbarkeit und – vor allem – der besseren Verteilbarkeit auf einzelne Entwickler.

Herr Ober da liegt ein Haar neben meiner Suppe…

Schaut man über den Tellerrand hinaus in andere Ingenieursbereiche erkennt man, dass dort identische Probleme existieren – und gelöst wurden: durch Modularität. Modular ist etwas, was in sich eine geschlossene Einheit bildet und über verschiedene definierte Schnittstelle angesprochen wird und seinerseits die Fähigkeiten anderer Module ebenfalls über definierte Schnittstellen nutzt. Für Benutzer eines Moduls ist deren innerer Aufbau meist vollkommen unwichtig. Betrachtet man das Modul einer Backstraße zum backen von Brot, so ist klar: Es benötigt Mehl, Hefe und Wasser und produziert Brot, mehr muss ich von Außen nicht wissen um damit Brot herzustellen. Angeschlossen an ein Mehlsilo, ein Hefesilo und eine Wasserleitung kann es also los gehen. Klar wachsen Backstraßen nicht auf Bäumen, sie müssen von irgendjemandem gebaut werden. Bei dem Bau solch eines Systems, schauen wir uns die Lösung zu zwei Problemen an, die wir auch in der Softwareentwicklung haben:

  1. Wie und wo fangen wir bei großen Systemen an zu entwickeln?
  2. Wie können wir die Arbeit auf mehrere Personen aufteilen?

Aus dem Bereich der Mathematik und der Informatik kennt man die Taktik des “Divide And Conquer” (Teile und herrsche), welches besagt das man ein großes Problem in viele kleinere Probleme unterteilt. Das macht man so lange bis alle kleinen Probleme lösen werden können und somit das Gesamtproblem gelöst ist.

Fangen wir also an unser “Problem” aufzuteilen: Eine Backstraße besteht (vermutlich, Leser der Backindustrie mögen mich bitte korrigieren) aus den Untermodulen Teigverarbeitung, Teigherstellung und Backofen wobei in das Modul Teigverarbeitung die Eingabe von Mehl, Hefe und Wasser erhält.

image

Wie dabei dieser Teig erzeugt wird, wie die Rohlinge geformt werden oder wie gebacken wird – das interessiert den Benutzer von Außen nicht. Hauptsache an den Schnittstellen steht das definierte Ergebnis mit den definierten Eigenschaften bereit.

Nun ist so eine Teigherstellungsmaschine (die in der Backstraße enthalten ist) auch kein einfaches Modul und so ist es ebenfalls aus Untermodulen zusammengesetzt, die – ihr werdet es nicht glauben – über fest definierte Schnittstellen miteinander kommunizieren (gähn…)

image

Mangels Maschinenbaustudium vermute ich das solch ein Modul aus einem Mixer (zum vermengen der Zutaten) und einem Kneter (der alles zu einem Teig knetet) bestehen wird. Dieses Spiel könnte man nun so lange treiben, bis man bei Schrauben und Kabeln der einzelnen Module angekommen ist.

image

Haben wir den überschaubaren Level der Schrauben und Kabel erreicht, kann losgebaut werden. Die Module auf unterster Ebene können auf die Ingenieure verteilt werden, da sie ja keine anderen Komponenten brauchen um fertiggestellt zu werden. Aber auch an den Modulen der oberen Schichten kann gebaut werden (Halterungen, Rohre, Kabel, usw.) weil deren Arbeit ebenfalls unabhängig ist – auch von Untermodulen. Denn deren Ausmaße und Schnittstellen sind je genau definiert – die Ingenieure müssen dies nur beim Bau berücksichtigen. Wurde unsere Beispiel sauber durchspezifiziert, kann an 6 Stellen losgebaut werden.

Und wie man sich die Brötchen, die aus der Backstraße kommen, mit der Softwareentwicklung verdienen kann, indem man sich genau das Prinzip zu nutze macht, schauen wir uns im nächsten Teil an… Weil jetzt habe ich Hunger!