Follow my new blog

Donnerstag, 26. Februar 2009

Code bubbling - Von der Software zur Schaumware

Was passiert mit Code unter Druck? Er wandert ins UI. Das ist eine Erfahrung, die ich gerade in der letzten Zeit immer wieder gemacht habe. Wenn ich mir Code in Projekten oder auch in Übungsaufgaben ansehe, dann findet der sich in umso größerer Menge im UI oder UI-nah, je höher der (Termin)Druck im Projekt ist. Wie Bläschen in der Limo wandert er nach oben in die Präsentationsschicht (wenn man in Schichten denken will).

Ist ja auch klar: Wo keine Zeit ist, da fehlt Muße zum Denken. Ohne Denken, keine Planung. Ohne Planung, keine Vorstellung darüber, wie Code organisiert sein könnte.

Die einzige Struktur die zwangsweise jedoch immer vorhanden ist, ist die Struktur des UI. In Ermangelung von Alternativen trägt man also Code in die durch das UI vorgegebenen Strukturelemente (Steuerelemente) ein.

Das UI wird damit quasi zur Blume der Software: Dort schäumt der ganze Code, für den man in Ermangelung anderer Strukturelemente grad keine Zeit hatte, einen geeigneteren Ort zu finden.

Aber wie mit dem Schaum im Glas ist es schwer, den Code im UI in tiefere Schichten zu drücken. Ist er erstmal dort oben, dann bleibt er dort auch. Der Schau auf dem Bier verschwindet irgendwann zwar von allein, die Codeblasen im UI jedoch nicht. Und je länger der Druck im Projekt anhält, desto mehr Code, der eigentlich tiefer angesiedelt sein sollte, blubbert nach oben.

So ist es denn kein Wunder, dass manche Software eigentlich Schaumware (neudeutsch: foamware) ist.

Schade . Denn zumindest minimale Strukturen unterhalb des UI zu finden, in und an denen Code auch unter Druck geeigneter und vor allem leichter testbar aufgehängt werden kann, ist gar nicht so schwer. Sogar mit ein bisschen "mechanischem" Separation of Concerns ist da schon einiges zu erreichen. Denn eines ist gewiss: Code in einem Eventhandler eines Steuerelementes ist nie eine gute Sache, wenn der Code nicht unmittelbar mit dem Steuerelement oder seinen "Geschwistern" im UI zu tun hat.

Prost!

Samstag, 14. Februar 2009

Blaues Wunder VSone 09

Das war sie wieder, die VSone 2009, die Frühjahrsentwicklerveranstaltung von ppedv. In München, am gewohnten Ort im Forum des Deutschen Museums - aber mit viel mehr Teilnehmern als in den letzten Jahren! Knapp 500 sollen gekommen sein. Es war eine gemütliche Atmosphäre in der Kinolobby unter dem ehemaligen IMAX-Kino.

Das Vortrags- und Workshop-Programm war gewohnt vielfältig, die Sprecherriege gemischt. Aus dem fernen Seattle war aber sogar Chris Sells angereist, um Microsofts Oslo vorzustellen.

Wer allerdings geglaubt hatte, zwei Tage lang eine ruhige Kugel bei Technologievorträgen schieben zu können, der erlebte sein blaues Wunder! Am Abend des ersten Konferenztages gab es kein Halten mehr: die Karneval-Narren waren los. Ja, sogar in München. Neno Loje, mich, Jürgen Kotz und Christian Wenz traf es wie alle anderen Teilnehmer. Auf der Abendveranstaltung galt "Hutpflicht":

image

Entschädigt für diese "Verunstaltung" unserer Charakterköpfe wurden wir dann jedoch zum Glück durch schmissige Darbietungen der Tanzgruppe des Karnevalsvereins Dorfen. Soviele Frauen waren bisher selten zu sehen auf Entwicklerveranstaltungen ;-)

image

Diesermaßen motiviert, waren die restlichen zwei Tage dann eine Kleinigkeit. Die Teilnehmer konnten sogar meinen Vortrag über die Concurrency Coordination Runtime (CCR) ohne Powerpoints ertragen und waren am Freitag von 9h bis 17h superaufmerksam in meinem Workshop zum Thema Anwendungsarchitektur und Parallelprogrammierung. Als im Workshop dann auch noch die Premiere des Xcoordination Application Space tadellos lief, war ich endgültig überzeugt von der VSone als gelungener "Kessel Buntes" Veranstaltung für .NET Entwickler und Sharepoint-Profis.

