dotnet-cologne: veni-vidi-vici!

Am letzten Freitag fand die dotnet Cologne zum 3. Mal statt. Ich habe die ersten 2 Male nicht teilgenommen, aber wie ein Freund meinte, diesmal hatte ich “Heimvorteil” ­čÖé Nach der langen Pause, bedingt durch Beinbr├╝che, Umz├╝ge und ├Ąhnliche Erfahrungen, hatte ich regelrechte Entzugserscheinungen: Entzug von der Community.

Principles, patterns, and practices are important, but it’s the people who make them work. – Robert C. Martin aka Uncle Bob

Es war super! ├ťber 300 Leute, darunter sehr-sehr viele bekannte Gesichter, riesen Freude sie wieder zu sehen. Der erste Vortrag, den ich besucht habe, hie├č “Agile Architekturen” und wurde von Ilker abgehalten. Er hat zwei konkrete Beispiele – die Hexagonale Architektur von Alistair Cockburn und Data Context Interaction, der “neue MVC” – vorgestellt, samt Fallbeschreibung und Code f├╝r jeweils ein User Case. Wir d├╝rften/sollten uns dann eine Meinung ├╝ber die L├Âsungen bilden und diese Meinung auch kundtun. Es wurde schnell klar, dass jede Variante ihre Vor- und Nachteile hat, und dass die allgemein g├╝ltige “agile Architektur” nicht wirklich existiert.

 

Nach der Pause ging es dann f├╝r mich mit Daniel “REST Wars: WCF WebHTTP vs. ASP.NET MVC” weiter. Es war eine sehr interessante und kompetente Pr├Ąsentation. Am Ende hatte ich eine ganz genaue Vorstellung dar├╝ber, WOZU man REST verwenden soll und welche technologische M├Âglichkeiten man hat. Notiz an mich: WCF WebAPI bei Codeplex anschauen.

 

Als Lunch-Session wollte ich unbedingt von Sergey ├╝ber psake h├Âren, aber die Zeit ist bei den Gespr├Ąchen mit den Jungs von User Group Karlsruhe irgendwie verflogen. Daf├╝r habe ich stattdessen Kay Giza kennengelernt ­čÖé Endlich kann ich ein Gesicht zu jener Person zuordnen, die mir seit Jahren regelm├Ą├čig Tipps und Empfehlungen schickt. Er hatte selbst eine Session ├╝ber MSDN vorbereitet und ich h├Ątte ihn sehr gern geh├Ârt, aber er war parallel mit der Azure-Session und an dieser musste ich auf jedem Fall teilnehmen. Also hat Kay meine Fragen zum MSDN zwischen a flying coke ( ­čśë you know what I mean) und einen Kaffee im Foyer beantwortet. Danke sch├Ân ­čÖé

 

Der “├ťberflieger” des Tages war eindeutig Bart de Smet – der Mann, der in seiner Freizeit u.a. Sudoku mit c# (Stichwort Microsoft Solver Foundation) l├Âst.
Ich konnte leider nur den ersten von 2 Vortr├Ągen besuchen, ├╝ber “LINQ to Everything” – und das hat er wirklich wortw├Ârtlich gemeint. (Stichwort IQbservable, IScheduler)

 

Der vorletzte Vortrag war der bereits erw├Ąhnte “Azure: Portierung einer Anwendung”. Wir haben einen kleinen ├ťberblick von der Zusammensetzung von “the Cloud” bekommen, und die “Do’s and Dont’s” dazu. Und mir wurden diese zwei Tatsachen klar: in der Cloud alles kostet Geld und ohne Clean-Code (insbesondere ohne Dependency Injection) braucht man dar├╝ber gar nicht nachzudenken. Wenn aber die Vorbedingungen erf├╝llt sind, dann geht das Portieren super schnell und einfach.

 

Last but not least habe ich mir eine Session ├╝ber “Rich-Internet Apps & Mobile Anwendungen mit HTML5…” geg├Ânnt, ein Thema, mit dem ich mich zur Zeit nicht direkt besch├Ąftigen muss – zur Entspannung so zusagen ­čśë Also dies war eine super Entscheidung, Tim Fischer ist ein gro├čartiger Vortragender! Als Nebeneffekt habe ich erfahren, wie man JavaScript-Code ohne es zu schreiben generiert (Stichtwort Sencha) und was hinter dem Begriff HTML5 steckt.

 

Damit war f├╝r mich das Pflichtprogramm zu Ende, aber nat├╝rlich nicht der Tag.
Jeder, der bei solchen Community-Events schon teilgenommen hat, wei├č, dass die besten Gespr├Ąche rund um Entwickler-Sein bei dem/den abschlie├čenden Bier/en danach gef├╝hrt werden. So hatte ich die Ehre, endlich codemurai kennen zu lernen und die Meinungen der anderen ├╝ber Themen wie “warum ist Softwareentwicklung einer M├Ąnnerdomaine” oder “Absatzmarkt China” oder “das Geheimniss hinter dem Erfolg von osteurop├Ąischen Softwareunternehmen” zu h├Âren.

 

