Follow my new blog

Montag, 4. Januar 2010

Aspekttrennung im GUI

Mich wundert, dass es bisher noch niemand bemerkt zu haben scheint: Der WinForms-Designer (und auch der WPF-Designer) vermischt zwei Aspekte bei der GUI-Programmierung. Das sind die Aspekte Gestaltung und Funktionalität. Dass er das tut, finden wir natürlich alle irgendwie bequem. Doch mich beschleicht das Gefühl, dass hier zuviel des Guten getan wird.

Ein Beispiel ein kleines Formular zum Addieren zweier Zahlen:

image

Da steckt etwas Gestaltung drin –  Steuerelemente wollten ausgewählt und angeordnet werden – und Funktionalität – Ereignisbehandlungsroutinen wollten hinter die Steuerelemente gestellt werden.

Die Programmierung war ganz einfach: Steuerelemente auf das Formular ziehen (Gestaltung), Doppelklick auf ein Steuerelement, um einen Eventhandler zu schreiben (Funktionalität). So weit, so gut. Das möchte ich kaum anders haben.

Wenn ich genau hinschaue, stört mich aber das Ergebnis dieser einfachen Programmierung im Code. Das sieht nämlich so aus:

public partial class WinDoTheMath : Form

{

    public WinDoTheMath()

    {

        InitializeComponent();

    }

 

    private void button1_Click(object sender, EventArgs e)

    {…}

 

    private void textBox1_Validating(object sender, CancelEventArgs e)

    {…}

}

Ganz normal – aber subtil verstörend. Denn was mir hier fehlt, das ist eine Zuordnung der Ereignisbehandlungsroutinen. Die “stehen im Code nur so rum”. Ich muss mir anhand der im Methodennamen steckenden Hinweise zusammenreimen, wie/wann sie zum Einsatz kommen.

Das finde ich inzwischen irgendwie umständlich – oder anders ausgedrückt: Der Zwang zum Zusammenreimen erhöht für mich die Komplexität des Codes. Ich sehe einfach nicht die Abhängigkeiten bzw. Zusammenhänge. Die hat der Designer nämlich im code behind versteckt:

private void InitializeComponent()

{

    …

    //

    // textBox1

    //

    this.textBox1.Location = new System.Drawing.Point(12, 12);

    this.textBox1.Name = "textBox1";

    this.textBox1.Size = new System.Drawing.Size(55, 20);

    this.textBox1.TabIndex = 0;

    this.textBox1.Validating +=

           new System.ComponentModel.CancelEventHandler(this.textBox1_Validating);

    //

    // textBox2

    //

    this.textBox2.Location = new System.Drawing.Point(121, 12);

    this.textBox2.Name = "textBox2";

    this.textBox2.Size = new System.Drawing.Size(55, 20);

    this.textBox2.TabIndex = 1;

    this.textBox2.Validating +=

           new System.ComponentModel.CancelEventHandler(this.textBox1_Validating);

    //

    // button1

    //

    this.button1.Location = new System.Drawing.Point(202, 9);

    this.button1.Name = "button1";

    this.button1.Size = new System.Drawing.Size(75, 23);

    this.button1.TabIndex = 2;

    this.button1.Text = "Add";

    this.button1.UseVisualStyleBackColor = true;

    this.button1.Click += new System.EventHandler(this.button1_Click);

    …

Sehen Sie die Zusammenhänge? Die stecken in der jeweils letzten Zeile der Codeblöcke für die Steuerelemente. Dort wird der Eventhandler dem Control zugewiesen. Da geht es um Funktionalität. Und was steht davor? Das geht es um die Gestaltung.

Gestaltung und Funktionalität sind im code behind vermischt. Das ist gut gemeint vom Designer – aber ich finde das nicht mehr verständnisfördernd. Früher war das genial; heute fühle ich mich dadurch verwirrt. Liegt das am Alter? ;-) Nein, ich glaube, das liegt am durch Clean Code Developer geschärften Blick für Verständlichkeit und Abhängigkeiten.

Wo Zusammenhänge nicht sofort klar werden, da regt sich ein ungutes Gefühl bei mir. Und unklar sind sie, wenn ich nicht dort, wo ich Code lese – also im “foreground code” –, mich leicht darüber informieren kann, wer die beteiligten Parteien sind und wie sie zusammen hängen. Weder weiß ich dort, welche Steuerelemente es gibt, noch weiß ich, wie daran Ereignisbehandlungsroutinen geknüpft sind. Das finde ich unschön.

Ich werde daher in Zukunft nicht mehr einfach auf Steuerelementen doppelklicken, um mir Eventhandler generieren zu lassen! Stattdessen schreibe ich die von Hand. Dabei kann ich dann auch wählen, wie ich sie implementiere: ob als eigenständige Methode oder doch nur als Lambda Funktion:

