Training - Beratung - Projektarbeiten

www.David-Tielke.de

[Webcast] Softwarequalität Teil 2 - Prozessqualität

Es ist mal wieder Webcast-Zeit. Nachdem wir uns im ersten Teil die Grundlagen zum Thema Softwarequalität angeschaut haben, widmen wir uns im zweiten Teil der Prozessqualität. Wie sollte also ein guter Softwareentwicklungsprozess aussehen und wie sollte er nicht aussehen? Worauf muss geachtet werden und was solltet Ihr machen oder auch besser die Finger davon lassen? All diese Fragen beschäftigen uns im zweiten Teil zum Thema Softwarequalität. Viel Spaß damit!

Webcast: Logging in .NET-Anwendungen

Im Rahmen eines Webinars der Developer Media zum Thema “Softwarequalität für Fortgeschrittene”, hatte ich den Themenplan etwas zu optimistisch verfasst und so musste ein wichtiges Thema leider ausfallen: Logging. Wie versprochen reiche ich das Thema nun als Webcasts nach.

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.