Es war sch├Ân, es war anstrengend und es war sehr lehrreich.
Genauso, wie eine Konferenz sein soll.

 

Tausend Dank an das Orga-Team und an die Sprecher f├╝r ihre gro├čartige Arbeit und f├╝r ihre M├╝he, diese Community zu st├Ąrken und zu pflegen.
“Individuals and Interactions over Processes and Tools” – wie das Agile Manifest es so sch├Ân definiert – weil wo w├Ąren wir, wenn das nicht war w├Ąre?!

 

Das n├Ąchste Event – NOS S├╝d 2011 – wartet schon…

Design copy-paste

www.codekicker.de
Irgendwann Anfang Februar habe ich von Marco Parrillo (Neue Mediengesellschaft Ulm mbH) eine Empfehlung f├╝r: “codekicker.de – Die deutschsprachige Entwickler-Community | Machen Sie mit!” bekommen. Ich finde so was immer super und habe die E-Mail sofort an meinen Kollegen weitergeleitet (die Empfehlung kam ja von einer seri├Âsen Gesellschaft).

Ich hatte in der Arbeit keine Zeit, also habe ich die Seite erst am Abend ge├Âffnet :
www.stackoverflow.com

Oh, dachte ich, super: eine deutsche Tochter von stackoverflow!

Auf den ersten Blick konnte man das nirgendwo sehen, also habe ich alles durchsucht: Impressum, Kontakte, sonstige Infoseiten, aber gar nichts… Danach habe ich einfach die Suchfunktion benutzt, aber das einzige Ergebnis eine Frage zu einem Stackoverflow-Exception war.

Durch googeln bin ich f├╝ndig geworden: die Seite ist gar keine Tochterseite von stackoverflow, sie wurde einfach nur kopiert! Auf die Suchbegriffe stackoverflow + codekicker habe ich folgende Diskussion bei stackoverflow gefunden:

In germany a Stackoverflow-like site was created, that is very, very similar to Stackoverflow in the mechanics of reputation and badges etc. That is so similar, that I think they could use the same software. Did you sell it to them? Or are they using a very similar clone-software? The Community I have in mind is Codekicker.

Das war ern├╝chternd (und gleichzeitig komisch, da es offensichtlich eine Diskussion auf codekicker ├╝ber das Thema gibt, aber irgendwie doch nicht gefunden wird).
Als ich dann die Antworten der Betreiber der Seite gelesen habe, wurde ich richtig entt├Ąuscht:

Vielen Dank f├╝r deine Anteilnahme an unserem Projekt ­čśë Du darfst uns gerne auf unserer Feedbackseite gr├╝ndlich deine Meinung sagen. Per Mail oder Telefon bin ich heute den ganzen Tag zu erreichen.

Gru├č,
Marvin

Irre ich mich, oder waren die 2 Studenten sogar stolz auf ihre copy-paste F├Ąhigkeiten? Und kann es wirklich sein, dass alle das ok finden und keine daran was auszusetzen hat? Ich meine, sogar die Logos sind ganz ├Ąhnlich. Und auch, wenn stackoverflow den Code nicht patentiert hat, jemand hat sich ja richtig viel M├╝he gemacht, das Design auszudenken, angefangen mit den Farben bis zum Gesamtbild.

Die Antworten variierten zwischen “das macht ja nichts, Hauptsache ist es auf Deutsch” und “wenn es dir nicht passt, dann nutze es nicht!” – was f├╝r mich erst Recht keine Antwort ist.

Ich ├╝berlege nun seit ein paar Wochen, ob ich das hier ansprechen soll, aber ich m├Âchte eure Meinung wissen:

  1. Bin ich wirklich die einzige, die diese Tatsachen st├Âren? W├Ąre es wirklich zu viel gewesen, irgendwo auf der Seite ein Dankesch├Ân an stackoverflow auszugeben?
  2. Auch wenn das von 2 Studenten OK w├Ąre, ist es auch in Ordnung, dass nun mehrere renommierte Zeitungen ihren guten Namen dazu geben?
  3. Ist es dann auch in Ordnung, dass diese Zeitungen mit einer kopierten Seite Geld machen wollen – da sie ja kr├Ąftig Werbung daf├╝r machen? Und diesen Satz habe ich gar nicht verstanden: “mit codekicker.de ist am 1. Februar 2011 die neue deutschsprachige Community-Plattform f├╝r Entwickler gestartet”? Die Seite gibt es ja schon seit Juli 2009.