public partial class WinDoTheMath : Form

{

    public WinDoTheMath()

    {

        InitializeComponent();

 

        this.textBox1.Validating += ValidateNumberEntered;

        this.textBox2.Validating += ValidateNumberEntered;

 

        this.button1.Click += (s, e) =>

              {

                  var sum = int.Parse(textBox1.Text) + int.Parse(textBox2.Text);

                  MessageBox.Show(string.Format("Sum: {0}", sum));

              };

    }

 

    private void ValidateNumberEntered(object sender, CancelEventArgs e)

    {

        int i;

        e.Cancel = !int.TryParse(((TextBox) sender).Text, out i);

 

        this.errorProvider1.SetError((Control)sender,

                                     e.Cancel ? "Input is not an integer!" : "");

    }

}

Jetzt habe ich alles auf einen Blick in der Codeansicht: die wirklich relevanten Steuerelemente mit ihrer Funktionalität.

Und ich habe die beiden Aspekte Gestaltung und Funktionalität sauber getrennt. Der Designer ist jetzt wirklich nur noch für die Gestaltung zuständig. Die schaue ich mir im Design View an – und der Code dafür liegt irgendwo verborgen, weil er mich für die Funktionalität, mit der ich sonst beschäftigt bin, nicht interessiert.

Das finde ich sauber. Wer noch?

Freitag, 1. Januar 2010

%%TITLE%%

%%CONTENT%%

Donnerstag, 31. Dezember 2009

Linksammlung zum Thema Monads [OOP 2010]

image Bei der Beschäftigung mit Funktionaler Programmierung stoße ich immer wieder auf den Begriff Monad. Leider bringt mich jedoch der zugehörige Wikipedia-Artikel bei dessen Verständnis nicht weiter. Deshalb habe ich jetzt ein wenig gegooglet. Für mich als Entwickler, der bisher (vor allem) mit imperativen Programmiersprachen zu tun hatte, sind dabei die folgenden Beiträge herausgekommen, die ich als verständnisfördernd empfinde.
So ganz bin ich mit diesen Beiträgen allerdings immer noch nicht zufrieden. Ich finde die Erklärungen für Monads immer noch recht theoretisch und fern der imperativen objektorientierten Programmierpraxis.

Wie würde ich nun Monads erklären? Hm... mal sehen. In einem zukünftigen Beitrag versuche ich das mal.

Samstag, 26. Dezember 2009

Neues Format für längere Blogartikel? [OOP 2010]

Es gibt keine formale Beschränkung für die Länge von Blogartikeln. Und so habe ich bisher kürzere wie längere unterschiedslos hier gepostet. Nun mir jedoch Zweifel gekommen, ob ich das auch weiterhin tun soll.

Online Zeitungen verfahren ja schon lange so, dass sie längere Beiträge splitten. Beispiel www.zeit.de: Artikel werden zunächst häppchenweise gezeigt, wie Sie hier sehen können; erst wenn Sie “Auf einer Seite lesen” anklicken, bekommen Sie alles auf einer Seite. Die Zeit hat für solche verschiedenen “Views” natürlich ein hübsches Redaktionssystem, bei dem auch noch ein PDF rausfällt. Das kann ich mir natürlich nicht leisten. Was also tun?

Wie wäre es denn, wenn ich längere Beiträge nicht mehr direkt ins Blog schriebe, sondern wie folgt veröffentlichen würde:

Zustand als Abhängigkeit - IoC konsequent gedacht

Wenn Sie mögen, können Sie diese Ansicht auf den ganzen Bildschirm vergrößern. Oder Sie stellen vom “Book View” um auf den “Scroll View”, um quasi wieder alles auf einer langen Seite zu haben. Oder Sie drucken den Inhalt aus. Oder Sie exportieren ihn als PDF, um ihn auf Ihrem favorisierten ebook-Reader zu lesen.

Was meinen Sie? Wäre solche Differenzierung in der Publikation nicht eine gute Sache? Blogartikel, die ca. eine Bildschirmseite lang sind, veröffentliche ich weiterhin direkt im Blog. Aber Artikel von mehreren Bildschirmseiten oder gar mehreren Druckseiten biete ich Ihnen im obigen Format.

Denn seien wir ehrlich: Längere Texte am Bildschirm zu lesen, ist immer unschön. Es macht wenig Unterschied, ob sie häppchenweise präsentiert werden oder in einem Stück. Längere Inhalte brauchen Überblick. Den bietet nur ein Ausdruck – wofür ein Blog keine wirklich gute Grundlage ist – oder eine Ganzseitendarstellung im Hochformat auf einem speziellen Reader.