image Beispielcode und Flipchart-Fotos des Workshops können hier heruntergeladen werden: http://www.ralfw.de/download/VSone09Workshop.zip. Es ist auch eine Vorabversion des Application Space dabei. Dessen Zweck ist es, Anwendungen sehr, sehr einfach von synchronen lokalen Komponenten über asynchrone lokale bis zu verteilten asynchronen Komponenten zu skalieren. Der Application Space ist Architekturinfrastruktur, die es wesentlich einfacher machen soll, in die Multi-Core- und Multi-Tier Programmierung einzusteigen. Aber davon ein andermal mehr.

Samstag, 7. Februar 2009

Semantische Kontrakte mittels Tests definieren

Im Kern komponentenorientierter Softwareentwicklung steht die Entkopplung von Codeeinheiten durch separate Kontrakte. Die Definition, was eine Codeeinheit tun soll, ist zu trennen von den möglicherweise vielen Implementationen dieser Definition. Typischerweise werden Kontrakte als Interfaces und Implemenationen als Klassen realisiert:

Kontrakt:

public interface ITaschenrechnerRechenwerk
{
    int Add(int a, int b);
   ...

Implementation:

public class TaschenrechnerRechenwerk : ITaschenrechnerRechenwerk
{
    public int Add(int a, int b) {...}
    ...

Gerade wenn solche Kontrakte in einer eigenen Assembly unabhängig von jeder Implementation vorliegen, lassen sie sich wunderbar an Dienstleister in nah und fern versenden. Der Dienstleister muss in seiner Implementation nur die Kontraktassembly referenzieren und das Kontrakt-Interface implementieren. Fertig ist die kontraktkonforme Realisation. Wenn Sie so eine Realisation geliefert bekommen, können Sie sicher sein, dass sie zum Rest Ihrer Anwendung passt, die sich auf den Kontrakt verlässt.

Oder? Ist es wirklich so einfach?

Syntaktischer Kontrakt

Formal ja. Durch das unveränderliche Interface, dass der Dienstleister nur implementieren muss, ist sichergestellt, dass seine Realisation kompatibel ist. Allerdings ist sie nur syntaktisch kompatibel. Eine Client-Klasse kann ohne Kenntnis dieser konkreten Realisierung implementiert werden:

Client:

public class TaschenrechnerClient
{
    public void Run(ITaschenrechnerRechenwerk tr)
    {
        double r = tr.Add(2,3);
        ...

}

ITaschenrechnerRechenwerk tr = ...;
TaschenrechnerClient tc = new TaschenrechnerClient();
tc.Run(tr);

image Ob tr eine Instanz von TaschenrechnerRechenwerk oder XYZ zugewiesen bekommt, ist TaschenrechnerClient egal. Das Interface stellt sicher, dass Client und Service-Implementation dieselbe "Sprache sprechen", dass sie zueinander passen. Mit einem Interface sorgen Sie nur dafür, dass eine Implementation "runde Hölzer" für "runde Löcher" bereitstellt. Das Interface beschreibt die Form einer Stecker-Steckdose-Kombination.

Semantischer Kontrakt

Was leistet aber ein Service, der die syntaktische Defintion erfüllt? Nur, weil ein Stecker in die Steckdose passt, heißt das noch lange nicht, dass an der Steckdose auch eine geeignete Spannung anliegt. Ein Service muss nicht nur so aussehen wie gewünscht, sondern auch leisten, was erwartet wird. Zu einer Komponente gehört deshalb nicht nur ein syntaktischer Kontrakt, sondern auch ein semantischer.

Der semantische Kontrakt baut auf dem syntaktischen auf und beschreibt die Erwartungen an den Service. Eine ITaschenrechnerRechenwerk-Implementation soll nicht nur eine Methode mit Namen Add(), double-Resultat und zwei double-Parametern bieten, sondern eben auch tun, was man von einer Methode mit Namen "Add" erwartet.

imageWie aber lassen Sie den Dienstleister wissen, was sie von einer Kontraktimplementation in semantischer Hinsicht erwarten? Trotz aller Bemühungen um formale Spezifikationen haben wir noch kein Mittel, um semantische Kontrakte so einfach wie syntaktische zu beschreiben. Sie werden also nicht umhin kommen, der Kontrakt-Assembly mit dem syntaktischen Kontrakt noch eine Beschreibung der Semantik beizulegen. Das könnten Sie in Form von Kommentaren in den Sourcen des syntaktischen Kontrakts tun oder mit einem separaten PDF-Dokument.

Und dann? Wenn Sie eine Implementation vom Dienstleister bekommen, was tun Sie dann? Wie stellen Sie sicher, dass die nicht nur den syntaktischen, sondern auch den semantischen Kontrakt erfüllt? Das müssen Sie testen. Oder Sie verlassen sich darauf, dass der Dienstleister es genügend getestet hat. Vielleicht liefert er ja einen Unit Test Testbericht mit und auch noch eine Angabe zur Codecoverrage des Tests.

Reicht das aber? Nein. Denn das sagt nur aus, dass der Code des Dienstleisters den Tests des Dienstleisters genügt. Darauf dürfen Sie sich nicht verlassen. Vielleicht hat der Dienstleister ja Ihre Beschreibung des semantischen Kontrakts falsch verstanden. Dann hat er wunderbare Tests gebaut, die alle fehlerfrei laufen - aber leiden testen sie das Falsche.

Tests als semantische Kontrakte

Wenn Sie sich wirklich auf einen Dienstleister verlassen wollen - der kann ja sogar im eigenen Team sitzen -, dann statten Sie ihn nicht nur mit einem unzweideutigen syntaktischen Kontrakt aus, den er nicht mehr interpretieren, sondern nur noch implementieren muss, sondern auch mit einem solchen semantischen. Auch ohne spezielle Spezifikationssprache geht das: mit Unit Tests. Es ist ganz einfach.

Als erstes definieren Sie weiterhin Ihren syntaktischen Kontrakt (s.o.).

Dann aber schreiben Sie keine länglichen Dokumente, um zu erklären, was eine Implementation können soll. Stattdessen schreiben Sie sofort Unit Tests, die alle Aspekte der Kontraktsemantik prüfen. Genau: Sie schreiben Unit Tests, ohne eine Implementation zu haben.  Das sollte kein Problem sein, denn zu einem syntaktischen Kontrakt, den Sie ersonnen haben, sollten Sie auch Erwartungen formulieren können, wie er sich zur Laufzeit verhält, wenn Sie ein seiner Implementationen mit diesen oder jenen Parametern aufrufen. Für den obigen Kontrakt kann das so aussehen:

Semantischer Kontrakt:

[TestFixture]
public class ExerciseITaschenrechnerRechenwerk
{
    ...