Eins ist sicher: ich empfinde gro├čen Respekt gegen├╝ber Jeff und Joel und die anderen von stackoverflow und ich finde, sie leisten gro├čartige Arbeit (auch, wenn sie dies sicher nicht umsonst tun). Genauso einen Respekt habe hatte ich auch vor den Redakteuren dieser Zeitungen. Durch sie aber – da ich ihre Empfehlung weitergeleitet habe – habe ich nun auch eine Sache unterst├╝tzt, deren Betreiber es meiner Meinung nach nicht verdient haben. Mein Fehler, wird so bald sicher nicht wieder passieren.

File provided for Reference Use Only By Microsoft Corporation (c) 2007

Seit ein paar Wochen, wenn ich mit Resharper zu der Implementierung von manchen Microsoft-Funktionen navigieren will, bekomme ich die ganze dll heruntergeladen und danach den ganzen Code zu sehen:

// ==++==
//   Copyright (c)  Microsoft Corporation.  All rights reserved.
// ==--==
namespace System.Globalization {
    using System.Security.Permissions; 
    using System.Runtime.Serialization; 
    using System.Text;
    using System; 
    //
    // Property             Default Description
    // PositiveSign           '+'   Character used to indicate positive values.
    // NegativeSign           '-'   Character used to indicate negative values. 
    // NumberDecimalSeparator '.'   The character used as the decimal separator.
    // NumberGroupSeparator   ','   The character used to separate groups of 
    //                              digits to the left of the decimal point. 
    // NumberDecimalDigits    2     The default number of decimal places.
    // NumberGroupSizes       3     The number of digits in each group to the 
    //                              left of the decimal point.
    // NaNSymbol             "NaN"  The string used to represent NaN values.
    // PositiveInfinitySymbol"Infinity" The string used to represent positive
    //                              infinities. 
    // NegativeInfinitySymbol"-Infinity" The string used to represent negative
    //                              infinities. 
    // Property                  Default  Description
    // CurrencyDecimalSeparator  '.'      The character used as the decimal
    //                                    separator.
    // CurrencyGroupSeparator    ','      The character used to separate groups 
    //                                    of digits to the left of the decimal
    //                                    point. 
    // CurrencyDecimalDigits     2        The default number of decimal places. 
    // CurrencyGroupSizes        3        The number of digits in each group to
    //                                    the left of the decimal point. 
    // CurrencyPositivePattern   0        The format of positive values.
    // CurrencyNegativePattern   0        The format of negative values.
    // CurrencySymbol            "$"      String used as local monetary symbol.
    // 

    [Serializable] 
    [System.Runtime.InteropServices.ComVisible(true)] 
    sealed public class NumberFormatInfo : ICloneable, IFormatProvider
    { 

        // invariantInfo is constant irrespective of your current culture.

        private static NumberFormatInfo invariantInfo;
...

Das ist wunderbar, es ist gro├čartig zu sehen, wie die Klassen Random or DateTime aufgebaut sind. Obwohl ich keine Ahnung habe, warum das passiert… Ich hoffe allerdings, dass es kein Bug ist, und ich weiterhin den Code statt irgendeine Dokumentation sehen werde, auch wenn komischerweise am Ende der meisten Klassen der Titel dieses Artikels steht: File provided for Reference Use Only By Microsoft Corporation (c) 2007 ­čśë

Das alles ist cool, aber noch nicht wirklich ein Grund f├╝r einen Blogeintrag. Den folgenden Kommentar wollte ich euch aber nicht vorenthalten:

        // READTHIS READTHIS READTHIS 

        // This class has an exact mapping onto a native structure defined in COMNumber.cpp

        // DO NOT UPDATE THIS WITHOUT UPDATING THAT STRUCTURE. IF YOU ADD BOOL, ADD THEM AT THE END. 

        // ALSO MAKE SURE TO UPDATE mscorlib.h in the VM directory to check field offsets. 

        // READTHIS READTHIS READTHIS