Vergleichen Sie einmal den Text im obigen Viewer bzw. nach Ausdruck mit der ursprünglichen Veröffentlichung. Welche Präsentation finden Sie lese- bzw. verständnisfreundlicher?

Nachtrag: In einigen Kommentaren zu diesem Posting wurde gegen solche Flash-Darstellung eingewandt, dass der Content dann nicht von Suchmaschinen indiziert würde. Das ist jedoch falsch wie diese Suche beweist: http://www.google.de/search?q="zustand+als+abhängigkeit"+site:scribd.com (die Einschränkung mit site:scribd.com habe ich nur gemacht, um den Link zum Dokument auf die erste Ergebnisseite zu bringen). Das erste Ergebnis ist ein Link auf das obige Dokument: http://www.scribd.com/doc/24512457/Zustand-als-Abhangigkeit-IoC-konsequent-gedacht.

Objektorientierung als Behaviorismus der Softwareentwicklung [OOP 2010]

Die Objektorientierung steht uns im Weg zu evolvierbareren Programmen. Ja, das glaube ich immer mehr. Nicht, dass ich Objekte missen möchte – sie sind nützliche und wichtige Strukturierungsmittel für Software.  Ich will natürlich Funktionseinheiten mit eigenem Zustand definieren können. Aber die Sprache der Objektorientierung wie sie C++, Java, C# und auch VB vorgeben, ist einfach nicht für Flexibilität und Evolvierbarkeit gedacht gewesen. Und da Sprache unser Denken bestimmt, kommen bei der Benutzung objektorientierter Sprachen eben nicht einfach flexible und evolvierbare Programme heraus.

Objektorientierung ist sozusagen die Brille der Programmier-Behavioristen. Wer durch sie blickt, vereinfacht die Welt extrem – manchmal bis zur Unkenntlichkeit. Wo die Welt der Behavioristen durch Reiz-Reaktion determiniert ist, da ist sie für “die Objektorientierten” durch Zustand und Synchronizität geprägt.

image Und so wie Behaviorismus bei der Dressur eines Tanzbären funktionieren mag, so funktioniert Objektorientierung auch bei einem simplen Malprogramm. Ok, sie “funktioniert” auch bei viel größeren Anwendungen – doch ich meine mit “funktionieren” nicht, dass objektorientierter Code am Ende läuft, sondern ob mit den Mitteln der Objektorientierung etwas heraus kommt, was sich auch weiterentwickeln lässt. Denn das ist ein wesentliches und trotzdem oft unterschätztes Qualitätsmerkmal von Software. Beim Tanzbären ist das hingegen nicht wichtig. Wenn der keine neuen Tricks mehr lernt, nimmt man einen neuen.

Wer hingegen mehr als einen Tanzbären dirigieren will, z.B. ein ganzes Unternehmen, der ist mit Behaviorismus schlecht bedient. Unternehmensführung funktioniert schlecht mit Zuckerbrot und Peitsche als Mittel, um Reize auszulösen und Reaktionen zu provozieren, die eine vielköpfige Belegschaft verlässlich zum Erfolg führen.

Dito die Objektorientierung in nicht mehr einfach überschaubaren Szenarien. Direkte, streng typisierte und synchrone Kopplung ist das falsche Denkmuster für evolvierbare Software. Solche Kopplung ist aber der Default der Objektorientierung. Nur mit Mühe und Disziplin kann man sich von ihm lösen. Das mag dann zwar das Expertentum von Softwareentwicklern ausmachen – doch wie in Panik die Moral schnell verdampft, so verflüchtigen sich unter Projektdruck auch solche programmierzivilisatorischen Errungenschaften. Was dann nicht durch die Sprache quasi erzwungen wird, das findet nicht statt.

Deshalb bin ich für möglichst hart verdrahtete neue Defaults – wenn schon nicht neue Sprachen, die die Evolvierbarkeit begünstigen. Ein solcher Default ist die echte Komponentenorientierung, bei der Komponentenimplementationen physisch alleingestellt in Komponentenwerkbänken stattfinden und Komponenten einander nicht mehr direkt referenzieren. Deshalb bin ich für Flows als ersten groben Implementationsansatz für jede Realisierung eines Features; denn in Flows gibt es keine Kopplung mehr zwischen den Schritten, so dass die Evolvierbarkeit sehr hoch ist. Deshalb bin ich für die Nutzung eines in-proc Bussystems, d.h. selbst wenn die Kommunikation noch lokal und synchron ist. Ein solcher Bus entkoppelt noch mehr als ein DI Container.

Komponentenorientierung begrenzt die OO-Defaults lokal auf die Implementation einer überschaubaren Komponente. Und DI Container, Flows sowie Busse sind neue Defaults für die Kopplung von Strukturen, wenn es unüberschaubar wird – was immer schneller passiert, als wir uns vorstellen.