    [Test]
    public void Check_add()
    {
        ITaschenrechnerRechenwerk tr;
        ...

        Assert.AreEqual(5, tr.Add(2, 3));
    }
    ...

Ein ganz normaler Unit Test für eine Implementation von ITaschenrechnerRechenwerk. Der syntaktische Kontrakt ist durch das Interface vorgegeben, der semantische durch das Assert(): Wenn eine Funktion Add() vorhanden ist (Syntax), dann erwarten Sie, dass sie für die Parameter 2 und 3 das Ergebnis 5 zurückliefert. Das ist unzweideutig. Darüber muss kein Dienstleister mehr mit Ihnen diskutieren. Seine Implementation muss nur diese semantische Anforderung erfüllen. Punkt. Wenn er nicht versteht, was "Add" bedeutet, dann soll er halt bei Ihnen nachfragen. Ob sein Verständnis am Ende zu korrektem Code im Sinne Ihres Kontraktes geführt hat, beweist ein erfolgreicher Test.

Semantische Kontrakte ohne Implementation definieren

Klingt plausibel, oder? Wie können Sie nun aber vor jeder Implementation den semantischen Kontrakt formulieren? Mit Mock-Objekten. Ob obigen semantischen Kontrakt steht hinter tr keine "richtige" Implementation, sondern nur ein Mock-Objekt, dass die im Assert() formulierte Erwartung erfüllt. Nicht mehr, nicht weniger. Das kann mit Rhino Mock zum Beispiel so aussehen:

MockRepository mocks = new MockRepository();
tr = mocks.StrictMock<ITaschenrechnerRechenwerk>();
Expect.Call(tr.Add(2,3)).Return(5);
mocks.ReplayAll();

Ohne Frage funktioniert das bei Ihnen, wenn Sie den semantischen Kontrakt formulieren. Den Code können Sie auch dem Dienstleister geben - aber in Bezug auf dessen Implementation des Kontrakts ist er nutzlos. Er ist genauso passiv wie ein PDF-Dokument. Der Dienstleister kann ihn nur lesen, aber nicht auf seine Implementation anwenden.

Das Problem ist, dass Sie tr fest ein Mock-Objekt zuweisen. Wie soll das der Dienstleister durch seine Implementation des syntaktischen Kontrakts ersetzen? Denn nur dann kann schon er vor Auslieferung an Sie seine Implementation auch gegen den semantischen Kontrakt testen.

Als Lösung bietet sich eine kleine Factory-Methode an:

[TestFixture]
public class SemanticContract_ITaschenrechnerRechenwerk
{
    protected virtual
             ITaschenrechnerRechenwerk
             CreateInstanceToExercise(Func<ITaschenrechnerRechenwerk> factory)
    {
        return factory();
    }