        internal int[]  numberGroupSizes = new int[] {3};
...
        internal bool m_useUserOverride=false;    // NEVER USED, DO NOT USE THIS! (Serialized in Everett)
...

­čść

Recht auf benutzerfreundliche Software

Bei den letzten DNUGK├Âln-Treffen habe ich einen verbl├╝ffenden Satz geh├Ârt:

Jeder B├╝rger hat ein streitbares Recht auf benutzerfrendliche Software.

Die Grundlage wird durch die Bildschirmarbeitsverordnung (BildschirmarbV, Dezember 1996) geliefert. Die Liste der Anforderungen an den Bildschirmarbeitspl├Ątzen beinhaltet folegende Punkte:

21.
Bei Entwicklung, Auswahl, Erwerb und ├änderung von Software sowie bei der Gestaltung der T├Ątigkeit an Bildschirmger├Ąten hat der Arbeitgeber den folgenden Grunds├Ątzen insbesondere im Hinblick auf die Benutzerfreundlichkeit Rechnung zu tragen:
21.1
Die Software mu├č an die auszuf├╝hrende Aufgabe angepa├čt sein.
21.2
Die Systeme m├╝ssen den Benutzern Angaben ├╝ber die jeweiligen Dialogabl├Ąufe unmittelbar oder auf Verlangen machen.
21.3
Die Systeme m├╝ssen den Benutzern die Beeinflussung der jeweiligen Dialogabl├Ąufe erm├Âglichen sowie eventuelle Fehler bei der Handhabung beschreiben und deren Beseitigung mit begrenztem Arbeitsaufwand erlauben.
21.4
Die Software mu├č entsprechend den Kenntnissen und Erfahrungen der Benutzer im Hinblick auf die auszuf├╝hrende Aufgabe angepa├čt werden k├Ânnen.

Das erste, was mir – und ich bin sicher, nicht nur mir – in Sinn gekommen ist, war: wie zum Teufel konnte Windows dieser Verordnung entsprechen?? Ich denke dabei vor allem an Punkt 21.3 …

Am Ende des vorher erw├Ąhnten Satzes, gab es allerdings noch eine Bemerkung: “es wei├čt nur keiner“.

Also jetzt schon.

Off Topic: In dieser Liste gibt es noch einen letzten Punkt:

22.
Ohne Wissen der Benutzer darf keine Vorrichtung zur qualitativen oder quantitativen Kontrolle verwendet werden.

Und jetzt frage ich mich, ob all die Konzerne (Deutsche Bahn, Telekom und wie die alle hei├čen), die ihren Mitarbeiter ausgespitzelt haben, wirklich dieser Anforderung entsprechen…

40 Jahre – 1600 Kilometer

public static void Main(Events e)
{
    Wenn( e == als Steinbock-M├Ądchen in einer ungarischen Familie in Rum├Ąnien geboren)
    {
       Gl├╝cklich aufwachsen
       Ein gl├╝cklicher/in einer Diktatur dem Establishment trotzender Teenager sein 
//ohne Diktatur kann man ja nicht so gut trotzen

       /*
       Testlauf f├╝r eine Partnerschaft, Akzeptanzkriterien nicht erf├╝llt, Feature verworfen
       */

       Wenn( e == bei der einzig blutigem Umsturz in Europa in der vordersten Reihe stehen)
       {
            Sch├Ątzen lernen, was man hat und was man erk├Ąmpft hat.
            Die ersten "Communities" mitgr├╝nden
       }
       Wenn(e == !( Flie├čbandarbeiter von 7 bis 15 Uhr sein wollen ) )
       {
            Eine Uni f├╝r Maschinenbau - Fahrichtung Schienenfahrzeuge besuchen
            Coole Sachen erleben // Z├╝ge selbst fahren, z.B.
       }
       sonst
       {
           // else gibt es nicht!
       }

       Wenn(e == das beste Model "Ehemann" gefunden && Typeof(e) == Typeof(Ehemann) && e.Land == "Deutschland")
       {
           Alles neu Anfangen // Sprache, Land, Stadt ... Leben
           Wenn(Recht f├╝r ein neue Ausbildung durch Zeitarbeit erarbeitet)
           {
                Sich in die Anwendungsentwicklung-Umschulung st├╝rzen, alles lernen, was nur geht.
                Wenn( fertiggelernt )
                      9/11 => IT-Markt absturz // wie auch sonst alle Arbeitsm├Ąrkte

               Weitere Sachen lernen // wie. z.B. Lotus Notes
                Wenn(e == Job finden)
                {
                      Noch mehr lernen.
                      Job als Hobby und Hobby als Job definieren.
                      Community beitreten!
                      Noch VIEL mehr lernen.
                }
           }
           Wenn(e == es reicht, den Ehemann nur 3 Tage die Woche zu sehen, da er Consultant || TimeSpan > 6 Jahre)
           {
                 Neuen Job in K├Âln suchen //Stadt durch zweite Parameter vorgegeben
                 Sleep(7 Wochen) //Wegen ungeplanntem Beinbruch
                 super Job + super Kollegen + tolle Freunde + wunderbare Stadt + geniale Wohnung 
//Wetter k├Ânnte besser sein ...

                 Do
                 {
                      continue;

                 } while ( true );
           }
     }
}

Tausende Codezeilen verstehen – aber wie?

Nach 7 Wochen Zwangsurlaub (siehe unten) bin ich wieder back to life: mit einem halbwegs neuen Bein (mit Titaneinlagen und Schlitzschrauben ­čśë ), in einer neuen Stadt mit 100%-er Snowcoverage, in einem neuen Job – nach einem Monat Versp├Ątung.

Das letzte Mal, als ich bei einer Firma den Code verstehen musste, ging es um ASP-Classic. Die Abh├Ąngigkeiten waren ├╝berschaubar, das Debugen ging mit Response.Write-Zeilen ;). Aber wie macht man das heute, wie versteht man den bestehende Code, der in vielen Jahren aus den flei├čigen Fingern der Programmierern herausgeflossen ist? Und das im Web, in einer unglaublich flexiblen E-Commerce-Anwendung…

Die L├Âsung war einfach: mit Unit-Tests ! Ich musste nicht erkl├Ąrt bekommen haben, WAS der Code tut, nur welche Aufrufe zu welchen Ergebnissen f├╝hren. Immer, wenn ich ein Szenario verstanden habe, wurden daf├╝r Tests geschrieben und das Ergebnis besprochen. Der Begriff unit wurde teilweise nat├╝rlich ausgedehnt, aber das hat nichts an der Tatsachen ge├Ąndert, dass am Ende