Wie wäre es, damit einmal im neuen Jahr zu experimentieren? Der überkommene Programmierbehaviorismus würde einem differenzierteren Verständnis von Software weichen.

Samstag, 7. November 2009

Zustand als Abhängigkeit – IoC konsequent gedacht [OOP 2010] [endlich-clean.net]

Design for Testability ist auch Design for Flexibility. Deshalb liebe ich TDD. Allerdings bin ich dabei immer wieder über einen Problemfall gestolpert: die Prüfung des Zustands eines system under test (SUT). Als Beispiel hier ein typischer (nicht unbedingt optimaler) Ansatz bei der bekannten und beliebten KataPotter.

Viele denken bei der Implementierung der Preisberechnung für die KataPotter an einen Warenkorb, den es zu füllen gilt, um dann den Preis unter Anwendung der Rabatte zu berechnen. Sie definieren eine solche Klasse dann z.B. so:

class KataPotterShoppingBasket

{

    private Dictionary<int, int> books = new Dictionary<int, int>();

 

    public void AddBook(int bookId)

    {

        //...

    }

 

    public decimal Total

    {

        get

        {

            //...

        }

    }

}

Ein  zugehörige Test scheint zunächst einfach:

[TestFixture]

public class testKataPotterShoppingBasket

{

    [Test]

    public void Enthält_ein_Buch()

    {

        var sut = new KataPotterShoppingBasket();

 

        sut.AddBook(1);

 

        //?

    }

}

Aber, ach, ohje… Wie prüft man denn nun, ob das hinzugefügte Buch auch im Warenkorb angekommen ist? AddBook() ist ein Kommando und verändert den Zustand des Warenkorbs. Wie kann man also ganz allgemein diesen Zustand in Tests prüfen?

Zugriffsroutinen für Zustand?

Naheliegend ist die Prüfung von Zustand durch direkten Zugriff auf den Zustand. Dafür muss das SUT nur einen solchen Zugriff anbieten. Für einen üblichen Warenkorb mag das der Fall sein. Da gibt es nicht nur ein AddBook(), sondern sicher auch noch ein GetBook() oder eine ganze Collection von Books, über die man prüfen kann, ob eine Zustandsveränderung wirksam war.

Der KataPotter-Warenkorb braucht solche Zugriffsroutinen aber nicht. Warum sollten sie also nur für den Test eingeführt werden? Ich halte nichts davon, weil sie das Interface des system under test aufblähen. Sie haben keinen Wert für die Domäne, sind in der Intellisense-Liste der Eigenschaften und Methoden aber immer sichtbar. Da hilft auch die Deklaration als intern nicht viel. Das wäre falsch verstandenes Design for Testability.

Wenn Sie die KataPotter so wie oben gezeigt angehen sollten, widerstehen Sie der Versuchung, dem Warenkorb z.B. eine Count-Eigenschaft zu geben, um den Test zum Laufen zu bekommen.

Solch ein Design wäre dann zwar irgendwie testbarer, aber die Flexibilität würde sich nicht erhöhen. Sie hätten nicht mehr das minimal Notwendige getan. Außerdem würde der Test dann zweierlei testen. Das ist zu vermeiden! Er würde AddBook() und Count prüfen. In beiden könnten ja Fehler stecken.

AddBook() und Count wären sogar gegenseitig abhängig in Bezug auf Tests. Sie könnten auch Count nicht zuerst testen, denn dazu müssten Sie Zustand aufbauen. Wäre das nicht der Fall, ist die Nutzung von mehreren SUT-Funktionalitäten in einem Test nicht so schlimm. Dann können Sie die in separaten Tests vorweg prüfen.

Ergebnis: Vermeiden Sie, spezielle Zugriffsmethoden für Zustand nur zum Zweck des Testens einzurichten. Allemal, wenn sie sie nicht wirklich isoliert testen können.

Zustandstest durch Ableitung?

Auf Zustand können Sie aber nicht nur von außen zugreifen, einfacher ist es von innen. Eine Alternative zu Zugriffsmethoden wäre daher die Ableitung einer Klasse vom SUT. Deren Zustand wäre ja der des Warenkorbs. Dazu müssten Sie nur die Sichtbarkeit des Zustands ändern:

class KataPotterShoppingBasket

{

    protected Dictionary<int, int> books = new Dictionary<int, int>();

    ...

}

Eine Ableitung kann dann “gefahrlos” eine Zugriffsmethode anbieten. Die “verunreinigt” die Domänenklasse nicht:

class TestableShoppingBasket : KataPotterShoppingBasket

{

    public int Count { get { return base.books.Count; } }

}