    [Test]
    public void Check_add()
    {
        ITaschenrechnerRechenwerk tr;
        tr = CreateInstanceToExercise(delegate
                {
                    MockRepository mocks = new MockRepository();
                    tr = mocks.StrictMock<ITaschenrechnerRechenwerk>();
                    Expect.Call(tr.Add(2,3)).Return(5);
                    mocks.ReplayAll();
                    return tr;
                });

        Assert.AreEqual(5, tr.Add(2, 3));
    }
    ...

Die Factory-Methode liefert eine Instanz des zu prüfenden Kontrakts zurück. In Abwesenheit einer realen Implementation bekommt sie die als Parameter in Form einer weiteren Factory-Methode geliefert. Der anonyme Delegat erzeugt das Mock-Objekt, das bei Ihnen den semantischen Kontrakt garantiert erfüllt.

Wenn Sie eine Testklasse in dieser Weise formulieren, läuft sie wunderbar in Ihrem Testrunner (z.B. ReSharper). Ganz ohne eine echte Kontraktimplementation. Und sie können sie mit dem syntaktischen Kontrakt als separate Assembly an den Dienstleister schicken. Ob Sie dann noch zusätzlich Kommentare in den Kontrakt-Quellen oder ein PDF brauchen, müssen Sie beurteilen. Jedenfalls müssen Sie sich nicht mehr auf guten Glauben verlassen, ob der Dienstleister solche Dokumentation verstanden hat. Er muss nur darlegen, dass seine Implementation erfolgreich mit dem semantischen Kontrakt geprüft wurde.

Semantische Kontrakte gegen Implementation prüfen

Und wie prüft der Dienstleister seine Implementation gegen den semantischen Kontrakt? Oder wie prüfen Sie eine Implementation dagegen? Das ist ganz einfach: Sie müssen dem semantischen Kontrakt zur Laufzeit nur eine echte Implementation unterschieben statt der default Mock-Objekte. Dazu genügt aber die Ableitung einer neuen Testklasse von der des semantischen Kontrakts:

[TestFixture]
public class CheckTaschenrechnerRechenwerk
                             : SemanticContract_ITaschenrechnerRechenwerk
{
    protected override
             ITaschenrechnerRechenwerk
             CreateInstanceToExercise(Func<ITaschenrechnerRechenwerk> factory)
    {
        return new Dienstleister.TaschenrechnerRechenwerk();
    }
}

Diese Klasse kann der Dienstleister mit seinem Testrunner genauso ausführen wie Sie Ihre Testklasse des semantischen Kontrakts. Nur wird jetzt nicht gegen die Mock-Objekte geprüft, die die factory-Funktion erzeugen würde, sondern gegen die wahre Implementation. Bei diesem Akzeptanztest wird die factory-Funktion aus den Testmethoden einfach nicht berücksichtigt.

Zusammenfassung

Mehr ist nicht nötig für vollständige Kontraktdefinitionen für Komponenten:

  1. Definieren Sie einen syntaktischen Kontrakt in Form von Interfaces und/oder (abstrakten) Klassen in einer eigenen Assembly.
  2. Definieren Sie zum syntaktischen Kontrakt einen semantischen Kontrakt in Form von Tests in einer eigenen Assembly.
  3. Geben Sie dem Dienstleister für die Realisierung der Komponente sowohl den syntaktischen wie auch den semantischen Kontrakt und verlangen Sie, dass er nur an Sie ausliefert, wenn auch der semantische Kontrakt bei ihm fehlerfrei ausgeführt wird.

Da Sie zum Zeitpunkt der Definition des semantischen Kontrakts keine Implementation vorliegen haben, benutzen Sie Mock-Objekte, um Ihre Erwartungen zu erfüllen. Der Dienstleister muss diese Mock-Objekte zur Überprüfung seiner Implementation dann nur leicht ersetzen können. Mit einer abstrakten Factory-Methode wie oben gezeigt, ist das aber leicht.

So entwickeln Sie zwar noch nicht wirklich Test-driven, aber zumindest Test-first innerhalb von Contract-first: vor jede Implementation setzen Sie einen syntaktischen und (!) einen semantischen Kontrakt.