  1. ich den Code verstanden habe
  2. die meisten analysierten Zeilen durch einen Test abgedeckt wurden
  3. die Diskussionen ├╝ber den Testnamen dazugef├╝hrt haben, dass manch unn├Âtige Zeilen (lese “Szenarien”) entfernt wurden, also der Code besser geworden ist

While(Ankle.IsBroken) Thread.SpinWait(1);

my broken ankle

Wie ihr seht, habe ich ein neues Dojo ausprobiert:

...
while (Ankle.IsBroken)
{
    Thread.SpinWait(1);
    Console.WriteLine("I am temporary out of order");
    KlinikumRechtsDerIsar.Heal(Ankle);
}
...

Es ist nicht mal ann├Ąhernd so lustig wie die sonstigen Dojos, bei denen ich mitgemacht habe und ich habe den starken Verdacht, dass ich auf diese Erfahrung h├Ątte locker verzichten k├Ânnen, aber wie der Revolvermann Roland Deschain von Gilead sagte:

Ka ist ein Rad, dachte er. Oder, was Eddie zu sagen pflegte: Was rumging, das kam auch wieder rum.

Ich komme auch wieder aber erst in 2-3 Wochen.

Dojo as You Go

Gestern war Dojo-Time in M├╝nchen. Kurz nach 6 haben wir uns – ich glaube wir waren 18 – bei Avanade getroffen. Ilker hat sich schon wieder ├╝bertroffen, er und Yasmine haben uns regelrecht verw├Âhnt.
Avanade Buffet
Die Location war perfekt, das Dojo das Beste an dem ich seit langem war, das Team hervorragend: begeistert und erwartungsvoll und zum Gl├╝ck gemischt. Es waren alte Hasen und “J├╝nglinge” da und wir haben richtig viel Spa├č gehabt. So “nebenbei” haben wir alle ganz sch├Ân viel gelernt ­čśë Wir haben diskutiert, geplant, gecodet. Und dann wieder diskutiert, geplant, gecodet. Und wieder, und wieder, bis um 9 Uhr.

DiskussionPlanungCode
Es war ein sehr kurzer Abend obwohl ich erst um halb eins zu hause war. Es war ein sch├Âner Abend. F├╝r mich war dieses vielleicht das letzte Dojo in M├╝nchen, ab Oktober werde ich den Yellow-Brick Road in K├Âln bestreiten. Aber das bedeutet nur einen Neuanfang, oder?

Also danke nochmal ihr M├╝nchner Dojoisten f├╝r die letzten 10 Monate, danke Ilker f├╝r die geopferten Stunden, ich wei├č, es hat dir riesen Spa├č gemacht. Mir auch ­čÖé

Webforms mit TDD entwickeln

Diese hier ist schon wieder eine wunderbare Idee aus Jimmy Nilssons Applying Domain-Driven Design and Patterns (das Buch scheint bis zur letzten Seite super Tipps zu liefern ­čśë ) und zwar von Ingemar Lundberg.

Was ist eigentlich die Aufgabe einer Webseite: irgendwelche Controls mit Text zu f├╝llen. Was dieser Text beinhaltet, dass wird von verschiedenen Funktionen entschieden. (Wie er ausgegeben wird, interessiert nicht.) Die Hauptaufgabe also bei der testgetriebenen Entwicklung von Webforms ist, diese Funktionalit├Ąten zu ermitteln und zu implementieren. So bekommt man eine Webanwendung, bei der die Hauptbereiche getestet sind und nur die eigentliche Html-Ausgabe ungetestet bleibt. Au├čerdem wird auf dieser Art sichergestellt, dass die View sonst nichts tut.

Nehmen wir ein einfaches Beispiel: das F├╝llen eines Warenkorbs. Es stehen 3 Produkte zur Auswahl und der K├Ąufer darf in seinen Warenkorb maximal 3 stellen. Um etwas Logik dabei zu haben, wird festgelegt, dass von ein Produkt nur maximal 2-mal gew├Ąhlt werden darf. Wenn diese Bedingung erf├╝llt ist, soll das Produkt nicht mehr ausw├Ąhlbar sein. Gleiches gilt, wenn im Korb bereits 3 Produkte sind, kein Produkt darf mehr ausw├Ąhlbar sein.

Shopping Cart

Das Ausw├Ąhlen eines Produktes passiert z.B. mit einem OnClick-Event auf dem Link. Aus der Sicht der Funktionalit├Ąt ist das nicht wichtig, hauptsache das Event wird ausgel├Âst.

Was tut also ein Modell um eine View zu steuern: nachdem es sichergestellt hat, dass alle Controls leer sind, l├Ądt es die Daten mit irgendeiner Repository (nennen wir sie IDeposit), gibt sie der View und veranlasst diese, die Daten zu rendern. Danach muss es die ├╝bermittelten Daten identifizieren k├Ânnen und, wenn es OK ist, muss es diese mit einer anderen Repository (die nennen wir IAcquisition) abspeichern. Mit diesem “ist OK” wird sichergestellt, dass die obigen Regeln eingehalten wurden, also dass nicht zu viele Produkte bzw. identische Produkte ausgew├Ąhlt wurden. Danach muss die View die Daten wieder rendern.

Mit diesen Informationen k├Ânnen wir bereits das Produkt und die 2 Interfaces definieren, die wir hier als Blackbox betrachten:

namespace WebformMVP.Tests
{
    //Wegen der Bedingung "nicht mehr als zwei vom selben Typ" muss eine Product-Klasse geben. Sonst w├╝rde auch ein string reichen
    public class Product
    {
        public string Name;
        public int Type;
        public Product(string name, int type)
        {
            Name = name;
            Type = type;
        }
    }