Wenn Sie im Test die abgeleitete Klasse als SUT instanzieren, haben Sie also Testbarkeit “ohne Reue”:

[Test]

public void Enthält_ein_Buch()

{

    var sut = new TestableShoppingBasket();

 

    sut.AddBook(1);

 

    Assert.That(sut.Count, Is.EqualTo(1));

}

Mit einer Ableitung müssen Sie nicht einmal Design for Testability betreiben. Und die Flexibilität wird auch nicht eingeschränkt. Eine scheinbar ideale Lösung, oder? Der Eingriff, private Variablen auf protected statt private zu setzen, ist minimal. Und dass dieser Ansatz an abgeschlossenen (sealed) Klassen scheitert, ist auch unbedeutend.

Dennoch will mir eine Ableitung vom eigentlichen SUT nicht recht schmecken. Nein, das liegt nicht daran, dass ich Ableitungen für generell überbewertet halte. Hier wäre ja sogar das Liskov Substitution Principle eingehalten. Den Aufwand für die Ableitungsklasse finde ich auch nicht zu hoch.

Nein, es etwas anderes, das mich stört…

Zustand als Abhängigkeit

Ich glaube, mich stört, dass Zustand hier eine Extrawurst gebraten bekommt. Zustand wird nicht seiner Natur nach behandelt. Denn Zustand ist eine Abhängigkeit!

Üblicherweise denken wir bei Abhängigkeiten an Funktionalität. Der Warenkorb könnte z.B. von einer Preisberechnungsfunktionalität abhängen:

image

Dann fänden wir es ganz natürlich, diese Abhängigkeit in den Warenkorb zu injizieren:

class KataPotterShoppingBasket

{

    private IPriceCalculator calc;

    private Dictionary<int, int> books = new Dictionary<int, int>();

 

    public KataPotterShoppingBasket(IPriceCalculator calc)

    {

        this.calc = calc;

    }

    ...

Aber was ist jetzt das private Feld calc? Es gehört genauso zum Zustand des Warenkorbs wie books. Der einzige Unterschied  besteht darin, dass calc von einem selbstdefinierten Typ ist und book einen .NET Fx Typ hat.

Darüber hinaus glauben wir dann aber irgendwie noch eine größere “Intimität” zwischen book und z.B. AddBook() zu spüren. Die Inhalte von book werden mittels der Warenkorbmethoden verändert, während calc konstant bleibt. book ist eben “wahrer” Zustand und deshalb anders zu behandeln als calc.

Doch warum ist die Abhängigkeit von einem Preisberechner denn offengelegt? Warum wird der injiziert? Weil das den Warenkorb isoliert testbar macht. Sie können ihn unter Injektion einer Attrappe auf die Probe stellen. Dazu kommt, dass damit die Preisberechnung auch noch flexibler austauschbar wird; ein schöner Nebeneffekt.

Wenn nun jedoch bessere Testbarkeit zur IoC für funktionale Abhängigkeiten geführt hat, warum sollte sie dann nicht auch bei Abhängigkeiten von Zustand zum Einsatz kommen? Ich sehe da kein gewichtiges Gegenargument. Das widerspräche auch nicht der Objektorientierung. Nach Injektion ist Zustand ja weiterhin in einem Objekt entsprechend dessen Zugriffsmethoden verborgen.

Wenn wir von Abhängigkeiten sprechen, sollten wir also konsequent sein. Zustand (state) wie Funktionalität (behavior) sind zwei Seiten derselben Medaille Abhängigkeit:

image

Aus meiner Sicht sieht die Lösung des Ausgangsproblems deshalb so aus:

class KataPotterShoppingBasket

{

    private readonly PriceCalculator calc;

    private readonly Dictionary<int, int> books;

 

    public KataPotterShoppingBasket(Dictionary<int, int> books,

                                    PriceCalculator calc)

    {

        this.calc = calc;

        this.books = books;

    }

    ...

Dem erweiterten Warenkorb injizieren Sie nicht nur die Abhängigkeit von einer Preisberechnungsfunktionalität, sondern auch einen initialen Zustand:

[Test]

public void Enthält_ein_Buch()

{

    var books = new Dictionary<int, int>();

    var sut = new KataPotterShoppingBasket(

                    books,

                    null);

 

    sut.AddBook(1);

 

    Assert.That(books.Count, Is.EqualTo(1));

}

Das halte ich für genauso konsequent wie einfach zu verstehen. Etwas systematisiert lautet dann das Muster:

  1. Abhängigkeiten einer Klasse zusammenfassen in einer Abhängigkeitsklasse. Das gilt für Zustände wie Funktionalität, von denen eine Klasse abhängt.
  2. Während Tests die dafür minimal nötigen Abhängigkeiten mit einer Instanz der Abhängigkeitsklasse injizieren.
  3. Erwartungen an Zustandsänderungen nach Ausführung von Kommandos auf dem SUT prüfen anhand der injizierten Abhängigkeitklasseninstanz.

Am Beispiel des Warenkorbs sähe das so aus:

class KataPotterShoppingBasket

{

