Specification Pattern

Seitdem ich die praktische Anwendung des von Eric Evans in Domain-Driven Design beschriebenen Spezifikationsmusters in Jimmy Nilsons Buch gesehen habe, bin ich ein großer Fan geworden.

SPECIFICATION provides a concise way of expressing certain kinds of rules, extricating them from conditional logic and making them explicit in the model.

Genau dasselbe Prinzip habe ich in diesem Artikel angewendet und ich nutze dieser Vorgehensweise laufend.

  • Wann braucht man eine Spezifikation?
    Immer, wenn ein Domain-Objekt bestimmte Erwartungen erfüllen muss, zum Beispiel beim Validieren (wie in o.g. Artikel) oder bei der Suche anhand von bestimmten Kriterien.
  • namespace Specification
    {
        public interface IValidable{}
    
        public class Invoice : IValidable{}
    
        public interface ISpecification
        {
            bool IsSatisfiedBy(IValidable obj);
        }
    
        public class BlackListClientsSpecification : ISpecification
        {
            private readonly DateTime m_currentDate;
    
            public DelinquentClientsSpecification(DateTime currentDate)
            {
                m_currentDate = currentDate;
            }
    
            public bool IsSatisfiedBy(IValidable obj)
            {
                Client candidate = obj as Client;
                //do something with Candidate and CurrentDate
                return true;
            }
        }
    }
    //Selektion:
    ...
        public class ClientRepository
        {
            IList<Client> SelectSatisfying(ISpecification specification)
            {
                return SelectAllClients().Where(a => specification.IsSatisfiedBy(a));
            }
        }
    ...
        var delinquentClients = clientRepository.SelectSatisfying(new DelinquentClientSpecification(currentDate));
    ...
    
  • Wozu braucht man eine Spezifikation?
    Ich kenne das aus eigener Erfahrung, wie sich Codeschnippsel (zum Beispiel obj.State==valid oder obj.Activ==true && obj.Created usw.), die verschiedenen Kriterien darstellen, wie Unkraut überall in Code verbreiten. Das kann man mit dieser Lösung wunderbar unterbinden, alle Bedingungen müssen in die jeweils passende Spezifikation gesammelt werden.

Man kann als Basis ein Interface, ( ISpecification ) oder eine abstrakte, an das beschriebene Domain Objekt angepasste Klasse InvoiceSpecification nutzen, abhängig davon, ob man nur einen Kontrakt oder auch Code wiederverwenden will.
Aus der Sicht des Testens, sind natürlich diese Klassen ideal, sie haben keine Abhängigkeiten, sind pure logische Ausdrücke der Entscheidungen z.B. der Geschäftsleitung. Für diese Klassen wurde TDD erfunden ;).

Coding Dojo – aber wie?

Wir haben in der Abteilung vor ein paar Wochen eingeführt, am letzten Tag von jedem Sprint einen kleinen Firmeninternen Dojo abzuhalten. Da wir vom Anfang an zu den Münchener Coding Dojos gehen, haben dadurch ziemlich viel Erfahrung gesammelt, wie so ein Dojo durchgeführt wird.

Wie laufen üblicherweise diese Veranstaltungen ab? Der Organisator beschreibt die zur Auswahl stehenden Aufgaben, die Teilnehmer besprechen es kurz, und nachdem die Wahl getroffen wurde, wird fast sofort losgelegt. An der Tastatur. Der erste Test entsteht dann allerdings erst nach 30-40 Minuten. Geplant wird mit einem zuckenden Finger über die Tastatur, der Test entsteht oft nach einem frustrierten Ruf “schreib endlich was, egal was!”. Am Ende jedes Dojos hatten wir dann die Anforderungen erfüllt, aber zur Refactoring kamen wir nie.

Aber wir sind Entwickler, wir entwickeln uns ständig weiter. Seit längerer Zeit spürten wir, dass wir so einen Prozess anders steuern sollten, dass etwas fehlt.

An diesem Freitag haben wir also etwas anderes ausprobiert, sozusagen à la Clean Code: zuerst Anforderungen aufschreiben (das war ja nichts neues), dann nachdenken. Ohne Tastatur, nur am Flipchart. Das war neu. Wir haben die ganze Planung inklusive Funktionseinheiten, Methodennamen, Abhängigkeiten aufgezeichnet.
Danach ging erst das Entwickeln mit TDD los. Nach ca. 90 Minuten waren wir fast fertig und wir haben keine Refactoring mehr gebraucht.

Auf die Frage nach der Meinung den Kollegen, ob das jetzt besser gelaufen war, waren die Antworten geteilt: manche meinten, wir wären auch auf dem anderen Weg zum selben Ergebnis gekommen. Die Wahrheit ist, das Beispiel war recht einfach, es gab nicht sehr viele trennbare Verantwortlichkeiten, aber ich bin sicher, dass man auf dieser Art viel mehr lernen kann. Es reicht ja nicht, eine Sprache zu kennen, ein guter Entwickler muss auch Abstraktionsebenen erkennen und definieren können. Ich finde diese Art zu Arbeiten: zuerst alle Möglichkeiten auf einem Stück Papier zu bewerten und dann sich auf die einzelne Methoden zu konzentrieren, viel effektiver, ich glaube fest daran, dass es zu besserem Code führt.

Also die Frage ist: wie soll eine mit TDD entwickelte Anwendung entstehen? Vom Anfang mit der Tastatur unter den Fingern oder darf es etwas Planung in den Köpfen und auf Papier stattfinden? Haben wir die Regeln von TDD verletzt oder verbessert?