    public interface IDeposit
    {
        IList<Product> Load();
    }

    public interface IAcquisition
    {
        void Add(Product product);
    }
}

Jetzt ist endlich Zeit f├╝r den ersten Test. Wie ich schon am Anfang geschrieben habe, eine View muss einfach nur Text darstellen. Um die View simulieren zu k├Ânnen, wird sie von einem Interface abgeleitet, genauso wie die Testklasse, unsere Fakeview. Diese bekommt als Felder strings anstelle von Controls, die allerdings korrekt gef├╝llt werden m├╝ssen. Wir tun so als ob, wir abstrahieren die View auf das Minimum:

namespace WebformMVP.Tests
{
    public interface IShoppingView
    {
        void AddSourceItem(string text, bool available);
    }

    [TestFixture]
    public class Tests : IShoppingView
    {
        string m_sourcePanel;
        string m_shoppingCartPanel;

        [Test]
        public void FillSourcePanel()
        {
            m_model.Fill();
            m_model.Render();

            Assert.That(m_sourcePanel, Is.EqualTo("Product 1 available; Product 2 available; Product 3 available; "));
        }
    }
}

So wird die Anwendung nat├╝rlich nicht mal kompiliert :), dazu brauchen wir noch ein paar Schritte.

Dadurch, dass die Testklasse von diesem Interface ableitet, sind wir in der Lage, die Methoden entsprechend ├╝berschreiben zu k├Ânnen. Dieser Trick nennt sich Implement Interfaces Explicitly. Gleichzeitig lassen wir die Klasse auch von IDeposit ableiten, um auch dessen Methode zu ├╝berschreiben:

namespace WebformMVP.Tests
{
    [TestFixture]
    public class Tests : IShoppingView, IDeposit
    {
        string m_sourcePanel;
        string m_shoppingCart;
        IList<Product> m_sources= new List<Product>{new Product("Product 1", 1), new Product("Product 2", 2), new Product("Product 3", 3)};

        void IShoppingView.AddSourceItem(string text, bool available)
        {
            m_sourcePanel += text + (available ? " available;": string.Empty) + " ";
        }

        IList<Product> IDeposit.Load()
        {
            return m_sources;
        }

        [Test]
        public void FillSourcePanel()
        {
            m_model.Fill();
            m_model.Render();

            Assert.That(m_sourcePanel, Is.EqualTo("Product 1 available; Product 2 available; Product 3 available; "));
        }
    }
}

Es funktioniert immer noch nicht, wir brauchen ja noch ein Modell.

namespace WebformMVP.Tests
{
    public class ShoppingModel
    {
        public void Fill()
        {
            throw new NotImplementedException();
        }
        public void Render()
        {
            throw new NotImplementedException();
        }
    }

    [TestFixture]
    public class Tests : IShoppingView, IDeposit
    {
        ...
        private ShoppingModel m_model;

        //Es muss sichergestellt werden, dass beim Laden der View alle Felder leer sind.
        [SetUp]
        public void Setup()
        {
            m_sourcePanel = string.Empty;
            m_shoppingCart = string.Empty;
            m_model= new ShoppingModel();
        }