    internal class Dependencies

    {

        public readonly IPriceCalculator calc;

        public readonly Dictionary<int, int> books =

    }

 

 

    private readonly Dependencies dependencies;

 

    public KataPotterShoppingBasket(Dependencies dependencies)

    {

        this.dependencies = dependencies;

    }

    ...

Der Test wäre sogar noch ein wenig klarer, finde ich:

[Test]

public void Enthält_ein_Buch()

{

    var dependencies = new KataPotterShoppingBasket.Dependencies();

    var sut = new KataPotterShoppingBasket(dependencies);

 

    sut.AddBook(1);

 

    Assert.That(dependencies.books.Count, Is.EqualTo(1));

}

Diesem Verfahren steht auch die “normale” Dependency Injection nicht im Wege. Sie würde es sogar verbergen. Dazu bräuchte die Abhängigkeitsklasse nur einen ausgewiesenen “Kanal” für die Injektion der Funktionalität, von der der Warenkorb abhängig ist, z.B. so für Unity als DI Container:

public class Dependencies

{

    [Dependency]

    public IPriceCalculator calc {get; set;}

    public readonly Dictionary<int, int> books = …

}

Zusätzlich müsste im Mapping auch die Abhängigkeitsklasse aufgeführt werden. Aber das ist kein sonderlicher Mehraufwand. Das ließe sich womöglich sogar automatisieren.

Fazit

Im Sinne eines systematischen Vorgehens beim Test und beim Design finde ich es sehr überlegenswert, Abhängigkeiten breiter als bisher zu fassen. Funktionseinheiten sind nicht nur von anderen Funktionseinheiten abhängig, sondern auch von Zustand.

Wenn dann Abhängigkeiten injiziert werden sollen, um bessere Testbarkeit zu erreichen, dann gilt das nicht nur für Funktionseinheiten, sondern auch für Zustand.

Gerade bei TDD sinkt dadurch – so mein Empfinden – der gedankliche Aufwand. Bei jedem Kommando und jeder Abfrage, die getestet werden, müssen Sie sich dann einfach nur die Frage stellen, welcher Zustand dabei eine Rolle spielt. Dann bauen Sie vor dem Test Ausgangszustand speziell für den Testfall zusammen (arrange) und injizieren ihn, rufen das SUT (act) auf und prüfen anschließend (bei Kommandos) den neuen Zustand (assert). Fertig.

Ich werde in der nächsten Zeit mal probeweise konsequent so vorgehen. Mal schauen, wie sich das anfühlt. Ich bin optimistisch, dass es ein gutes Gefühl sein wird. Weniger Zweifel, weniger Nachdenken, also höhere Produktivität.

Samstag, 31. Oktober 2009

TDD wofür? [OOP 2010] [endlich-clean.net]

Anlässlich einiger Code Katas, die ich in letzter Zeit gemacht habe, hier ein paar Gedanken zu Nutzen und Grenzen von TDD (Test-Driven Development oder Test-Driven Design):

Was leistet ein Test? Er prüft die Korrektheit der Funktionsweise einer Codeeinheit. Klingt selbstverständlich. Die Erkenntnis durch TDD liegt für mich aber in der Umkehrung: Nur, was als eigene Codeeinheit vorliegt, kann auch direkt auf Korrektheit geprüft werden.

Codeeinheiten sind mehr oder weniger kompliziert. Eine ganz einfache könnte so aussehen:

public int Add(int a, int b)
{
    return a-b;
}

Ist die korrekt? Das stellt ein genauso einfacher Test leicht fest:

[Test]
public void Adding_two_numbers()
{
    var sut = new MyMath();
    Assert.AreEqual(5, sut.Add(2, 3));
}

Wenn der Testbalken jetz rot ist, dann lokalisiert eine Codeinspektion schnell den Übeltäter.

Wie steht es aber mit diesem Code zur FizzBuzz Kata:

IEnumerable<string> FizzBuzz()
{
    for(int i=1; i<=100; i++)
    {
        if (i%3 == 0)
           yield return “Fizz”;
       else if (i%5 == 0)
           yield return “Buzz”;
       else if (i%3 == 0 && i%5 == 0)
           yield return “FizzBuzz”;
       else
          yield return i.ToString();
    }
}

Ist der korrekt? Wer kann das auf einen Blick sehen? Vor allem aber, wer kann ihn so einfach runterschreiben? Die Entwicklung geht ja eher schrittweise vor. Genügt da ein Test wie:

[Test]
public void Test_FizzBuzzer()
{
    var sut = new FizzBuzzer();
    string expectedValues = new[] string {“1”, “2”, “Fizz”, “4”, “Buzz”, …};
    int i;
    foreach(string v in sut.FizzBuzz())
        Assert.AreEqual(expectedValues[i++], v);
}

Mit dem kann man natürlich von Anfang an “draufhauen” auf das system under test (SUT). Aber wenn irgendwas nicht klappt, weiß man nicht so recht, wo es hapert. Die SUT-Codeeinheit ist unübersichtlich und damit der Test sehr pauschal.

Durch TDD entsteht ein Gefühl dafür, wenn es unübersichtlich wird. Dann testen die Tests nämlich nicht mehr nur eine Funktionalität, sondern mehrere. Im obigen Fall ist der Test sozusagen ein Integrationstest, der sowohl die Generierung von Zahlen wie auch die Transformation einer Zahl prüft. Ob es ein Problem mit den Zahlen gibt oder eines bei der Transformation lässt sich im Zweifelsfall nur schwer sehen. Für mehr Klarheit bietet sich dann der Debugger an.

Besser ist es jedoch, den Debugger in der Schublade zu lassen. Stattdessen lieber den Code in Teilfunktionen zerlegen. Denn: Nur, was als eigene Codeeinheit vorliegt, kann auch direkt auf Korrektheit geprüft werden.

Die Teilfunktionen hier im Beispiel: Zahlengenerierung und Transformation. Die könnten mit ihrer Signatur so aussehen:

IEnumerable<int> ErzeugeZahlenreihe() { … }

string TransformiereZahl(int n) { … }

Daraus folgen dann insgesamt drei Tests:

  1. Prüfe die Zahlengenerierung
  2. Prüfe die Transformation
  3. Prüfe die Integration von Zahlengenerierung und Transformation

Jeder Test ist nun sehr spezifisch und vergleichsweise einfach. Dass das zu etwas mehr Code führt, ist nicht so schlimm, wie sich gleich zeigen wird. Geht etwas schief, ist der Scope jedenfalls klein. Ein Debugger muss nur selten angeworfen werden, um den Fehler darin zu finden. Das erhöht die Entwicklungsgeschwindigkeit wieder.

Wieviel Rätselraten Sie betreiben wollen, wenn in Ihrem Code etwas schief geht, bestimmen also sie. Der Rätselspaß (oder –ärger?) ist proportional zur Größe Ihrer SUTs.

Zudem gilt: Die Integration einzeln getesteter und für korrekt befundener Codeeinheiten zu testen ist weniger aufwändig, als nur die Codeeinheiten verschmolzen zu einem Ganzen zu testen.

Und jetzt zur zweiten TDD-Erkentnis: Code in einer eigenen Einheit ist prinzipiell austauschbar. Nicht nur lässt er sich gezielt testen und damit kontrolliert verändert werden. Er lässt sich auch gezielt komplett ersetzen.

In Max Frischs “Andorra” steht – wenn ich mich recht erinnere: “Nur was verzapft ist, geht nicht aus dem Leim.” Ein Satz, der mir irgendwie aus dem Deutschunterricht im Gedächtnis geblieben ist, auch wenn die Lektüre ansonsten keinen bleibenden Eindruck bei mir hinterlassen hat.

Angelehnt an diesen Satz, möchte ich vor dem TDD-Hintergrund formulieren und hoffen, dass es sich bei Ihnen ebenso einprägt: “Nur was isoliert testbar ist, kann ersetzt werden.” Das ist der Grund, warum aus Test-Driven Development über die Zeit Test-Driven Design geworden ist. Denn TDD macht es nicht nur leichter, die Korrektheit von Code zu prüfen, weil es testbare Codeeinheiten “austreibt”. Indem es diese Codeeinheiten motiviert, erzeugt TDD Flexibilität.

Am Beispiel: Gibt es erstmal eine eigene Codeeinheit für die Zahlengenerierung und die Transformation, dann kann es dafür alternative Implementationen geben. Die Zahlengenerierung könnte heute die natürlichen Zahlen erzeugen und morgen nur Primzahlen oder nur Fibonacci-Zahlen. Die Transformation könnte heute Vielfache von 3 und 5 erkennen und morgen gerade Zahlen oder Primzahlen.

Direkt und isoliert testbare Codeeinheiten haben immer einen irgendwie gearteten Kontrakt. Für die Zahlengenerierung wäre das z.B. Func<IEnumerable<int>>. Solange eine Integration nur auf die Kontrakte der sie konstituierenden Codeeinheiten abhebt, ist sie also frei, jede Implementation, die ihn erfüllt, zu nutzen.

Ergo: TDD führt nicht nur zu einfacherer Testbarkeit aufgrund kleinerer Codeeinheiten, sondern auch zu größerer Flexibilität.

Doch damit nicht genug! Ebenfalls segensreich empfinde ich, dass TDD quasi sofort Ergebnisse liefert. Wer erst lange an einer Implementation rumschraubt und am Ende testen will, der verschiebt die Befriedigung eines grünen Testbalkens auf unbestimmte Zeit. Implementationsperioden ohne Testläufe sind Durststrecken. TDD ist also eine ganz zeitgemäße “Genuss sofort”-Technik. Warum sich die Befriedigung eines grünen Testbalkens versagen? Aber vor allem: Warum länger als unbedingt nötig im Nebel tappen? Denn je länger Sie ohne Testlauf implementieren, desto tiefer wandern Sie in einen Nebel der Ungewissheit hinein. Ist das, was Sie an Code schreiben, tatsächlich zielführend? Löst Ihr Code wirklich das Problem? Oder schreiben Sie überhaupt den Code, den Sie schreiben wollen? Implementieren Sie tatsächlich Ihr Konzept?

TDD führt also auch zu häufigem Realitätscheck. Das ist umso wichtiger, je unbekannter die Problemdomäne. Wie sich in CodingDojos auf dem .NET Open Space und der prio-Konferenz herausgestellt hat und auch Übungen während Clean Code Developer Trainings zeigen, lauert die Unbekanntheit überall. Kaum ein Problem ist so einfach, dass sich TDD erübrigen würde. Nehmen Sie dafür aber nicht mein Wort, sondern probieren Sie es aus. Die KataPotter ist dafür ein Beispiel, das schon so manchem einiges Stirnrunzeln gemacht hat. Oder versuchen Sie sich am Minesweeper. Auch schön ist der Umgang mit römischen Zahlen.

Apropos römische Zahlen: Hier sehe ich allerdings durchaus eine Grenze von TDD. TDD hilft, separate Funktionseinheiten in einer größeren zu erkennen. Wird die größere schwer zu testen, dann liegt eine Zerlegung nahe.

TDD hilft jedoch nicht weiter, wenn eine Codeeinheit quasi unteilbar ist. Das ist oft nur scheinbar der Fall – am Ende ist jedes Lösung ja aber ein endlicher Baum, dessen Blätter eben die “atomaren” Codeeinheiten sind. Wie solche “atomaren” Codeeinheiten intern aussehen, beantwortet TDD nicht. TDD ist ein Hilfsmittel zur Strukturierung, nicht für den Algorithmenentwurf. Ein Bubblesort-Algorithmus lässt sich mittels TDD vielleicht noch entwickeln, weil er sich in naheliegende Codeeinheiten zerlegen lässt; ein Quicksort-Algorithmus jedoch ist jenseits von TDD.

Die Kata zur Umwandlung von römischen Zahlen profitiert mithin nicht so stark von TDD wie z.B. FizzBuzz oder KataPotter. Sie ist eher algorithmischer, denn struktureller Natur.

Und so bin ich bei meiner abschließenden Erkenntnis zu TDD: TDD ist kein Ersatz für Kreativität, Nachdenken und Erfahrung. Auch mit TDD als Werkzeug im Programmierkoffer lohnt der Einsatz des Gehirns vor dem Programmieren. Nachdenken hilft und verkürzt die Lösungszeit. Angesichts von z.B. der Minesweeper-Kata müssen wir uns nicht dümmer stellen als wir sind. Wir müssen nicht reflexartig in die Tasten hauen und sofort einen Test schreiben. Es lohnt sich, einen Moment über das Problem nachzudenken. Welche Datenstrukturen sind im Spiel? Welche Algorithmen? Wie könnte eine Grobstruktur der Lösung aussehen, welche Codeeinheiten/Verantwortlichkeiten könnten wie zusammenspielen?

Wenn Sie dann einen gewissen Plan haben, dann erst legen Sie mit TDD los. Und zwar gehen Sie dann gezielt mit TDD auf die Grobstrukturen los, die Sie durch Nachdenken ermittelt haben. Das erspart unnötigen Refaktorisierungsfrust.

Zum Schluss eine offene Frage: Erzeugt TDD nicht nur testbare und flexible Strukturen, sondern auch evolvierbaren Code? Ist Testbarkeit bzw. Flexibilität mit Evolvierbarkeit gleichzusetzen? Oder könnte eine Zerlegung, die nicht durch TDD motiviert ist, noch evolvierbarer sein? Hm…

Einstweilen entscheide ich mich allerdings im Zweifel für den Glauben an TDD gepaart mit Nachdenken. Für die Strukturierung von Komponenten ist das ein sehr hilfreiches Vorgehen.