SOLID Principles

Im vorherigen Artikel habe ich drei der fünf wichtigsten Prinzipien des OOD (Objektorientiertes Design) benannt, ohne mehr darüber zu schreiben. Das würde ich jetzt gerne nachholen.
Diese Prinzipien wurden von Robert C.Martin (a.k.a. Uncle Bob) unter dem Namen S.O.L.I.D. Principles zusammengefasst:

  • SRP: The Single Responsibility Principle
  • OCP: The Open Closed Principle
  • LSP: The Liskov Substitution Principle
  • DIP: The Dependency Inversion Principle
  • ISP: The Interface Segregation Principle

Das Thema wurde von Uncle Bob in mehreren Blogartikeln, Podcasts und vor allem in seinem Buch sehr ausführlich erklärt. Auch andere Entwickler haben darüber geschrieben, zum Beispiel hat Stefan Lieser darüber eine ganze Artikelserie in dotnetpro veröffentlicht.
Ich habe das erste Mal vor einem Jahr in diesem Podcast von Hanselman und Uncle Bob darüber gehört, und während der letzten 12 Monaten wurde ich durch die tägliche Arbeit überzeugt, dass man mit diesen Regeln solide Anwendungen bauen kann.

Was bedeuten also diese Akronyme:

SRP: The Single Responsibility Principle

Die Definition lautet:

A class should have only one reason to change.

Diese Regel ist wahrscheinlich die einfachste und wird wahrscheinlich am meisten verletzt. Wer kennt nicht Klassen, die sowas tun, wie Daten speichern, manipulieren, E-Mails versenden und all das eventuell auch noch loggen. Das war früher eine ganz normale Vorgehensweise. Was passiert aber, wenn die Datenbank-Struktur sich verändert hat? Dann musste man nicht nur diese ändern sondern auch all die Klassen – meistens Verwaltungen oder Manager genannt – die all diese Verantwortlichkeiten hatten. Und darum geht es hier: eine Klasse darf nur einen Grund für Änderungen haben, sie darf nur eine Verantwortlichkeit haben. Also wenn man mehrere Gründe erkennen kann, warum sich eine Klasse verändert, dann wurde dieses Prinzip verletzt. Und das gilt nicht nur für Klassen, sondern auch für andere Funktionseinheiten wie Funktionen, Klassen, Assemblies, Komponenten, alle auf ihre Abstraktionsebene betrachtet.

OCP: The Open Closed Principle

Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

Jede Funktionseinheit soll erweiterbar sein, also darf nicht zu viele Abhängigkeiten haben, weil die diese Freiheit stark oder ganz einschränken können. Wenn man allerdings ein verändertes Verhalten implementieren will, soll das nicht durch Veränderung des Codes sondern durch hinzufügen von neuen Funktionen passieren.
Das ist nur durch ausreichende Abstraktion zu erreichen. Wenn die Kernfunktionalität in eine abstrakte Basisklasse gekapselt ist, kann man das neue Verhalten in einer abgeleiteten Klasse implementieren.

LSP: The Liskov Substitution Principle

Dieses Prinzip wurde von Barbara Liskov definiert und es lautet so:

Subtypes must be substitutable for their base types.

Einfach übersetzt: jede abgeleitete Klasse einer Basisklasse muss diese Klasse so implementieren, dass sie durch diese jeder Zeit ersetzbar ist. Jedes Mal, wenn man eine Basisfunktion so implementiert, dass diese was ganz anderes tut, als man grundsätzlich erwarten würde, verletzt man dieses Prinzip. Das berühmteste Beispiel ist das Rechteck und das Quadrat. Auf den ersten Blick meint man, dass ein Quadrat ein spezialisiertes Rechteck ist. Was passiert aber, wenn man die Länge oder die Breite eines Quadrates setzt? Es muss jeweils die andere Eigenschaft auch gesetzt werden, sonst wäre es ja kein Quadrat ;). Das ist aber ein Verhalten, was man bei einem Rechteck niemals erwarten würde. Also würde diese Ableitung das Liskovsche Substitutionsprinzip grob verletzen.

DIP: The Dependency Inversion Principle

Die Definition lautet

1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
2. Abstractions should not depend upon details. Details should depend upon abstractions

Dieses Prinzip ist sehr einfach zu erklären (Beispiel für die Verletzung sieht man ja im vorherigen Artikel): Keine Klasse sollte fremde Klassen instanziieren, sondern diese als Abstraktionen (z.B. Interfaces) in Form eines Parameters bekommen. Das führt dazu, dass die fremde Klasse als Black Box fungieren kann, ihre Veränderungen würden nicht zu Veränderung dieser konkreten Klasse führen.

ISP: The Interface Segregation Principle

Das Prinzip bezieht sich auf “fette” Interfaces:

Clients should not be forced to depend on methods they do not use.

Ein Interface ist der veröffentlichte Kontrakt einer Klasse, eines Moduls. Je mehr Methoden darin registriert wurden, vor allem, wenn sie sehr ähnlich sind oder wenn sie keine selbsterklärende Namen haben, dann ist das eine Zumutung gegenüber der Clients, des Verwenders. Er könnte dazu gezwungen sein, den Code der dahinter stehenden Implementierung anzusehen, was die Erstellung des Interfaces sinnlos macht. Dieses soll ja als Black Box fungieren, nicht als eine Herausforderung für den Entwickler.