        [Test]
        public void FillSourcePanel()
        {
            m_model.Fill();
            m_model.Render();

            Assert.That(m_sourcePanel, Is.EqualTo("Product 1 available; Product 2 available; Product 3 available; "));
        }
    }
}

Ok, es kompiliert endlich! Aber wir gehen ja nach TDD vor, der Test ist wie gew├╝nscht rot :D. Die 2 Methoden Fill und Render sind noch nicht implementiert.
Was sollen die Methoden tun? Fill() sollte eine lokale Liste mit Hilfe der Deposit-Repository f├╝llen und Render() soll diese Elemente in das SourcePanel-Feld der View schreiben. Also muss unser Modell eine Liste, das IDeposit-Interface und das IShoppingVew als neue Member bekommen. Letzteren werden nat├╝rlich injectet (s. Dependency Inversion):

   public class ShoppingModel
   {
      IDeposit m_deposit;
      IList<Product> m_products;
      [NonSerialized]IShoppingView m_view;

      public ShoppingModel(IDeposit deposit)
      {
         m_deposit = deposit;
      }

      public void SetView( IShoppingView view )
      { 
         m_view = view;
      }
      public void Fill()
      {
         m_products = m_deposit.Load();
      }
      public void Render()
      {
         foreach( Product product in m_products )
         {
            m_view.AddSourceItem( product.Name, true );
         }
      }
   }

   [TestFixture]
   public class ShoppingCartTests:IShoppingView,IDeposit
   {
      ...
      private ShoppingModel m_model;

      [SetUp]
      public void Setup()
      {
         m_model = new ShoppingModel(this);
         m_model.SetView( this );
         m_sourcePanel = string.Empty;
         m_cartPanel = string.Empty;
      }

      [Test]
      public void FillSourcePanel()
      {
         m_model.Fill();
         m_model.Render();

         Assert.That( m_sourcePanel, Is.EqualTo( "Product 1 available; Product 2 available; Product 3 available; " ) );
      }
      ...
   }

Der Test ist gr├╝n! Jetzt ist sicher klar wie es weitergeht und ich will den Artikel nicht noch l├Ąnger machen. Hier sind also die n├Ąchsten Tests und die Implementierung dazu:

   [TestFixture]
   public class ShoppingCartTests:IShoppingView,IDeposit
   {
      private string m_sourcePanel;
      private string m_cartPanel;
      private IList<Product> m_products = new List<Product> { new Product( "Product 1", 1 ), new Product( "Product 2", 2 ), new Product( "Product 3", 3 ) };
      private ShoppingModel m_model;

      [SetUp]
      public void Setup()
      {
         m_model = new ShoppingModel(this, new Cart());
         m_model.SetView( this );
         m_sourcePanel = string.Empty;
         m_cartPanel = string.Empty;
      }
...
      [Test]
      public void AddAnItem()
      {
         m_model.Fill();
         m_model.AddAt( 0 );
         m_model.Render();

         Assert.That( m_sourcePanel, Is.EqualTo( "Product 1 available; Product 2 available; Product 3 available; " ) );
         Assert.That( m_cartPanel, Is.EqualTo( "Product 1 " ) );
      }

      [Test]
      public void AddTwoItemsOfAKind()
      {
         m_model.Fill();
         m_model.AddAt( 0 );
         m_model.AddAt( 0 );
         m_model.Render();

         Assert.That( m_sourcePanel, Is.EqualTo( "Product 1 Product 2 available; Product 3 available; " ) );
         Assert.That( m_cartPanel, Is.EqualTo( "Product 1 Product 1 " ) );
      }

      [Test]
      public void AddThreeDifferentItems()
      {
         m_model.Fill();
         m_model.AddAt( 0 );
         m_model.AddAt( 2 );
         m_model.AddAt( 1 );
         m_model.Render();

         Assert.That( m_sourcePanel, Is.EqualTo( "Product 1 Product 2 Product 3 " ) );
         Assert.That( m_cartPanel, Is.EqualTo( "Product 1 Product 3 Product 2 " ) );
      }
...
      void IShoppingView.AddCartItem( string text )
      {
         m_cartPanel += text + " ";
      }
   }
   public class ShoppingModel
   {
      IDeposit m_deposit;
      IList<Product> m_products;
      ICart m_cart;
      [NonSerialized] IShoppingView m_view;

      public ShoppingModel(IDeposit deposit, ICart cart)
      {
         m_deposit = deposit;
         m_cart = cart;
         m_view = view;
         m_products = new List<Product>();
      }
      public void SetView( IShoppingView view )
      { 
         m_view = view;
      }

      public void Fill()
      {
         m_products = m_deposit.Load();
      }

      public void Render()
      {
         foreach( Product product in m_products )
         {
            m_view.AddSourceItem( product.Name, m_cart.IsOkToAdd(product) );
         }
         foreach( Product product in m_cart.List )
         {
            m_view.AddCartItem( product.Name );
         }
      }

      public void AddAt( int index )
      {
         var product = m_products[index];
         m_cart.Add( product );
      }
   }

   public interface ICart
   {
      bool IsOkToAdd( Product product );
      void Add( Product product );
      IList<Product> List { get; }
   }

   public class Cart :ICart{

      private IList<Product> m_cartItems = new List<Product>();

      public bool IsOkToAdd( Product product )
      {
         return m_cartItems.Where( a => a.Type == product.Type ).Count() < 2 && m_cartItems.Count < 3;
      }

      public void Add( Product product )
      {
         m_cartItems.Add( product );
      }

      public IList<Product> List
      {
         get { return m_cartItems; }
      }
   }

Die einzige gr├Â├čere ├änderung zum ersten Test ist das neue ICart-Objekt. Da es hier um mehr als es eine Liste geht (irgendwo muss ja die Logik der maximal 2 gleichen Produkte pro Warenkorb errechnet werden), habe ich daf├╝r das Interface und die Klasse definiert.

Jetzt sind wir fast fertig. Es muss lediglich die abstrahierte Umgebung in eine Webanwendung nachgebaut werden. Das hei├čt, wir implementieren die Methoden Page_Load(), Pre_Render() und AddAt_Click() und die Methoden des Interface IShoppingView. Um die Kontrolle zu behalten, l├Âschen wir den Designer und schalten den ViewState aus (deswegen mag ich diesen Ingemar so sehr ­čśë ).

//default.aspx
<%@ Page Language="C#" AutoEventWireup="true" EnableViewState="false" CodeBehind="Default.aspx.cs" Inherits="ShoppingCart.Web._Default" %>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Shopping Cart</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:Panel runat="server" ID="srcPanel"></asp:Panel>
    <asp:Panel runat="server" ID="cartPanel"></asp:Panel>
    </div>
    </form>
</body>
</html>

//default.aspx.cs
using System;
using System.Collections.Generic;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace ShoppingCart.Web
{
   public class _Default : System.Web.UI.Page, IShoppingView
   {
      protected ShoppingModel model;
      protected HtmlTable srcTable, cartTable;
      protected Panel srcPanel, cartPanel;

      protected void Page_Load( object sender, EventArgs e )
      {
         if( !IsPostBack )
         {
            model = new ShoppingModel(new FakeDeposit(),new Cart());
            //Speichern, hier in Session aber sonst nat├╝rlich mit einer Repository
            Session["ShoppingModel"] = model;
            model.Fill();
         }
         else
         {
            model = (ShoppingModel)Session["ShoppingModel"];
         }
         model.SetView( this );
         ModelRender();
      }

      protected void Page_PreRender()
      {
         srcPanel.Controls.Clear();
         cartPanel.Controls.Clear();
         ModelRender();
      }

      private void ModelRender()
      {
         srcTable = new HtmlTable();
         srcPanel.Controls.Add( srcTable );
         srcTable.Width = "50%";
         srcTable.Border = 1;

         cartTable = new HtmlTable();
         cartPanel.Controls.Add( cartTable );
         cartTable.Width = "50%";
         cartTable.Border = 1;
         cartTable.BgColor = "#cccccc";

         model.Render();
      }
      public void AddSourceItem( string text, bool available )
      {
         int index = srcTable.Rows.Count;
         HtmlTableRow tr = new HtmlTableRow();
         HtmlTableCell tc = new HtmlTableCell { InnerText = text };
         if( available )
         {
            LinkButton lb = new LinkButton();
            tc.Controls.Add( lb );
            lb.ID = index.ToString();
            lb.Text = ">>";
            lb.Click += AddAt_Click;
         }
         tr.Cells.Add( tc );
         srcTable.Rows.Add( tr );
      }
      private void AddAt_Click( object sender, EventArgs e )
      {
         model.AddAt( Convert.ToInt32( ((LinkButton)sender).ID ) );
      }
      public void AddCartItem( string text )
      {
         HtmlTableCell tc = new HtmlTableCell { InnerText = text };
         HtmlTableRow tr = new HtmlTableRow();
         tr.Cells.Add( tc );
         cartTable.Rows.Add( tr );
      }
   }

   internal class FakeDeposit :IDeposit
   {
      public IList<Product> Load()
      {
         return new List<Product> { new Product( "Product 1", 1 ), new Product( "Product 2", 2 ), new Product( "Product 3", 3 ) };
      }
   }
}

Fertig. Ich muss eingestehen, als ich das Beispiel aus dem Buch nachprogrammiert habe, war ich wirklich ├╝berrascht, wie alles geklappt hat, obwohl ich w├Ąhrend des Testens keine Webseite angesprochen habe. Die Wahrheit ist, ich habe noch nie nach dem MVP-Pattern entwickelt, aber eine Webseite so aufzusetzen ist genial! Hoch lebe die Abstraktion!

Ich lade hier das Projekt hoch, vielleicht glaubt es mir jemand nicht ­čśë

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 ;).