OO-design/clean code

En god utvikler er opptatt av objektorientert design og ren kode. I denne kategorien skiver jeg om design patterns og relaterte ting. Forvent endel kode-eksempler..

1-2-3 dispatch

Har du lest A Brief, Incomplete, and Mostly Wrong History of Programming Languages ? Blogposten beskriver bl.a. Java’s fødsel på denne måten:

1996 – James Gosling invents Java. Java is a relatively verbose, garbage collected, class based, statically typed, single dispatch, object oriented language with single implementation inheritance and multiple interface inheritance. Sun loudly heralds Java’s novelty.

Dette blir veldig morsomt da det etterfølges av følgende:

2001 – Anders Hejlsberg invents C#. C# is a relatively verbose, garbage collected, class based, statically typed, single dispatch, object oriented language with single implementation inheritance and multiple interface inheritance. Microsoft loudly heralds C#’s novelty.

Da jeg leste dette slo det meg at jeg ikke ante hva single dispatch var for noe. Jeg skjønte at det var noe grunnleggende i hvordan programmeringsspråk fungerer, og ante at det hadde noe med metode/funksjonskall å gjøre, men der stoppet min kunnskap. Etter litt googling føler jeg meg nå bedre – og jeg vil nå gjerne dele det jeg har lært!

Single dispatch gir ikke så mye mening før vi ser på hva alternativet er. Multiple dispatch (eller multimethods) er en feature hvor hvilken metode som kjøres når man gjør et metodekall kan avgjøres basert på informasjon som bare er tilgjengelig under kjøring (dvs. dynamisk). I denne artikkelen vil jeg eksemplifisere både single og multiple dispatch, men også demonstrere VISITOR PATTERN, som er en teknikk vi av og til bruker for å omgå utfordringene med single dispatch.

Single dispatch

C# er et eksempel på et single dispatch-språk. “Problemet” med single dispatch, om jeg kan kalle det det, kan illustreres med koden nedenfor. Her har vi en Person-klasse som inneholder fornavn og etternavn. Deretter arver vi fra denne for å definere en russer, som i tillegg har et “patrynomisk” navn – et navn russere har som utkommer av farens fornavn. Til slutt har jeg samlet forretningslogikken for å produsere “full display name” for en person i FullNameFormatter.

public class Person
{
    public string GivenName { get; set; }
    public string Surname { get; set; }
}
 
public class Russian : Person
{
    public string PatronymicName { get; set; }
}
 
public static class FullNameFormatter
{
    public static string FullName(Person p)
    {
        return string.Format("{0} {1}", p.GivenName, p.Surname);
    }
    public static string FullName(Russian p)
    {
        return string.Format("{0} {1} {2}", 
            p.GivenName, p.PatronymicName, p.Surname);
    }
}

Jeg bruker så disse klassene til å definere et par personer, og skrive ut navnene deres..

static void Main(string[] args)
{
    List<Person> people = new List<Person>
    {
        new Person { GivenName = "Jens", Surname = "Stoltenberg" },
        new Russian { GivenName = "Boris", PatronymicName = "Nikolayevich", 
            Surname = "Yeltsin" }
    };
 
    people.ForEach(p => Console.WriteLine(FullNameFormatter.FullName(p)));
}
Output:
Jens Stoltenberg
Boris Yeltsin

Resultatet er ikke hva jeg ønsker – russerens mellomnavn kommer ikke med! Dette er fordi C# har single (statisk) dispatch: Kompilatoren kan ikke skille mellom personene i listen, behandler alle som instanser av Person, og dispatcher til “feil” metode for russeren.

Det finnes flere løsninger på dette problemet. Den mest åpenbare er kanskje å ha logikken for full name i Person-klassen, og overstyre den i Russian. Av og til er det det mest fornuftige, og føles veldig objektorientert. Men man kan ikke plassere all forretningslogikk inn i klassene, og dessuten gjør dette at vi sprer full name-logikken mellom mange, ulike filer.

En annen, vanlig løsning er å ha bare én FullName-metode, og bruke reflection – dvs. sjekke objektets egentlige type. FullName vil da typisk ha en switch med én case for hver type. Det vil si at man egentlig gjør sin egen, reflection-baserte dispatch.

Men vi har også et objektoerientert mønster for å løse problemet – det heter Visitor pattern, og det vil jeg presentere nå..

Sidebar: Multiple dispatch i C# 4.0
Interessant nok kan man med C# 4.0 enkelt “fikse” koden over til å bruke multiple dispatch, slik at fullname blir formatert riktig for russeren. Det gjør man ved å endre deklerasjonen av people fra List<Person> til List<dynamic>. .NET vil da vente til runtime med å avjøre hvilket type objekt dette er, og vet dermed at det har å gjøre med en russer når kallet til FullName skal dispatches – slik at riktig overload kalles.

Dual dispatch

I Visitor pattern bruker vi en teknikk som kalles dual dispatch – som i dette eksempelet vil sørge for at riktig overload av FullName skal bli kalt. Jeg legger til et IFullNameFormatter-interface (dette er visitoren), og FullNameFormatter må arve fra dette. I tillegg må jeg gi Person en FullName-metode som tar imot en formatter/visitor og kaller den med seg selv som parameter. Denne må russeren overstyre.

public interface IFullNameFormatter
{
    string FullName(Person p);
    string FullName(Russian r);
}
 
public class Person
{
    public string GivenName { get; set; }
    public string Surname { get; set; }
    public virtual string FullName(IFullNameFormatter formatter)
    {
        return formatter.FullName(this);
    }
}
 
public class Russian : Person
{
    public string PatronymicName { get; set; }
    public override string FullName(IFullNameFormatter formatter)
    {
        return formatter.FullName(this); // Have to override, or this will not work!
    }
}
 
public class FullNameFormatter : IFullNameFormatter
{
    public string FullName(Person p)
    {
        return string.Format("{0} {1}", p.GivenName, p.Surname);
    }
    public string FullName(Russian r)
    {
        return string.Format("{0} {1} {2}",
            r.GivenName, r.PatronymicName, r.Surname);
    }
}

Jeg kan nå formaterer navnene til personene i listen min på følgende måte:

static void Main(string[] args)
{
    List<Person> people = new List<Person>
    {
        new Person { GivenName = "Jens", Surname = "Stoltenberg" },
        new Russian { GivenName = "Boris", PatronymicName = "Nikolayevich", 
            Surname = "Yeltsin" }
    };
 
    var formatter = new FullNameFormatter();
    people.ForEach(p => Console.WriteLine(p.FullName(formatter)));
}
Output:
Jens Stoltenberg
Boris Nikolayevich Yeltsin

Når jeg nå kaller FullName på russer-objektet, vil russeren kalle riktig Fullname i formatteren – og problemet er løst. FullName-logikken er nå samlet i én klasse, men splittet tydelig opp i ulike overloads i denne klassen. Ulempen er at vi må overstyre en metode i hver subklasse av Person.

Visitor er et relativt komplisert mønster, og man bør tenke seg om to ganger før man tar det i bruk. Dual dispatch er ikke alltid helt intuitivt, spesielt for mindre erfarne utviklere på teamet, og mange kan bli nervøse når de ser visitor-klasser og accept-metoder. God navngiving, som den jeg har brukt her, kan hjelpe på dette problemet.

Multiple dispatch

Og så har vi endelig kommet til multiple dispatch. I motsetning til single dispatch vil man nå avgjøre hvilken funksjon/metode som kalles basert på data som er tilgjengelig når programmet kjøres. I stedet for at det er et en-til-en forhold mellom et metodekall og en metode, kan et metodekall nå resultere i eksekveringen av ett av en hel rekke metoder.

Clojure har støtte for multiple dispatch, og her er et eksempel på hvordan det kan utnyttes. Først definerer jeg en variabel, people, som inneholder et array av fire personer. Personene er rett og slett maps (distionaries). Alle disse fire vil kreve ulike full name-regler.

(def people
     [{:given-name "Jens" :surname "Stoltenberg" :nationality "Norwegian"}
      {:given-name "Dong" :surname "Lee" :nationality "Korean"}
      {:given-name "Boris" :surname "Yeltsin" :nationality "Russian" 
       :patronymic-name "Nikolayevich"}
      {:given-name "Dung" :surname "Nguyen" :nationality "Vietnamese" 
       :middle-name "Tan"}])

Deretter skal jeg definere en multimethod for å formatere navnet. defmulti sier hva metoden heter, og hva den skal dispatche på. I dette tilfellet vil jeg dispatche på nasjonaliteten til personen, som er en nøkkel (et felt om du vil) i person-mapen. Man kan dispatche på andre ting også, inkludert type (for eksempel java-objekt type).

Til slutt kommer de ulike “overloadene” av full-name, hvor jeg spesifiserer hvilken verdi av nasjonalitet hver enkelt metode håndterer.

(defmulti full-name :nationality)

(defmethod full-name “Korean” [user]
  (str (:surname user) ” “ (:given-name user)))

(defmethod full-name “Russian” [user]
  (str (:given-name user) ” “ (:patronymic-name user) ” “ (:surname user)))

(defmethod full-name “Vietnamese” [user]
  (str (:surname user) ” “ (:middle-name user) ” “ (:given-name user)))

(defmethod full-name :default [user]
  (str (:given-name user) ” “ (:surname user)))

Den siste overloaded er spesifisert som default, og vil håndtere alle kall til full-name hvor det ikke finnes en bedre match.

Jeg kan nå iterere over alle personene og skrive ut nasjonaliteten og full name for hver på denne måten:

(doseq [p people]
       (println (:nationality p) “:” (full-name p)))
Output:
Norwegian : Jens Stoltenberg
Korean : Lee Dong
Russian : Boris Nikolayevich Yeltsin
Vietnamese : Nguyen Tan Dung

Jeg håper du lærte noe fra denne gjennomgangen. Jeg vet i alle fall nå hva forskjellen mellom single og multiple dispatch er for noe. Jeg lærte meg også mer om hvorfor vi av og til trenger å bruke VISITOR pattern, og vet nå om én ekstra ting jeg kan bruke .NET’s nye dynamic feature til.

Fremtidige løfter

Nei, denne blogposten handler ikke om nyttårsforsetter, og heller ikke om politiske valgløfter. Den handler om to concurrency-relaterte features i Clojure som heter future og promise. Tilsvarende abstraksjoner har jeg ikke vært borti i andre språk, men til slutt i denne blogposten vil jeg implementere lignende funksjonalitet i C#.

Future er en liten makro som vil kjøre koden du gir den i en ny tråd. Resultatet av koden vil bli cachet, slik at man ikke behøver å re-kalkulere verdien hver gang man refererer den. Her er et lite eksempel:

(def radius 5.8)

(def x (future
         (Thread/sleep 1000) ; simulerer at dette tar litt tid
         (* 3.141592 radius radius)))

(time (println “Arealet er” @x))
(time (println “Arealet er” @x))

Output:
Arealet er 105.68315488
“Elapsed time: 1000.634298 msecs”
Arealet er 105.68315488
“Elapsed time: 1.096718 msecs”

Future er glimrende når man skal opprette en verdi som det tar litt tid å beregne, og man ikke nødvendigvis trenger verdien med en gang. Når man trenger verdien og den ikke er ferdig beregnet vil man måtte vente til den er ferdig. Men man kan også bruke future til samtidighet generelt, for å kjøre kode i en annen tråd, uten å bry seg om returverdien.

Promise er et løfte om at vi skal beregne en verdi på et eller annet tidspunkt. Her er et Hello World-eksempel som bruker både promise og future:

(comment “Promise that there at some point will be a
          name to greet..”)
(def name-to-greet (promise))

(comment “At some point in the future, when there is
          a name to greet, say hello”)
(future (println “Hello,” @name-to-greet))

(comment “Ask for the user’s name, and deliver on the
          promise made earlier.”)
(print “What is your name? “) (flush)
(deliver name-to-greet (read-line))

(shutdown-agents) ; Just some thread cleanup

Future brukes til å opprette en tråd hvor man trenger verdien som er lovet. Den vil stå og vente på at verdien er tilgjengelig. Når vi leverer det vi har lovet kan futuren fullføres.

Eksempelkjøring:
What is your name? Billy Bob
Hello, Billy Bob

Advarsel: Promise er merket med “Alpha – subject to change” i API-dokumentasjonen. Bruk den derfor med varsomhet.

Som nevt i innledningen vil jeg nå vise én mulig C#-implementasjon av det jeg har vist. Det er godt mulig det finnes bedre måter, og at for eksempel Task Parallel Library i .NET 4.0 kan brukes på samme måte (har ikke lekt med det enda), men jeg synes følgende abstraksjoner ble ganske så fine. Først begynner vi med en C#-variant av den første Clojure-demoen:

static void Main(string[] args)
{
    const double radius = 5.8;
    var x = Future.Create(() =>
    {
        Thread.Sleep(1000);
        return 3.141592 * radius * radius;
    });
 
    Benchmark.Measure(() => Console.WriteLine(x.Value));
    Benchmark.Measure(() => Console.WriteLine(x.Value));
}

Her brukte jeg QuickBencher (et open source prosjekt jeg har liggende på CodePlex) til å måle forbrukt tid, og verifiserte samme oppførsel som Clojure-varianten.

Før jeg viser implementasjonen av Future-klassen tar vi også med en C#-variant av promise-eksempelet:

class Program
{
    static void Main(string[] args)
    {
        var nameToGreet = new Promise<string>();
 
        Future.Create(() => Console.WriteLine("Hello {0}", nameToGreet.Value));
 
        Console.Write("What is your name? ");
        nameToGreet.Deliver(Console.ReadLine());
    }
}

Ser ganske greit ut, ikke sant? Denne koden fungerer også som den skal. Her følger til slutt implementasjonen av Promise og Future. Legg merke til at Future egentlig er to ulike klasser, hvor den ene representerer en Future som har en returverdi. Create-metodene i den ikke-generiske klassen brukes til å opprette riktig type basert på lambda-argumentet.

public class Promise<T>
{
    private T _value;
    private bool _delivered;
    public T Value
    {
        get
        {
            while (!_delivered) Thread.Sleep(10);
            return _value;
        }
    }
    public void Deliver(T value)
    {
        _value = value;
        _delivered = true;
    }
}
 
public class Future<T>
{
    private Thread _producerThread;
    private T _value;
    public T Value 
    { 
        get 
        {
            _producerThread.Join();
            return _value;
        } 
    }
    public Future(Func<T> producer)
    {
        _producerThread = new Thread(() => _value = producer());
        _producerThread.Start();
    }
}
 
public class Future
{
    public static Future<T> Create<T>(Func<T> producer)
    {
        return new Future<T>(producer);
    }
    public static Future Create(Action action)
    {
        return new Future(action);
    }
    public Future(Action action)
    {
        new Thread(() => action()).Start();    
    }
}

Promise fungerer rett og slett slik at om man forsøker å aksessere verdien før den er satt så vil den vente til den har en verdi å levere (med en optimistisk og kanskje litt naiv loop). Future fungerer ved at den eksekverer jobben den har fått i en ny tråd. Versjonen som har en returverdi er nokså lik Promise; om man forsøker å aksessere verdien sørger jeg for at tråden er ferdig med det den skal gjøre (altså produsere verdien) før jeg returnerer ved å kalle Join() på tråden.

I eksempelet hvor jeg kombinerte Promise og Future vil altså futuren stå og vente på at løftet leveres. Og løftet står og venter på at jeg taster inn navnet mitt, som jeg gjør i hovedtråden.

Spørsmål?

DCI arkitekturen

Eller: Hvordan jeg fikk sansen for multippel arv.

På QCon London fikk jeg ved en tilfeldighet med meg en halv forelesning med Jim O. Coplien, som snakket om noe han kalte The DCI Architecture. DCI står for Data-Context-Interaction, og er ifølge Coplien en bedre måte å designe systemer på enn dagens “normale” bruk av objektorientering.

“Object-oriented programming was supposed to unify the perspectives of the programmer and the end user in computer code: a boon both to usability and program comprehension. While objects capture structure well, they fail to capture system action. DCI is a vision to capture the end user cognitive model of roles and interactions between them.”

Fakta:
Opphavsmannen til DCI er professor ved Univeritetet i Oslo Trygve Reenskaug. Dette er samme nordmann som formulerte model-view-controller mønsteret i 1979.

For å få en komplett forståelse av hva denne visjonen er for noe bør du klikke på referansene i slutten av blogposten. Det du får her er en liten demo av min forståelse av DCI, gjennom at jeg ved hjelp av Ruby implementerer overføring av penger mellom to kontoer. Valget av programmeringsspråk er ikke tilfeldig – for å få til DCI er jeg avhengig av støtte for multippel arv, noe Ruby har i form av mix-ins. Og for at det skal bli virkelig elegant må jeg kunne påføre arven dynamisk i runtime.

La oss anta at vi allerede har en Account-klasse i domenet vårt som representerer en bankkonto. Den er veldig enkel (eller blodfattig som mange vil si), og har bare to read-only properties: navn og balanse. I tillegg har den en en liten metode for å representere kontoen som en streng (to_s tilvarer ToString() i C#).

account.rb:
1 class Account
2   attr_reader :name, :balance
3   def initialize name, balance
4     @name, @balance = name, balance
5   end
6   def to_s
7     #{@name}\t$#{@balance}
8   end
9 end

Når vi nå skal implementere overføring av penger mellom kontoer rører vi ikke den eksisterende Account-klassen. Vi vil i stedet implementere denne nye adferden i helt egne entiteter. Det å overføre penger er i teorien et abstrakt begrep, noe som kan foregå mellom andre ting enn bare Accounts. Vi vil derfor snakke om roller i stedet – overføring av penger har to roller: sender og mottager. Først implementerer jeg senderen, som jeg kaller for MoneySource:

money_tranfer.rb (del 1):
1 require transaction/simple
2
3 module MoneySource
4   class InsufficientFundsError < StandardError; end
5   attr_writer :recipient
6
7   def transfer amount
8     validate_transfer_of amount
9     Transaction::Simple.start(self) do |trans|
10       begin       
11         remove amount
12         @recipient.receive amount
13       rescue
14         log Aborting transfer of $#{amount} from #{name}
15         trans.abort_transaction
16       end
17     end
18   end
19   def validate_transfer_of amount
20     raise InsufficientFundsError if @balance < amount
21   end
22   def remove amount
23     @balance -= amount
24     log Removing $#{amount} from #{@name}
25   end
26 end

MoneySource har en metode kalt transfer som tar et beløp som eneste parameter. Først valideres det at det er tilstrekkelig med penger for å foreta overføringen. Deretter startes det en transaksjon (“transaction-simple” en et lite Ruby-biblotek som gir grei, in-memory transaksjonsstøtte) hvor MoneySource først fjerner det gitte beløpet fra sin egen balanse, for deretter å sende det samme beløpet til en mottager. Transaksjonen avbrytes om noe av en eller annen grunn skulle gå galt.

Merk at MoneySource ikke vet noe om Account, og Account vet ikke noe om MoneySource. Antagelsen MoneySource gjør er at den har variablene @name og @balance.., det er ikke tilfeldig at Account har det samme.

MoneySource har også en property for å sette en mottager (@recipient), og antar at denne har en receive-metode. Det er nå på tide å implementere den andre rollen, nemlig MoneyDestination:

money_tranfer.rb (del 2):
28 module MoneyDestination
29   def receive amount
30     @balance += amount
31     log Adding $#{amount} to #{@name}
32   end
33 end

I design time følger vi Single Responsibility Principle – hver modul/klasse har ett klart definert ansvar (om du lurte så er Accounts ansvar er å ha en balanse) – og vi unngår som nevnt tett kobling mellom entitetene. DCI er også en løsning som følger Open-Closed Principle, og er i høy grad en smidig arkitektur. Vi forsøker å unngå polymorfisme; alt er definert ett tydelig sted – vi har ingen virtuelle metoder som normalt gjør det vanskeligere å finne frem i koden.

“A program that follows the DCI paradigm exposes its inner workings to a reader of its code.” 

I runtime derimot vil vi i DCI-arkitekturen gi objektene roller i ulike kontekster, som lar dem samhandle på nye måter. Account er et data-objekt (D’en i DCI). MoneySource og MoneyDestination er roller som definerer interaskjonen (I’en i DCI) mellom objekter i en gitt kontekst (C’en i DCI). Rollene arves altså inn når de behøves.., et gitt objekt kan bekle mange, ulike roller. Det er her multippel arv kommer inn i bildet.

Den siste modulen jeg trenger definerer selve konteksten: MoneyTransfer. Den har en execute-metode som tar tre parametre: et objekt som skal være kilde, et objekt som skal være destinasjon, og beløpet som skal overføres. Execute utvider kilden med MoneySource (linje 37: MoneySource-modulen mikses inn i source-objektet i runtime). På samme måte utvides destinasjonen med MoneyDestination. Source har nå fått en property recipient og en metode transfer (som tidligere definert i MoneySource). Disse benyttes i linje 39 og 40 til å utføre overføringen.

money_tranfer.rb (del 3):
35 module MoneyTransfer
36   def self.execute source, target, amount
37     source.extend MoneySource
38     target.extend MoneyDestination
39     source.recipient = target
40     source.transfer amount
41   end
42 end

I denne demoen er source og target Account-objekter, men de behøver ikke være det.

Her er et lite skript som bruker MoneyTransfer. De fleste detaljene er uvesentlige og er derfor utelatt.

tranfer.rb:
47 require account
48 require money_transfer
49
50 setup_accounts # details omitted
51 list_accounts # details omitted
52 source = get_account Select account to transfer money from
53 target = get_account Select account to transfer money to
54 amount = get_amount  Specify amount to transfer
55 MoneyTransfer.execute(source, target, amount)
56 list_accounts

Jeg setter opp noen kontoer, lister dem i konsollet, og ber brukeren om å spesifisere source, target og amount. Jeg kaller så MoneyTransfer.execute, og lister kontoene igjen. Her er et eksempel på en slik overføring:

Sample run #1:
C:\Users\tormar\ruby_projects\DCI>transfer.rb
0: Salery       $1000
1: Usage        $1000
2: Savings      $1000
Select account to transfer money from: 1
Select account to transfer money to: 2
Specify amount to transfer: 800
Tue Mar 16 16:24:07 +0100 2010: Removing $800 from Usage
Tue Mar 16 16:24:07 +0100 2010: Adding $800 to Savings
0: Salery       $1000
1: Usage        $200
2: Savings      $1800

Siden jeg implementerte transaksjonshåndtering vil jeg også demonstrere hva som skjer om target av en eller annen grunn skulle kaste et exception (selv om det ikke har så mye med temaet å gjøre). Den røde logge-linjen kommer fra MoneyDestination:

Sample run #2:

C:\Users\tormar\ruby_projects\DCI>transfer.rb
0: Salery       $1000
1: Usage        $1000
2: Savings      $1000
Select account to transfer money from: 0
Select account to transfer money to: 2
Specify amount to transfer: 1000
Tue Mar 16 16:28:11 +0100 2010: Removing $1000 from Salery
Tue Mar 16 16:28:11 +0100 2010: Error: not able to receive money right now
Tue Mar 16 16:28:11 +0100 2010: Aborting transfer of $1000 from Salery
0: Salery       $1000
1: Usage        $1000
2: Savings      $1000

DCI er altså en reaksjon på det “oppfinnerne” ser på som en gal bruk av objektorientering. De hevder at måten vi begynte å bruke polymorphism, coupling, cohesion, etc da vi oppdaget OO på 80- og 90-tallet strider mot objektorienteringens mål, som var å gi et bedre samsvar mellom software og folks mentale modell av virkeligheten. De vil gjøre systemets adferd mer eksplisitt ved å gjøre roller til fullverdige entiteter – løsrevet fra objektene.

Dette får meg til å tenke på Command Query Responsibility Segregation (CQRS) som er så i skuddet for tiden – er ikke det også på en måte en reaksjon på det samme? Der gjør man i alle fall adferden eksplisitt gjennom command-objekter, og skreddersyr øvrig domenemodell i forhold til dem.

En siste tankevekker: Coplien og Reenskaug er to av dem som (såvidt jeg forstod) hardnakket hevder at språk som Java og C# ikke er orntlige, objektorienterte språk. I C# dreier egentlig alt seg om design av klasser, mens objekter har en mere sentral posisjon i språk som Ruby – hvor de f.eks. kan endre karakter fullstendig under kjøring av programmet. Man bryr seg sjelden om hvilken type et objekt har i Ruby, bare hvilke egenskaper det tilbyr. Jeg vet ikke om det er så fruktbart å stå på den barikaden, men det er interessant å diskutere forskjellen.

“Roles are about objects and how they interact to achieve some purpose. For thirty years I have tried to get them into the into the main stream, but haven’t succeeded. I believe the reason is that our programming languages are class oriented rather than object oriented. So why model in terms of objects when you cannot code them? And why model at all when you cannot keep model and code synchronized?” –Trygve Reenskaug

Til slutt må jeg få gjenta det jeg sa innledningsvis, ikke døm DCI ut fra det du har sett her. Dette har vært min tolkning, så gå til kildene for en dypere forståelse, som er: The DCI Architecture: A New Vision of Object-Oriented Programming | The Common Sense of Object Oriented Programming (pdf) | DCI på wikipedia | Trygve Reenskaug (UiO)

En minimal http-server i .Net

.Net-rammeverket er fullt av moduler for å lage webtjenester; du kan bruke WebForms eller ASP.NET MVC, SOAP web services eller WCF, RIA services eller Astoria data services, alt etter dagsform og hvilket behov du har. Noen ganger kan det derimot være greit å vite hvordan man på aller enklest måte kan lage en http-basert server. I Chrome-vinduet under ser du hvordan jeg aksesserer en “no-fuss” service som kan fortelle meg hva klokka er…

CropperCapture[48]

I denne artikkelen presenterer jeg den ikke så veldig godt kjente klassen HttpListener (i System.Net namespacet), og viser hvordan man enkelt kan bruke den til å lage en slags webserver. Denne teknikken kan være aktuell om man f.eks. skulle trenge å raskt mocke opp noen webservicer som ikke følger de vanlige standardene, eller om man skal lage moduler som kommuniserer over http med en proprietær protokoll.

Men jeg har mer på lur: Http-serveren jeg presenterer her er designet for å være utvidbar, og bruker derfor STRATEGY PATTERN i håndteringen av forespørslene. Du vil også få se hvordan jeg kombinerer attributter og reflection for å kunne dynamisk legge til ny adferd uten å måtte editere eksisterende kode.

Her følger selve server-klassen: SimpleHttpServer. Det sentrale skjer i linje 30 til 33, hvor jeg oppretter en HttpListener, registrerer adresse og port for lytting, og starter å lytte. Dette tilsvarer mer eller mindre hvordan Internet Information Server (IIS) selv registrerer seg for lytting mot operativsystemet.

    9 public class SimpleHttpServer

   10 {

   11     private string _address;

   12     private int _port;

   13     private ResponderFactory _dispatcher;

   14     public SimpleHttpServer(string address, int port, ResponderFactory dispatcher)

   15     {

   16         _port = port;

   17         _address = address;

   18         _dispatcher = dispatcher;

   19     }

   20 

   21     private HttpListener _httpListener;

   22     public void Start()

   23     {

   24         StartHttpListener();

   25         while (true)

   26             WaitForRequestThenHandle();

   27     }

   28     private void StartHttpListener()

   29     {

   30         _httpListener = new HttpListener();

   31         _httpListener.Prefixes.Add(

   32             String.Format(“http://{0}:{1}/”, _address, _port));

   33         _httpListener.Start();

   34     }

   35     private void WaitForRequestThenHandle()

   36     {

   37         var incomingRequestContext = _httpListener.GetContext();

   38 

   39         ThreadPool.QueueUserWorkItem((state) =>

   40         {

   41             try

   42             {

   43                 var context = state as HttpListenerContext;

   44                 var url = context.Request.Url;

   45                 var encoding = context.Request.ContentEncoding;

   46 

   47                 var result = _dispatcher

   48                     .GetResponder(GetCommandKey(url, encoding))

   49                     .RespondTo(GetCommandArguments(url, encoding));

   50 

   51                 Respond(context, encoding, result);

   52             }

   53             catch (Exception ex)

   54             {

   55                 // Don’t want the service to die, just log it..

   56                 Console.WriteLine(ex.ToString());

   57                 // Could then responde with some error code…

   58             }

   59         },

   60         incomingRequestContext);

   61     }

   62 

   63     private static string GetCommandKey(Uri url, Encoding encoding)

   64     {

   65         // AbsolutePath always starts with a ‘/’

   66         return HttpUtility.UrlDecode(url.AbsolutePath.Substring(1), encoding);

   67     }

   68     private static string GetCommandArguments(Uri url, Encoding encoding)

   69     {

   70         // Query always starts with a ‘?’, but may be null

   71         return url.Query != null && url.Query.Length > 1

   72                         ? HttpUtility.UrlDecode(url.Query.Substring(1), encoding)

   73                         : string.Empty;

   74     }

   75     private static void Respond(HttpListenerContext context, Encoding encoding, string result)

   76     {

   77         var bytes = encoding.GetBytes(result);

   78         context.Response.ContentLength64 = bytes.Length;

   79         context.Response.OutputStream.Write(bytes, 0, bytes.Length);

   80         context.Response.StatusCode = 200; // everything is ok :)

   81         context.Response.Close();

   82     }

   83 }

Etter å ha opprettet HttpListener kjører jeg en uendelig løkke som mottar innkommende forespørsler og håndterer dem (en robust implementasjon ville også hatt en Stop-metode som terminerte løkken). Kallet til GetContext() i linje 37 returnerer når en request mottas, og så bruker jeg ThreadPool til å spawn’e en ny tråd som håndterer den og svarer tilbake.

Denne serveren bruker selve URL’en til å avgjøre hva den skal gjøre. I eksempelet i starten av artikkelen var requesten http://127.0.0.1:8081/time?. Alt etter domenet og porten men før spørsmålstegnet bruker jeg til å avgjøre hvilken Responder som skal brukes. Det er her STARTEGY pattern kommer inn i bildet – en responder er en klasse som implementerer interfacet Responder

    5 public interface Responder

    6 {

    7     string RespondTo(string arguments);

    8 }

    5 public interface ResponderFactory

    6 {

    7     Responder GetResponder(string responderKey);

    8 }

Argumentene som sendes til responderen er alt som kommer etter spørsmålstegnet i requesten. HttpListener støtter mye mer enn dette, men alt jeg er interessert i denne gangen er selve URL’en.

SimpleHttpServer ble initialisert med en ResponderFactory. Denne vil – gitt en nøkkel – returnere riktig responder. Ta en titt til på serveren om du ikke fikk det helt med deg første gangen, spesielt linje 47 til 49.

En første versjon av ResponderFactory (den faktiske implementasjonen kommer lengre nede) kunne vært en klasse som uansett nøkkel returnerte en UnknownCommandResponder:

    5 public class UnknownCommandResponder : Responder

    6 {

    7     private string _request;

    8     public UnknownCommandResponder(string request)

    9     {

   10         _request = request;

   11     }

   12     public string RespondTo(string arguments)

   13     {

   14         return String.Format(“Unknown command: ‘{0}’ with arguments ‘{1}’”,

   15             _request,

   16             arguments);

   17     }

   18 }

Det kan være greit å ha en slik default strategi/responder til å svare på alle mulige ting man måtte finne på å etterspørre. Her ser du den i aksjon:

CropperCapture[46]

Opprette flere tjenester: Attributter og reflection

Før jeg legger til den første “fornuftige” responderen oppretter jeg et .net-attributt. Det trenger ikke være noe mer avansert enn å lage en klassen som arver fra Attribute.

    6 public class RespondToAttribute : Attribute

    7 {

    8     public string Key { get; set; }

    9     public RespondToAttribute(string key) {

   10         Key = key;

   11     }

   12 }

Jeg har laget svært få attributter i min karriære, men det er en teknikk som kan gi veldig elegante løsninger om det brukes riktig. Måten jeg bruker det på her er ganske vanlig. Og enkel! Den vil rett og slett la meg legge til nye respondere uten at jeg behøver å endre noen eksisterende kode.

Men first thing first: Når jeg implementerer mine respondere vil RespondToAttribute la meg spesifisere hvilken nøkkel hver responder gjelder for. Her er f.eks. en enkel responder som legger sammen en rekke med tall separert med komma:

    6 [RespondTo("add")]

    7 public class Add : Responder

    8 {

    9     public string RespondTo(string arguments)

   10     {

   11         int result = 0;

   12         Array.ForEach(arguments.Split(‘,’),

   13             (arg) => result += Int32.Parse(arg));           

   14         return string.Format(“The answer is {0}”, result);

   15     }

   16 }

(Det er en konvensjon i .Net at man slipper å skrive “Attribute”-delen av attributt-navnet. Dermed blir linje 6 så fin.., denne klassen “responderer på add”.)

Når jeg gjør ting som dette er det typisk endel implisitte regler jeg må følge. I dette tilfelle vil det for eksempel ikke gi mening om mer enn én klasse responderer på “add”. Nøkkelen må med andre ord være unik. For å håndheve slike regler oppretter jeg som regel validerende enhetstester. Følgende test bruker reflection til å hente ut alle typer i prosjektet, samle opp alle RespondToAttributes fra typene, og sjekke at de er unike..

   12 [Test]

   13 public void Should_all_be_unique()

   14 {

   15     var keys = new List<string>();

   16     var allTypes = Assembly.GetExecutingAssembly().GetTypes();

   17     foreach (var t in allTypes)

   18     {

   19         var attribute = Attribute

   20             .GetCustomAttribute(t, typeof(RespondToAttribute))

   21             as RespondToAttribute;

   22 

   23         if (attribute == null)

   24             continue;

   25 

   26         if (keys.Contains(attribute.Key))

   27             Assert.Fail(attribute.Key + ” appear more than once!”);

   28 

   29         keys.Add(attribute.Key);

   30     }

   31 }

Og denne samme teknikken vil jeg bruke når jeg implementerer den endelige ResponderFactory-klassen. Den oppretter en dictionary med nøkler mappet til respondere (eller mer nøyaktig mappet til funksjoner som oppretter respondere).

    7 public class ReflectiveResponderFactory : ResponderFactory

    8 {

    9     private Dictionary<string, Func<Responder>> _dispatchTable;

   10 

   11     public ReflectiveResponderFactory()

   12     {

   13         _dispatchTable = new Dictionary<string, Func<Responder>>();

   14         Array.ForEach(Assembly.GetExecutingAssembly().GetTypes(),

   15             (type) => AddDispatchIfResponder(type, GetResponderInfo(type)));

   16     }

   17     private static RespondToAttribute GetResponderInfo(Type maybeResponderType)

   18     {

   19         return Attribute.GetCustomAttribute(maybeResponderType,

   20             typeof(RespondToAttribute)) as RespondToAttribute;

   21     }

   22     private void AddDispatchIfResponder(Type type, RespondToAttribute responderInfo)

   23     {

   24         if (responderInfo != null)

   25             _dispatchTable.Add(

   26                 responderInfo.Key,

   27                 () => Activator.CreateInstance(type) as Responder);

   28     }

   29 

   30     public Responder GetResponder(string responderKey)

   31     {

   32         if (!_dispatchTable.ContainsKey(responderKey))

   33             return new UnknownCommandResponder(responderKey);

   34         return _dispatchTable[responderKey].Invoke();

   35     }

   36 }

Her forutsettes det at alle respondere (med RespondTo-attributt) har en default, parameter-løs konstruktør, slik at den kan opprettes i lambda-uttrykket i linje 27. Du bør opprette en enhetstest for å validere dette også, slik at ingen lager en responder med konstruktør-parametre i fremtiden og dermed introduserer en bug (ikke at de ikke ville ha oppdaget det, men rask tilbakemelding er alltid kjekt).

Om du nå har skjønt hvordan disse klassene henger sammen gjenstår det bare å opprette og starte en SimpleHttpServer for at dette skal fungere. Her kjører jeg serveren i et konsollprogram, men i et mer realistisk senario vil du typisk kjøre den i en enkel Windows service.

    7 static void Main(string[] args)

    8 {

    9     new SimpleHttpServer

   10         (“*”, 8081, new ReflectiveResponderFactory())

   11         .Start();

   12 }

Og nå kan vi endelig få vite hvor mye 10 + 20 + 30 er…

CropperCapture[47]

For å utvide serveren med mer funksjonalitet er det nå bare til å opprette flere klasser som arver fra Responder-interfacet, og legge til et RespondToAttribute. ReflectiveResponderFactory vil finne og registrere den nye klassen under oppstart, og delegere til en ny instans av responderen om nøkkelen kommer i en forespørsel.

Jeg håper noen klarte å henge med óg få noe fornuftig ut av dette. Mer info om HttpListener finner du her, og info om .net attributes finner du via google. I neste blogpost vil du få se hvordan jeg implementerer nøyaktig samme funksjonalitet ved hjelp av Ruby.

Knagger: , ,

Enkle knep for bedre objektorientering

Konseptet objektorientering (OO) ble født allerede på 60-tallet; Simula, som ble utviklet på Norsk Regnesentral med Ole-Johan Dahl og Kristen Nygaard i spissen, var det første objektorienterte programmeringsspråket. Det var likevel ikke før på 90-tallet at OO ble mainstream. I dag foregår det meste av moderne systemutvikling i objektorienterte språk.

Men det at man bruker et slikt språk er ikke det samme som at man driver med god objektorientering.

Objektorientering er å innkapsle detaljer! Man skjermer andre (objekter) fra å se/vite om detaljene, slik at de ikke gjør seg avhengig av disse detaljene. På den måten blir det større rom for å gjøre endringer – løsningene blir mere fleksible fordi man enkelt kan endre detaljene uten at det får bi-effekter. God OO skjuler detaljer godt, dårlig OO skjuler detaljer mindre godt.

Et ganske vanlig design som ikke er bra OO er systemer som inneholder to fundamentalt forskjellige typer objekter: (1) entiteter som først og fremst er bærere av data – som inneholder properties, og ikke så mye mer, og (2) objekter man typisk kaller controllere (eller handlere eller managere) som bruker den første typen, og som inneholder all logikken i systemet. Dette er et prosedyreorientert system, og det man bedriver kalles imperativ programmering – man instruerer hele tiden programmet om hva som skal gjøres, i stedet for å be objektene selv om å gjøre jobben.

Hvordan praktisere god OO?

Jeg forsøker hele tiden å lære både meg selv og andre hvordan man designer et system etter gode OO-prinsipper, og tror jeg har kommet frem til noen av de mest grunnleggende retningslinjene man bør følge – noen enkle huskeregler, eller knep – man bør ha i bakhodet når man programmerer. Prinsippene er ikke noe jeg selv har funnet på (langt i fra), men representerer de enkleste prinsippene man kan bruke hele tiden for å gå fra prosedyreorientering til god objektorientering.

Det første prinsippet er TELL, DON’T ASK! Si at objektet skal gjøre noe for deg, ikke spør det om informasjon. Implisit i dette er blant annet at man skal unngå getters og setters, dvs. properties i .net-klasser. Properties brukes til å spørre om et objekts tilstand, eller til å eksplisit endre tilstanden, og da skjuler man ikke lenger hvordan objektet fungerer.

Hvis du har kode som tar avgjørelser basert på tilstanden til et objekt, se om du i stedet kan flytte denne koden inn i objektet selv. Har du et slikt prosedyreorientert system som jeg nettopp beskrev, så forsøk å flytte kode ut fra handlerne (type 2) til dataobjektene (type 1). Gjør du det vil du snart se at propertiene blir overflødige.

Eller gå motsatt vei: Hvis du tar utgangspunkt i et objekt som nesten bare inneholder properties – undersøk hvor disse propertiesene brukes, og se om du kan flytte den koden til objektet.

Her følger et banalt eksempel. Du finner sjelden kode som er så tydelig feilplassert som dette her i ditt eget design, men det illustrerer i alle fall poenget mitt:

    1 public class DogManager

    2 {

    3     private void Manage(Dog dog)

    4     {

    5         if (dog.IsSleepy) dog.Sleep();

    6         else if (dog.IsHungry) dog.Eat();

    7         else if (dog.IsRestless) dog.BegOwnerForWalk();

    8         else dog.SniffSomething();

    9     }

   10 }

DogManager spør hunden om dens tilstand, og tar avgjørelser om hva den skal finne på basert på dette. Koden bør flyttes til Dog:

    1 public class Dog

    2 {

    3     public void DoSomething()

    4     {

    5         if (IsSleepy) Sleep();

    6         else if (IsHungry) Eat();

    7         else if (IsRestless) BegOwnerForWalk();

    8         else SniffSomething();

    9     }

Et prinsipp som er nært knyttet til Tell, Don’t Ask er LAW OF DEMETER (LoD). Denne “loven” sier at man skal begrense hva objekter vet om resten av systemet. Enheter (objekter/metoder) skal kun snakke med sine venner, ikke fremmede. Man begrenser altså koblingen mellom enhetene i systemet, og oppnår dermed blant annet færre bieffekter ved endringer.., slik som jeg var inne på tidligere.

LoD sier mer konkret at man i en gitt metode (M) i et objekt (O) kun kan kalle metoder på:
1) Objektet O selv
2) Parametre som sendes til metoden M
3) Objekter som opprettes i metoden M
4) Objekter som lever i O (objektets private variabler)

Man skal altså ikke kalle metoder på objekter man får returnert fra andre (via properties eller metodekall). Husker man hele tiden på dette når man koder vil man oppnå bedre OO. Her er en ny, banal illustrasjon:

    1 private void WalkTheDog(Dog theDog)

    2 {

    3     theDog.Legs.Move(); // violates LoD

    4 }

    5 

    6 private void WalkTheDog2(Dog theDog)

    7 {

    8     theDog.Walk(); // makes more sense..

    9 }

Det gir ikke mening at denne metoden skal vite noe om hvordan hunden fungerer – at bena må bevege seg for at hunden skal gå. Be heller hunden om å gå direkte, det er hunden som selv skal vite hvordan den fungerer (detaljene skal innkapsles og skjules).

Legg også merke til at brudd på LoD ofte kan gjenkjennes ved flere punktum: theDog punktum Legs punktum Move(). Dog er metodens nærmeste venn, Legs er en fremmed, og skal ikke kommuniseres med.

Den tredje og siste huskeregelen som vil gjøre deg til en bedre utvikler er at OO ikke nødvendigvis skal modellere virkeligheten! Dette er en vanlig misforståelse som mange av oss fikk innprentet da vi begynte å lære objektorientert programmering. Om vi begynner å designe et system ved å lage klasser av alle entitetene vi kan observere i det domenet vi skal modellere kommer vi ofte skjevt ut. Begynn i stedet med adferden du ønsker, og se hvilke objekter som “naturlig” kommer ut av det.

Skal vi for eksempel designe et kontrollsystem for en bil kan det være veldig feil å starte med å lage klasser for hjul, bremser, ratt, motor, bensintank, gir og bilen selv. Et system med klasser som drivstoffregulator, bremsekraftforsterker, oljenivå og servostyring vil kanskje bli enklere og mere oversiktelig. Testdreven utvikling (TDD) vil kunne hjelpe deg til å drive frem det beste designet.

På tide med en Oppsummering

De tre huskereglene jeg har presentert, sammen med de to kanskje aller viktigste programmeringsprinsippene – Single Responsibility (SRP) og Don’t Repeat Yourself (DRY) – vil sørge for at du designer kode som er enklere å forstå, og lar seg vedlikeholde, endre og utvide.

Fortell objektene hva de skal gjøre på enklest mulig måte, ikke spør de hva de holder på med. Mange properties (eller andre typer gettere og settere) betyr sansynligvis at du kan gjøre designet bedre. Ikke la objektene dine snakke med fremmede. Og ikke begynn med å modellere virkeligheten, driv i stedet frem objekter du trenger gjennom å starte med implementering av adferd.

Helt til slutt: Jeg snakker her om hvordan ideell, objektorientert kode ser ut. OO er ikke den optimale løsningen for alle problemer. Lager du f.eks. en enkel CRUD-applikasjon er full objektorientering overkill. OO egner seg bedre jo mere adferd systemet skal ha. Se for eksempel posten om Greg Young’s foredrag i Bergen, hvor han bruker OO på “command-siden” av systemet, men ikke på “query-delen”.

Noen eksterne lenker: Law of Demeter | Tell, Don’t Ask | Object-oriented programming | Single Responsibility Prinsiple | Don’t Repeat Yourself

PS: Denne blogposten begynte som en intern ZipTalk jeg holdt for utviklingsavdelingen i PSWinCom. Tittelen var da “OO > O”.

Skummelt med virtuelle metoder

Ta en titt på følgende program, og se om du kan utlede hva output blir når det kjøres.

    1 class SomeClass

    2 {

    3     protected SomeClass()

    4     {

    5         SomeFunction();

    6     }

    7     protected virtual void SomeFunction()

    8     {

    9         Console.WriteLine(“SomeFunction in SomeClass”);

   10     }

   11 }

   12 

   13 class SomeDerivedClass : SomeClass

   14 {

   15     private readonly string msg;       

   16 

   17     public SomeDerivedClass(string msg)

   18     {

   19         this.msg = msg;

   20     }

   21     protected override void SomeFunction()

   22     {

   23         Console.WriteLine(msg);

   24     }

   25 }

   26 

   27 class Program

   28 {

   29     static void Main(string[] args)

   30     {

   31         var d = new SomeDerivedClass(“Constructed in main”);

   32         Console.ReadLine();

   33     }

   34 }

Ikke juks! Hva tror du det blir? Jeg skal vedde på at i alle fall forfatteren av SomeDerivedClass håper output blir “Constructed in main”. Det blir det derimot ikke – Dette programmet har ikke noe annet output enn en newline!

Det som skjer når man oppretter objektet i linje 31 er at konstruktøren i linje 17 kalles. Men før den kan eksekvere innholdet i linje 19 kaller den implisit konstruktøren til baseklassen – linje 3. Denne konstruktøren kaller SomeFunction, men siden dette er et objekt av type SomeDerivedClass vil den ikke kalle SomeFunction i SomeClass, den vil kalle SomeFunction i SomeDerivedClass. Problemet er at den metoden bruker en instansvariabel som settes i konstruktøren, men linje 19 har ikke blitt kjørt enda. this.msg er derfor null i det øyeblikket den skrives ut.

Om msg hadde hatt en “default-verdi”, ved at linje 15 for eksempel så ut som nedenfor, ville det vært den verdien som ble output.

   15 private readonly string msg = “Set by initializer”;

Denne oppførselen gir logisk sett mening, men er veldig skummel fordi de som sub-klasser SomeClass ikke forventer at det skal fungere slik. I instans-metoder forventer man at konstruktøren har blitt kjørt, slik at man kan benytte seg av tilstand som settes der.

Man bør derfor aldri kalle virtuelle (inkludert abstrakte) metoder i konstruktører, fordi dette fører til en eksekveringsrekkefølge det er vanskelig å holde oversikt over. Don’t go down the rabbit hole! Generelt sett bør konstruktører være reservert til å sette den initielle tilstanden til objektet, som normalt sett betyr å lagre referanser til kontruktørens argumenter. Konstruktører skal ikke ha oppførsel.

Bruker du ting som FxCop eller Code Analysis i Visual Studio vil du også bli advart om slike situasjoner. Ta advarselen seriøst!

CropperCapture[43]

Greg Young slapp løs domenet sitt på NNUG i Bergen

gregyoung_at_nnug

Greg Young er et kjent navn innen Domain-Driven Design. For tiden er han frivillig hjemløs og “arbeidsløs”, og reiser rundt i verden og holder foredrag og kurs mens han forsøker å skrive bok. Nå er han i Norge, og sist onsdag hadde vi gleden av å ha ham som gjest på NNUG i Bergen.

Først holdt Greg en liten apell han kalte “The Great Failure”. Han sier at det overskyggende flertall av systemer vi lager i softwarebransjen i dag er CRUD applikasjoner. Vi har lurt våre kunder til å tro at det er det de vil ha – mens systemen gjør brukerne langt mindre effektive enn det de kunne ha vært.

gy_explicit Årsaken til dette er den posisjonen vi har gitt relasjonsdatabasen, som vi bruker langt mer enn vi burde. Vi burde bry oss mer om hvilke handlinger brukerne våre skal utføre, om intensjonen bak handlingene. Vi burde designe software’n basert på adferd, og modellere adferden eksplisitt. Dette er noe av motivasjonen bak DDD, og spesielt den formen for DDD som Greg står for.

Deretter fortsatte han med å gi en innføring i noen sentrale elementer innen DDD – først og fremst Aggregate roots og Bounded Context – slik at publikum ikke skulle falle helt av i den siste og mest spennende sesjonen…

…som omhandlet noen av de nyvinningene som har kommet til DDD i årene etter at Eric Evans skrev den berømte boken, nyvinninger som Greg Young er en av personene bak. Jeg gjør et tappert forsøk på å oppsummere hva han snakket om; det vil ikke bli en erstatning for å høre på Greg selv, men kanskje en god “teaser”. De fire konseptene han introduserte, som alle henger mer eller mindre sammen, var:

1. Explicit State Representation

Dette dreier seg om å modellere endringer i datamodellen eksplisit. “Make the implicit explicit”, sa han gjentatte ganger. Han eksemplifiserte dette bl.a. med en prosess for en webshop:

Først oppretter man en handlevogn – det er én handling. Deretter legger man kanskje til 2 varer i handlevognen. Det er en ny handling, og representeres ved et state-objekt med de to varene. Deretter bestemmer man seg kanskje for å fjerne en av varene. I en normal CRUD app ville man gjort dette som en update av handlevognens innhold, og da ville man ha mistet viktig informasjon, som at det først var lagt til to varer. Greg ville ha laget et nytt state-objekt som representerte handlingen “å fjerne en vare”.

På den måten er audit-loggen bygget inn i hvordan systemet fungerer. Og man kan senere “spille tilbake” alt som har skjedd. Greg argumenterte meget godt for hvorfor dette er en fremgangsmåte som gir mening for mange systemer.

2. Event Storage

Event storage henger sammen med det forrige punktet: Greg snakket om å designe software med en “write once” arkitektur, dvs bruk av en add-only datamodell, hvor updates ikke er mulig. Den fullstendige tilstanden til modellen lagres ikke i basen – i stedet lagrer man serien med tilstands-endringer, som gir nå-tilstanden når de spilles av i riktig rekkefølge.

Dette vil bl.a. løse alle samtidighetsproblemer som normale CRUD-systemer sliter med, fordi to prosesser aldri behøver å skrive til samme record. Dessuten kan det garantere at audit’en er 100% riktig (om state-objektene lagres til et write once medium).

Greg bruker kun to tabeller for å persistere sin domenemodell: Den første tabeller lagrer aggregate roots (samling med relaterte objekter) i et serialisert format, og de lastes alltid sammen. Den andre tabellen inneholder events – dvs. tilstands-endringer. Når en enumerasjon av events lastes opp og leveres til aggregatet, spilles de av internt, og aggregatet får dermed riktig nå-tilstand.

3. Command Query Separation

Command-query separation (CQS) er et generelt prinsipp vi kjenner fra Bertrand Meyer, som sier at metoder enten skal være en kommando eller en spørring. Helt konkret betyr dette at en hvilken som helst metode enten endrer tilstand (objektets state eller har andre effekter som disk IO eller lignende) eller returnerer noe. CQS er f.eks. et av prinsippene Uncle Bob trekker frem i Clean Code, og jeg forsøker å følge det i all kode jeg produserer.

I denne sammenhengen betyr CQS noe mer. Greg snakker om å dele systemet en konstruerer i to deler: én del som er skreddersydd og optimalisert for å hente ut data, og en annen del som er designet for å utføre handlinger og endre data. Query/read-delen av systemet kan holdes tynt, og leverer rett og slett DTO’er (data transfer objects). Det er i command/write-delen av systemet at Greg vil benytte prinsippene i Domain-Driven Design – god objektorientering er “overkill” i read-delen, og kommer mye bedre til sin rett i et system som kun skal håndtere kommandoer.

gy_architecture

4. Asynchronous Context Mapping

Dette dreier seg om å skalere DDD. I arkitektur-tegningen over ser vi hvordan et system er delt i to delsystemer – ett for reads (query) og ett for writes (commands). De kunne brukt den samme datakilden, men ved å gi dem hver sin database bryter man den sterke avhengigheten mellom dem. Read-systemet henter endringer i Write-systemet asynkront (pull i stedet for push). Vi har dermed introdusert “relaxed consistency” (også kalt Eventually consistant) – og dataene vil ikke være korrekte i et hvilket som helst øyeblikk.

gy_slas Det finnes et teorem som kalles CAP, som sier at et system kan ikke ha høy konsistens (Consistency), tilgjengelighet (Availability) og skalerbarhet (Partition-tolerance) samtidig. Ved å redusere konsistensen bare bittelitt kan vi derimot øke den potensielle skalerbarheten og tilgjengeligheten ganske mye. Det er derfor Greg fokuserer på SLA’er (Service Level Agreements). Han sier vi bør definere hva som er akseptabel SLA for alle operasjoner i det totale systemet vi designer, og bruke det til å dele opp i mindre systemer som kommuniserer asynkront.

På denne måten er vi heller ikke lengre avhengige av det svakeste leddet – ett system kan gå ned uten at resten blir påvirket, fordi vi følger en pull-modell. Dette har også mye til felles med best-practise SOA.

SLA’ene vil også fortelle oss hva som er de viktigste delene av systemet vi bygger. Der hvor vi har de strengeste kravene til oppetid og respons setter vi inn den største innsatsen. Greg påpeker at man som regel kan operere med avslappet konsistens mellom de fleste bounded contexts. Derimot vil vi måtte behøve å etablere kompanserende handlinger for å håndtere det at dataene ikke nødvendigvis er up-to-date til enhver tid.

Og så får du mulighet til å lære disse teknikkene…

NNUG Bergen tilrettelegger nå for et 2-dagers kurs med Greg Young hvor du får mulighet til å lære denne skalerte DDD-modellen. Dette er en unik mulighet til gi-bort-pris som jeg må innrømme frister veldig mye. Jeg jobber nå på et system som har mye til felles med det Greg har erfaring med, og jeg tror disse teknikkene kunne tilført nyttig kunnskap som over tid kunne tatt arbeidet vårt opp på et nytt nivå. Men det ser desverre ikke ut som om vi er i en sitasjon hvor teamet vårt kan gripe denne muligheten akkurat nå.

Til slutt noen linker: Greg Young blogger på codebetter.com. Greg har også holdt presentasjonen “Unleash Your Domain” andre steder enn på NNUG, og du finner den blant annet på infoQ. De har også gjort tilgjengelig et intervju med Greg, hvor han snakker om de samme tingene. For mer informasjon om DDD ville jeg startet på domaindrivendesign.org.

Onion architecture

en løk Onion Architecture er navnet Jeffrey Palermo har foreslått for å beskrive et software design pattern som er mye brukt spesielt i Alt.Net-miljøet. Palermo presanterte arkitekturen i en blogpost-serie i juli/august 2008.

Arkitekturen fokuserer på hvordan vi håndterer avhengigheter i programvaren vår, og er et steg bort fra den tradisjonelle lagdelingen vi alle har vært vandt til. Den beskrives som en “løk” med flere lag, hvor kjernen inneholder domenet vi modellerer og forretningslogikken, mens infrastruktur og brukergrensesnitt befinner seg helt ytterst. Alle avhengigheter går innover i løken – ingen ting får lov til å avhenge av noe i et lag lengre ute enn seg selv. Gjennom mye bruk av Dependency Inversion Principle designer man slik en løsning hvor forretningsverdien er skjermet, og ting som endres oftere og som har flere eksterne avhengigheter (UI teknologi, databaser etc) ligger på utsiden og kan lettere byttes ut.

La oss som et konkret eksempel ta en titt på hvordan løsningen jeg utviklet i TDD i praksis og Validering kan være et domene-ansvar ser ut i denne typen løk-modell (klikk på løken for full størrelse om du ikke er langsynt):

onion

Helt innerst i arkitekturen ligger domenemodellen, som i mitt tilfelle er User-klassen. Den får ikke lov til å referere noe annet som ikke også ligger i domenemodellen.

Laget utenfor kaller vi for Domain Services – domenetjenester. Her finner man de tjenestene som har mest med forretningslogikken å gjøre. I mitt eksempel er dette blant annet spesifikasjonen for hva som er en gyldig brukerregistrering – d.v.s. ValidRegistrationSpecification, og alle de andre klassene som inngår i den. Som du ser på tegningen avhenger denne spesifikasjonen først og fremst av User-klassen, men én av spesifikasjonene avhenger også av en annen domenetjeneste, nemlig den abstrakte UserRepositor’en. Jeg vet ikke om alle er helt enige i at en repository er en domenetjeneste – jeg har bl.a. sett noen definere et eget lag mellom domenemodellen og domenetjenestene for repositoriene.

Ett nivå lengre ute finner vi Application Services – tjenester som har mest å gjøre med operasjonene man skal kunne gjøre med programvaren. Jeg definerer f.eks. både controller’en og det abstrakte view’et i Model View Presenter til å være en del av dette laget. Disse klassene er mer eller mindre uavhengig av hvilken presentasjonsteknologi som er brukt, og skal derfor ikke ha noen avhengigheter til laget utenfor, slik som reglene i Onion Architecture dikterer. De bruker derimot domenetjenestene og domenemodellen til å utføre handlinger initiert av brukeren.

I det ytterste laget finner vi de konkrete implementasjonene som er avhengig av spesifik teknologi knyttet til brukergrensesnitt og infrastruktur. I mitt tilfelle er det de konkrete view’ene (ASP.NET brukerkontrollene) og den konkrete repositori’en (som bruker en db4objects-database). Vi kan også plassere testene våre her, for å illustrere hvordan de anhenger av hele arkitekturen og bruker et bestemt testrammeverk. Mens klassene i lagene nedenfor kan betraktes som POCO’s (Plain Old CLR Objects), er klassene i dette ytterste laget som du forstår tettere knyttet bestemte teknlogier. Gjennom å følge prinsippene i Onion Architecture har vi gjort oss mindre sårbare for endringer i disse teknologiene.

Det er vedt å merke seg at Onion Architecture har mye til felles med Evans’ Domain-Driven Design, men at det ikke følger “boka” punkt for punkt. Faktisk mener jeg at “reglene” i denne arkitekturen er mere rigide på noen punkter, men også mere universelt anvendelige.

Inversion of Control

UserRepository og Db4oUserRepository er kanskje den aller tydeligste bruken av Dependency Inversion i dette eksempelet. Som du ser avhenger tjenestene i løsningen av den abstrakte UserRepository, mens “ingenting” avhenger av Db4o-varianten. Ingenting er i hermetegn, fordi den jo må instantieres ett eller annet sted.

Når man følger disse prinsippene, og ikke minst driver frem designet gjennom testdreven utvikling, får man MANGE slike “løse” avhengigheter, og det er her Inversion of Control-rammeverk (IoC)kommer inn i bildet. Dependency Inversion Principle gir oss et design som er lettere å endre, men som er vanskeligere å initialisere. IoC er enkelt fortalt en slags startmotor for programvaren din. Arkitekturen er veldig løs, men IoC-containeren syr alt sammen igjen. Bruk av IoC-rammeverk er derfor ikke livsviktig, men veldig praktisk, når man følger Onion Architecture.

Det finnes en bra sampling med IoC-rammeverk for .NET-utviklere, og er dette ukjent materie for deg bør du absolutt ta en titt. Ikke forvent øyeblikkelige resultater, men ta deg god tid, og forsøk å introdusere bruken gradvis. Noen av de mest kjente rammeverkene er: Castle Windsor, StructureMap, Spring.NET, og min personlige favoritt Ninject. Anbefaler dessuten å ta en titt på relevante DimeCasts for å få en god start på dependency injection.

Så det var min introduksjon til Onion Architecture, pluss en liten rant om IoC – jeg håper det gav mening. Det ryktes forresten at Fredrik Kalseth snakker om Onion Architecture på MSDN Live som pågår i disse dager. Det er fortsatt ikke for sent å melde seg på til showene i Bergen (22. sep), Trondheim (24. sep) og Oslo (29. sep).

Noen knagger: , , , , , , ,

Validering kan være et domene-ansvar

Hvis du skummet gjennom min forrige post, TDD i praksis, så observerte du meg implementere registrering av ny bruker. Selv om det var en lang artikkel var det (minst) en ting jeg utelot der – det var nemlig ingen validering av input. Alle registreringer ville blitt en suksess uansett hva man tastet inn.

Validering kan gjøres på mange måter, men for meg er det for det første et eget ansvar som fortjener å bli implementert som egne tjenester/moduler, og for det andre er det et domene-ansvar, og bør ikke knyttes til infrastrukturen eller brukergrensesnittet (denne påstanden gjelder ikke for de mest banale valideringene, og igjen vil noen kunne kalle denne approachen for overkill).

Merk at dette er en konklusjon jeg mer eller mindre har funnet på av meg selv – en fremgangsmåte som gir mening for meg – så hvis du har noen kommentarer og innvendinger så er de som alltid hjertelig velkomne!

Jeg vil benytte anledningen til å implementere validering ved hjelp av specification pattern. Jeg har blogget om dette før, så jeg vil ikke ta med alle detaljene, men jeg vil spesielt vise hvilke tester jeg har skrevet for validering av input. Jeg skrev en og en test, og sørget for at hver test ble grønn før jeg gikk videre, men her har du hele testklassen i one go. Bruk litt tid på å lese gjennom dem..

  103 [TestFixture]

  104     public class When_validating_a_registration

  105     {

  106         Mock<UserRepository> mockRepository;

  107         Specification<User> validator;

  108         User user;

  109 

  110         [SetUp]

  111         public void SetUp()

  112         {

  113             mockRepository = new Mock<UserRepository>();

  114             validator = new ValidRegistrationSpecification(mockRepository.Object);

  115             // Default valid user registration

  116             user = new User() { Username = “T-Man”, Password = “abcd1234#” };

  117         }

  118         [Test]

  119         public void Should_validate_a_strong_password() {

  120             Assert.That(validator.IsSatisfiedBy(user));

  121         }

  122         [Test]

  123         public void Should_not_validate_password_without_letters() {

  124             user.Password = “12345678#”;

  125             Assert.IsFalse(validator.IsSatisfiedBy(user));

  126         }

  127         [Test]

  128         public void Should_not_validate_password_without_digits()

  129         {

  130             user.Password = “abcdefgh#”;

  131             Assert.IsFalse(validator.IsSatisfiedBy(user));

  132         }

  133         [Test]

  134         public void Should_not_validate_password_without_special_character()

  135         {

  136             user.Password = “abcd12345″;

  137             Assert.IsFalse(validator.IsSatisfiedBy(user));

  138         }

  139         [Test]

  140         public void Should_not_validate_password_with_less_than_8_characters()

  141         {

  142             user.Password = “abc123#”;

  143             Assert.IsFalse(validator.IsSatisfiedBy(user));

  144         }

  145         [Test]

  146         public void Should_not_validate_usernames_with_less_than_4_characters()

  147         {

  148             user.Username = “MrT”;

  149             Assert.IsFalse(validator.IsSatisfiedBy(user));

  150         }

  151         [Test]

  152         public void Username_can_not_be_accepted_if_it_already_exist()

  153         {

  154             var mockRepository = new Mock<UserRepository>();

  155             mockRepository

  156                 .Setup(rep => rep.Find(It.IsAny<Predicate<User>>()))

  157                 .Returns(new[] { new User() });

  158             Assert.IsFalse(validator.IsSatisfiedBy(user));

  159         }

  160     }

Metodenavnene i testklassen gir en perfekt oppsummering av reglene for hva en som er en gyldig brukerregistrering. Et passord må inneholde bokstaver, tall, spesialtegn – totalt minst 8 stykker, og brukernavnet må være minst 4 tegn, og ikke eksistere fra før. Nå kunne jeg ha implementert denne valideringen med noen ganske få linjer kode, men jeg vil at designet skal reflektere kunnskapen om reglene, og at det skal være veldig enkelt å endre en og en regel, og å legge til eller fjerne regler. ValidRegistrationSpecification ser derfor slik ut:

   78 public class ValidRegistrationSpecification : Specification<User>

   79 {

   80     private List<Specification<User>> _specifications;       

   81     public ValidRegistrationSpecification(UserRepository repository)

   82     {

   83         _specifications = new List<Specification<User>>();

   84         _specifications.Add(new UsernameShouldBeAtLeastFourCharacters());

   85         _specifications.Add(new UsernameMustBeAvailable(repository));

   86         _specifications.Add(new PasswordMustHaveLetter());

   87         _specifications.Add(new PasswordMustHaveDigit());

   88         _specifications.Add(new PasswordMustHaveSpecialCharacter());

   89         _specifications.Add(new PasswordMustHaveAtLeastEightCharacters());

   90     }

   91     public bool IsSatisfiedBy(User candidate)

   92     {

   93         try

   94         {

   95             foreach (var specification in _specifications)

   96                 if (!specification.IsSatisfiedBy(candidate))

   97                     return false;

   98         }

   99         catch (Exception) { }

  100         return true;

  101     }

  102 }

Klassen arver fra Specification (for User) – et enkelt interface som bare inneholder én metode: IsSatisfiedBy(T candidate). Hvis IsSatifiedBy returnerer true for en gitt bruker, så kan registreringen gjennomføres.

ValidRegistrationSpecification er videre egentlig en liste med med mer detaljerte spesifikasjoner, én for hver test jeg har skrevet, og IsSatifiedBy spoler gjennom alle disse. La oss se nærmere på én av dem, som spesifiserer at et passord må inneholde bokstaver:

   19 public class PasswordMustHaveLetter : Specification<User>

   20 {

   21     public bool IsSatisfiedBy(User candidate)

   22     {

   23         for (int i = 0; i < candidate.Password.Length; i++)

   24             if (Char.IsLetter(candidate.Password[i]))

   25                 return true;

   26         return false;

   27     }

   28 }

Du er kanskje ikke vandt til klasser på bare 10 linjer (inkludert klammene)? Det er ingenting galt i det skal jeg si deg.

Her følger den mest avanserte spesifikasjonen (med en Cyclomatic Complexity på hele 3) som kontrollerer at brukernavnet ikke er opptatt. Den er avhengig av en UserRepository som den benytter til å se om den finner en match på brukernavnet.

   64 public class UsernameMustBeAvailable : Specification<User>

   65 {

   66     private UserRepository _repository;

   67     public UsernameMustBeAvailable(UserRepository repository)

   68     {

   69         _repository = repository;

   70     }

   71     public bool IsSatisfiedBy(User candidate)

   72     {

   73         return _repository.Find((user)

   74             => user.Username == candidate.Username)

   75             .Count() == 0;

   76     }

   77 }

Jeg har ikke lest Eric Evans bok, så jeg vet ikke om denne løsningen er helt i tråd med Domain-Driven Design. Men jeg vet han spesifikt snakker om Specification pattern (excuse the pun). Og i sommer lærte Bellware meg at entiteter aldri skal referere domenetjenester (som repositories), så valideringen kunne ikke vært plassert i User-klassen.

For ordens skyld – jeg sier ikke at det jeg driver med er Domain-Driven Design. Jeg vil heller, som Bellware, kalle det DDD-light: Jeg bruker mønstrene og ideene fra DDD der det gir verdi og mening i det systemet jeg til enhver tid utvikler.

I neste blogpost skal jeg samle trådene fra utenfra-og-inn programmering, TDD og mocking i praksis og denne posten, og analysere designet jeg har kommet opp med.

Knagger: , , , , , , ,

TDD og mocking i praksis

I de to forrige postene, Utenfra-og-inn programmering og Avhengigetsisolering (a.k.a Mocking), har jeg forklart hvordan jeg praktiserer testdreven utvikling. Denne gangen gjør jeg et forsøk på å vise det i praksis, en slags “tutorial” om du vil. Det er selvfølgelig en overhengende fare for at jeg produserer en svært lang blogpost, men om du har lyst til å bli med meg og oppleve Mockist TDD i praksis så er du i alle fall svært velkommen.

Jeg dokumenterer steg for steg hva jeg gjør, og om du vil må du gjerne kode oppgaven parallelt med meg – det vil du nok lære mest av. I så fall trenger du NUnit og Moq (eller annet testrammeverk og mockingrammeverk du er komfortabel med), db4objects, samt Visual Studio. Jeg har ikke lagt ved den ferdige løsningen, det er selve prosessen som er poenget her.

Når jeg gjør testdreven utvikling forsøker jeg å utvikle i så små steg som mulig, og jeg hopper mye frem og tilbake mellom test og produksjonskode. Jeg har også en tendens til å foretrekke mange, små klasser som jobber sammen for å løse oppgaver, til fordel for større klasser med mer kompleks logikk. Noen vil kalle det overkill, jeg kaller det et fleksibelt design. Du kan gjøre deg opp din egen mening.

Oppgaven

“På vår fantastiske webside skal man kunne registrere seg som ny bruker med brukernavn og passord. Passordet skal selvsagt lagres kryptert. Når man har en brukerkonto skal man kunne logge inn.”

Ok, det hørtes ikke så vanskelig ut – de fleste utviklere har gjort dette noen ganger før. Asp.net har jo også kontroller og providere som gjør dette veldig enkelt. Men vi skal bruke denne oppgaven til å øve på TDD, så vi later som om alt det der ikke finnes, glemmer hva vi har gjort tidligere, og setter i gang.

En liten påminnelse: Det finnes hundrevis av måter å implementere dette på, og det finnes hundrevis av måter å gjøre TDD på. Dette er min fremgangsmåte, slik jeg foretrekker akkurat nå. Det finne singen fasit, og neste gang ville jeg kanskje ha gjort det anderledes. Så ikke kopier meg, men reflekter over hvorfor jeg gjør det jeg gjør, og du vil sikkert lære noe på veien.

Forberedelser

Jeg fyrer opp Visual Studio og åpner et nytt prosjekt basert på Asp.net Web Application templaten. Vi innbiller oss at dette er den eksisterende websiten vi skal utvikle den nye “featuren” for. For å gjøre dette enkelt vil alt opprettes i dette ene prosjektet, inkludert testene – normalt ville jeg ha fordelt elementene i løsningen i diverse moduler.

Den første testen, en kontroller og et view

Oppgaven vi har fått er todelt: Brukerregistrering og brukerautentisering. Registrering kommer naturlig før autentisering, så vi starter med det. Er du en utvikler som ikke er vandt til TDD og utenfra-og-inn programmering kan det nå være fristene å tenke at du har lyst til å starte med å opprette en bruker-klasse, eller kanskje definere hvordan databasen skal se ut. Det skal vi ikke gjøre. Ditt første instinkt skal være å skrive en test, en test som passerer om brukerregistreringen fungerer.

La oss opprette en testklasse – jeg velger å gi den det merkelige navnet When_registering_a_new_user:

    9 [TestFixture]

   10 public class When_registering_a_new_user

   11 {

   12 }

Jeg ser for meg at jeg vil ha en eller annen form for controller som tar seg av registreringen. Controlleren kan kommunisere med et view hvor brukeren skal taste inn et brukernavn og passord og trigge registreringen. Når registreringen er gjennomført kan controlleren kommunisere dette tilbake via viewet. La meg forsøke å skrive en test:

    9 [TestFixture]

   10 public class When_registering_a_new_user

   11 {

   12     [Test]

   13     public void Should_report_registration_ok_in_view()

   14     {

   15         var view = new UserRegistrationView();

   16         var controller = new UserRegistrationController(view);

   17         view.TriggerRegistrationRequest(“Kong Håkon”, “1234″);

   18         Assert.IsTrue(view.RegistrationOk);

   19     }

   20 }

Ingen av disse klassene finnes enda. Dette er helt vanlig når man gjør TDD; man forsøker først å finne ut hvordan klassene bør se ut – deretter implementerer man dem.

Jeg er ikke helt fornøyd med hvordan jeg må trigge registration request ved å kalle en metode på viewet, og jeg like heller ikke at jeg må implementere to ulike klasser for denne testen – jeg ønsker å fokusere på én ting om gangen. Men hvis jeg foreløpig lar viewet være et abstrakt interface, og tar i bruk mockingrammeverket (Moq) kan vi få dette til å bli litt bedre. Jeg endrer testen:

   13 [Test]

   14 public void Should_report_registration_ok_in_view()

   15 {

   16     var mockView = new Mock<UserRegistrationView>();

   17     var controller = new UserRegistrationController(mockView.Object);

   18     mockView.Raise(view => view.RegistrationRequested += null, EventArgs.Empty);

   19     mockView.Verify(view => view.SetRegistrationResult(true), Times.Once());

   20 }

Det vil være mye rødt i editoren nå siden det fortsatt ikke er noen av disse klassene, metodene eller eventene som eksisterer, men det lærer vi oss raskt å leve med, så forsøk å se bort i fra det.

Denne testen synes jeg ble litt bedre. På linje 18 trigger jeg et RegistrationRequested event, som jeg tenker vil tilsvare eventet som fyres når brukeren klikker på “register” knappen på websiden. På neste linje verifiserer jeg at SetRegistrationResult har blitt kalt med argumentet “true”. Jeg vil nemlig at controlleren skal plukke opp registreringsforespørselen og kalle den metoden om alt går som det skal. I stedet for å verifisere på tilstand verifiserer jeg nå adferd.

Det er på tide å gjøre noe med alle de røde advarslene, så nå vil jeg opprette controlleren og viewet. Siden jeg bruker CodeRush er dette kun noen få tastetrykk – bruker du ikke noe slikt verktøy må du gjøre det for hånd. Dette er hva jeg kommer opp med.

   10 public interface UserRegistrationView

   11 {

   12     event EventHandler RegistrationRequested;

   13     void SetRegistrationResult(bool success);

   14 }

   15 public class UserRegistrationController

   16 {

   17     private UserRegistrationView _view;

   18     public UserRegistrationController(UserRegistrationView view)

   19     {

   20         _view = view;

   21     }

   22 }

Nå kompilerer prosjektet vårt, men kjører jeg testen vil den selvfølgelig feile, for SetRegistrationResult vil aldri bli kalt. Og det er helt perfekt. I TDD følger man et mønster som heter “Red, Green, Refactor”, som innebærer at vi først skal sørge for å lage en test som feiler. Det har vi oppnådd.

En grønn test

Så hvordan kan vi komme til neste steg – en vellykket test? Jo, det er veldig enkelt, selv om det virker som en helt idiotisk ting å gjøre:

   18 public UserRegistrationController(UserRegistrationView view)

   19 {

   20     _view = view;

   21     view.SetRegistrationResult(true);

   22 }

Jeg kaller rett og slett SetRegistrationResult i konstruktøren til controlleren. Hvorfor det? Jo, nå vet jeg at testen fungerer – at testen blir grønn hvis SetRegistrationResult kalles. To av tre steg er gjennomført, og vi kan gå igang med det litt vage steget “refakturering”.

Men hva med eventet som trigges? Det var jo det som skulle føre til registreringen. Stemmer det! Jeg kunne ha skrevet en ny test som passet på at SetRegistrationResult ikke ble kalt uten at eventet ble trigget, men akkurat nå synes jeg ikke det er nødvendig. Jeg gjør denne endringen med den ene testen jeg har:

   15 public class UserRegistrationController

   16 {

   17     private UserRegistrationView _view;

   18     public UserRegistrationController(UserRegistrationView view)

   19     {

   20         _view = view;

   21         _view.RegistrationRequested += _view_RegistrationRequested;

   22     }

   23 

   24     void _view_RegistrationRequested(object sender, EventArgs e)

   25     {

   26         _view.SetRegistrationResult(true);

   27     }

   28 }

.. jeg kjører testen på nytt etter endringen for å være sikker på at jeg ikke har ødelagt noe; den er fortsatt grønn, så alt er ok.

Test nummer 2: User repository

Men det skjer jo ingen registrering her. Hmmm. En registrering innebærer å opprette og lagre en ny bruker, ikke sant?! (Som du ser spiller jeg dum, og forplikter meg dermed ikke til et konkret design før det er absolutt nødvendig.) Ok, da trenger vi en eller annen form for Repository som kan lagre nye brukere. Jeg får skrive en ny test – kan jo ikke begynne å implementere noe nytt uten…

   40 [Test]

   41 public void Should_persist_user_in_repository()

   42 {

   43     var mockView = new Mock<UserRegistrationView>();

   44     var mockRepository = new Mock<UserRepository>();

   45     var controller = new UserRegistrationController(mockView.Object, mockRepository.Object);

   46     mockView.Raise(view => view.RegistrationRequested += null, EventArgs.Empty);

   47     mockRepository.Verify(r => r.Insert(It.IsAny<User>()));

   48 }

Jeg lager en ny mock som skal representere en repository, og sender den også inn til controlleren. I linje 47 kontrollerer jeg at Insert har blitt kalt, og at det ble sendt inn et User objekt. Hverken UserRepository eller User eksisterer enda.

Det meste av testen ligner på den forrige testen, så DRY-prinsippet (kanskje det aller viktigste prinsippet innen softwareutvikling) tvinger meg til å trekke ut det som er felles. Jeg ender opp med denne testklassen:

   29 [TestFixture]

   30 public class When_registering_a_new_user

   31 {

   32     [Test]

   33     public void Should_report_registration_ok_in_view()

   34     {

   35         When_doing_a_registration();

   36         mockView.Verify(view => view.SetRegistrationResult(true), Times.Once());

   37     }

   38     [Test]

   39     public void Should_persist_user_in_repository()

   40     {

   41         When_doing_a_registration();

   42         mockRepository.Verify(r => r.Insert(It.IsAny<User>()));

   43     }

   44     private void When_doing_a_registration()

   45     {

   46         mockView = new Mock<UserRegistrationView>();

   47         mockRepository = new Mock<UserRepository>();

   48         controller = new UserRegistrationController(mockView.Object, mockRepository.Object);

   49         mockView.Raise(view => view.RegistrationRequested += null, EventArgs.Empty);

   50     }

   51     private Mock<UserRepository> mockRepository;

   52     private Mock<UserRegistrationView> mockView;

   53     private UserRegistrationController controller;

   54 }

For at dette skal kompilere må jeg gjøre tre ting; opprette UserRepository, fikse controllerens konstruktør til å ta inn en repository, og opprette en User klasse. Noen CodeRush-shortcuts senere har jeg opprettet følgende:

   15 public class User

   16 {

   17 }

   18 public interface UserRepository

   19 {

   20     void Insert(User userToInsert);

   21 }

   22 public class UserRegistrationController

   23 {

   24     private UserRepository _repository;

   25     private UserRegistrationView _view;

   26     public UserRegistrationController(

   27         UserRegistrationView view, UserRepository repository)

   28     {

   29         _repository = repository;

   30         _view = view;

   31         _view.RegistrationRequested += _view_RegistrationRequested;

   32     }

Prosjektet kompilerer igjen. Vår første test passerer fortsatt, men den nye testen gjør ikke det, så det får vi gjøre noe med. Vi må rett og slett sørge for at den nye brukeren lagres:

   33 private void _view_RegistrationRequested(object sender, EventArgs e)

   34 {

   35     _repository.Insert(new User());

   36     _view.SetRegistrationResult(true);

   37 }

Der, nå er begge testene grønne. Kontrolleren lagrer en ny bruker til repositorien, og sier fra til viewet at det er gjort. Men den mangler noe veldig sentralt. Trenger vi ikke noe mer input? Hvor er brukernavnet og passordet?

Har du noen gang programmert så mye adferd uten å lage en eneste public property? Er det ikke interessant hvordan TDD lar oss fokusere på adferd? Men nå er det kanskje på tide å legge til i alle fall brukernavn og passord. Men først må vi ha en test for dette. Den siste testen vi skrev verifiserer at brukeren lagres til basen. Hva om vi legger til å validere selve brukeren som legges til også – at den har riktig navn f.eks.? Jeg forsøker meg på dette ved å endre testen til dette her:

   48 [Test]

   49 public void Should_persist_user_in_repository()

   50 {

   51     User addedUser = null;

   52     mockRepository

   53         .Setup(r => r.Insert(It.IsAny<User>()))

   54         .Callback((User user) => addedUser = user);

   55     When_doing_a_registration();

   56     Assert.AreEqual(“Kong Håkon”, addedUser.Username);

   57 }

Jeg bruker litt Moq-magic til å sette opp en callback som tar vare på brukeren som sendes til repositorien, og sjekker at navnet på den er “Kong Håkon”. Men hvor har jeg fått dette navnet fra? Det skal jo brukeren sende inn.., via viewet selvfølgelig. Så vi setter opp dette også:

   50 [Test]

   51 public void Should_persist_user_in_repository()

   52 {

   53     mockView.SetupGet(v => v.Username).Returns(“Kong Håkon”);

   54     User addedUser = null;

   55     mockRepository

   56         .Setup(r => r.Insert(It.IsAny<User>()))

   57         .Callback((User user) => addedUser = user);

   58     When_doing_a_registration();

   59     Assert.AreEqual(“Kong Håkon”, addedUser.Username);

   60 }

Jeg forventer nå at viewet har en Username property, og at den vil returnere “Kong Håkon” for oss når den blir forespurt. Registreringsjobben skal da overføre dette navnet til brukeren som lagres. Peace of cake! Jeg oppretter de to propertiene jeg trenger for å få kompilert, kjører testen én gang for å se det røde lyset, implementerer litt mer logikk i kontrolleren, og har to grønne tester igjen.

PS: Jeg har forøvrig trukket ut litt logikk fra When_doing_a_registration() og plassert det i en SetUp-metode – hvis ikke hadde jeg fått en null-ref exception i linje 53.

   10 public interface UserRegistrationView

   11 {

   12     event EventHandler RegistrationRequested;

   13     string Username { get; }

   14     void SetRegistrationResult(bool success);

   15 }

   16 public class User

   17 {

   18     public string Username { get; set; }

   19 }

   35 private void _view_RegistrationRequested(object sender, EventArgs e)

   36 {

   37     _repository.Insert(new User

   38     {

   39         Username = _view.Username

   40     });

   41     _view.SetRegistrationResult(true);

   42 }

Passordkryptering

Nå kunne jeg gjort det samme for passord som for brukernavn, men oppgaven sa at jeg skulle kryptere passordet. Det er derfor viktig at passordet som lagres IKKE er det samme som sendes inn fra viewet. Jeg jobber litt til med testene:

   63 Assert.AreNotEqual(“passw0rd”, addedUser.Password);

Nei, dette føles ikke særlig riktig. Når jeg tenker over det er hashing av passord et helt annet ansvar, noe kontrolleren kan deligere til noen andre. Ved å utnytte det kan jeg skrive en enkel test som validerer at passordet hashes, mens jeg utsetter selve hash-logikken.

   77 [Test]

   78 public void Should_hash_password()

   79 {

   80     When_doing_a_registration();

   81     mockHasher.Verify(h => h.HashPassword(It.IsAny<string>()));

   82 }

Jeg har begynt å gå litt raskere frem nå, men jeg regner med at du klarer å henge med. Jeg har lagt til en ny avhengighet, PasswordHashService, som jeg injecter i controller-konstruktøren. Og så skriver jeg en ny test som verifiserer at HashPassword kalles. Noen sekunder senere får jeg dette til å kompilere og testen til å passere ved å gjøre følgende..

   39 private void _view_RegistrationRequested(object sender, EventArgs e)

   40 {

   41     _repository.Insert(new User

   42     {

   43         Username = _view.Username,

   44         Password = _hasher.HashPassword(_view.Password),

   45     });

   46     _view.SetRegistrationResult(true);

   47 }

Password settes i linje 44. Jeg bruker to ulike properties som ikke finnes enda.., så jeg oppretter dem. Done!

En konkret hasher

Vi har nå laget en fungerende controller, men ingen av de objektene den sammarbeider med eksisterer enda – de ble alle “mocket ut” i testene. Vi kan nå jobbe oss innover. Jeg velger å ta PasswordHashService først. Jeg skriver selvsagt tester før produksjonskode.., og her er den første:

   55 [TestFixture]

   56 public class MD5HasherTests

   57 {

   58     [Test]

   59     public void QuickBrownFoxTest()

   60     {

   61         Assert.AreEqual(

   62             “9e107d9d372bb6826bd81d3542a419d6″,

   63             new MD5Hasher().HashPassword(“The quick brown fox jumps over the lazy dog”));

   64     }

   65 }

Jeg fant hashen til denne setningen i Wikipedia’s artikkel om MD5, så jeg tenkte den kunne være et greit utgangspunkt for å teste algoritmen. Litt cut n’ paste fra nettet og prøving og feiling gir meg følgende implementasjon som passerer testen:

   53 public class MD5Hasher : PasswordHashService

   54 {

   55     public string HashPassword(string passwordToHash)

   56     {

   57         var passwordBytes = Encoding.UTF8.GetBytes(passwordToHash);

   58         var hashBytes = MD5.Create().ComputeHash(passwordBytes);

   59         var buffer = new StringBuilder();

   60         for (int i = 0; i < hashBytes.Length; i++)

   61             buffer.Append(hashBytes[i].ToString(“x2″).ToLower());

   62         return buffer.ToString();

   63     }

   64 }

En konkret repository

Neste interface jeg trenger en konkret implementasjon av er UserRepository. For å implementere den trenger jeg et persisteringsmedium, en eller annen form for database. Det enkleste jeg vet om er å bruke en objektdatabase, db4objects, så det vil jeg gjøre her. Jeg kan enkelt sette opp en test som oppretter en database, lagrer en bruker, kontrollerer at jeg kan hente brukeren ut igjen, og rydder opp ved å slette basen. Jeg gjør dette i små steg, men her er alt i en batch:

   44 [TestFixture]

   45 public class Db4oUserRepositoryTests

   46 {

   47     const string db_path = @”c:\temp\Db4oUserRepositoryTests.dbo”;

   48     private IObjectContainer database;

   49     private Db4oUserRepository repository;

   50 

   51     [SetUp]

   52     public void SetUp()

   53     {

   54         database = Db4oFactory.OpenFile(db_path);

   55         repository = new Db4oUserRepository(database);

   56     }

   57     [TearDown]

   58     public void TearDown()

   59     {

   60         database.Dispose();

   61         File.Delete(db_path);

   62     }

   63     [Test]

   64     public void Should_insert_user()

   65     {

   66         repository.Insert(new User { Username = “Foo” });

   67         Assert.AreEqual(

   68             “Foo”,

   69             (from User u in database select u).Single().Username);

   70     }

   71 }

Jeg vil ha en konkret UserRepository som jeg vil kalle Db4oUserRepository. Jeg vil sende en instans av en objektdatabase inn via konstruktøren (IObjectContainer er et db4objects interface). I setup-metoden oppretter jeg en ny filbasert database og selve repositorien, og i teardown disposer jeg databasen og sletter filen.

Selve testen lagrer en ny bruker i repositorien, og så bruker jeg Linq 2 Db4o for å hente ut alle brukere fra basen, sjekke at det finnes én-og-bare-én bruker (ved å kalle Single()), og kontrollere at brukernavnet er riktig. Vær oppmerksom på at du må legge til “using Db4objects.Db4o.Linq” for å få Linq-spørringen i linje 69 til å kompilere. Vil du vite mer om Db4objects kan du lese min blogpost om dette fantastiske rammeverket.

Følgende enkle implementasjon tilfredstiller testen over:

   27 public class Db4oUserRepository : UserRepository

   28 {

   29     private IObjectContainer _database;

   30     public Db4oUserRepository(IObjectContainer database)

   31     {

   32         _database = database;           

   33     }

   34     public void Insert(User userToInsert)

   35     {

   36         _database.Store(userToInsert);

   37     }

   38 }

Et konkret view

Det eneste som gjenstår nå for å ha en fungerende brukerregistrering er å lage et konkret view. Dette er den eneste biten jeg ikke bryr meg om å skrive tester for – det er somregel ikke verdt innsatsen, sålenge forretningslogikken er separert ut av viewet. Jeg oppretter en ny Web User Control, kaller den Registration.ascx, og dropper inn to TextBox’er og en Button.

    1 <%@ Control Language=”C#” AutoEventWireup=”true” CodeBehind=”Registration.ascx.cs”

    2     Inherits=”WebApplication1.Registration” %>

    3 User name: <asp:TextBox ID=”txtUsername” runat=”server”></asp:TextBox><br />

    4 Password: <asp:TextBox ID=”txtPassword” runat=”server”></asp:TextBox><br />

    5 <asp:Button ID=”createUserButton” runat=”server” Text=”Create user”

    6     onclick=”createUserButton_Click” />

Og her er code-behind, hvor jeg lar Registration arve fra UserRegistrationView:

   11 public partial class Registration : UserControl, UserRegistrationView

   12 {

   13     private UserRegistrationController _controller;

   14     protected void Page_Load(object sender, EventArgs e)

   15     {

   16         _controller = new UserRegistrationController(

   17             this,

   18             Application["UserRespoitory"] as UserRepository,

   19             new MD5Hasher());

   20     }

   21 

   22     // Inherited from UserRegistrationView

   23     public event EventHandler RegistrationRequested;

   24     public string Username { get { return txtUsername.Text; } }

   25     public string Password { get { return txtPassword.Text; } }

   26     public void SetRegistrationResult(bool success)

   27     {

   28         if (success)

   29         {

   30             createUserButton.Text = “Success!”;

   31             createUserButton.Enabled = false;

   32         }

   33     }

   34     // end UserRegistrationView Members

   35     protected void createUserButton_Click(object sender, EventArgs e)

   36     {

   37         if (RegistrationRequested != null)

   38             RegistrationRequested(this, e);

   39     }

   40 }

Denne approchen er opprinnelig inspiret av Phil Haack’s blogpost om Supervising Controller i ASP.NET. Initaliseringen av kontrolleren som foregår i Page_Load knytter sammen alle objektene som trengs for å registrere en bruker, og når brukeren fyrer av RegistrationRequested eventet ved å klikke på knappen vil controlleren ta over og gjennomføre registreringen.

Om du drar inn Registration-kontrollen i Default.aspx så kan du teste at det fungerer som det skal. Foreløpig får du tilbakemelding ved at knappen blir disablet og teksten sier “Success!”, men her kan man tenke seg at vi skjuler hele kontrollen og viser login-kontrollen (som fortsatt gjenstår å lage) i stedet.

Jeg har valgt å plassere repositorien i Application state – jeg vet ikke hvor bra dette fungerer i praksis, og det er ikke sånn jeg ville har gjort det “i virkeligheten”, men det fungerer greit her og nå. ASP.NET og state er ikke det viktige temaet i denne artikkelen, så ikke gi meg flak for denne fremgangsmåten. Initaliseringen skjer i Global.asax:

    6 public class Global : System.Web.HttpApplication

    7 {

    8     protected void Application_Start(object sender, EventArgs e)

    9     {

   10         var database = Db4oFactory.OpenFile(@”c:\temp\Db4oUserRepository.dbo”);

   11         Application["Db4oUserRepository.dbo"] = database;

   12         Application["UserRespoitory"] = new Db4oUserRepository(database);

   13     }

   14 

   15     protected void Application_End(object sender, EventArgs e)

   16     {

   17         var database = Application["Db4oUserRepository.dbo"] as IDisposable;

   18         database.Dispose();

   19     }

   20 }

Innlogging

For å implementere innlogging går jeg frem som jeg gjorde da jeg startet på brukerregistrering. Innlogging er et eget ansvar som trenger egne tester, eget view og egen controller. Controlleren vil bruke UserRepository, som man gjennom testene må utvide med logikk for å hente opp en bruker basert på navn. Den vil også gjenbruke PasswordHashService, fordi passordet må hashes før man kan sammenligne med det lagrede passordet.

Gjennom denne blogposten har du allerede sett og lært alt du trenger for å fullføre oppgaven, så jeg stopper her. Om du har lyst til å øve deg litt selv så anbefaler jeg at du fortsetter på egenhånd. Skriv tester først, og ikke lag en eneste klasse eller metode før du har kode (test eller annen kode) som allerede refererer klassen/metoden.

Jeg håper du likte denne TDD-tutorialen. I en ny blogpost som kommer veldig snart vil jeg analysere designet jeg har kommet opp med, og snakke litt om best practises rundt smidig arkitektur. På gjensyn!

Knagger: , , , , , , , , , ,


Einar W. Høst: Det er jo læringen som gjør det morsomt! Se også http://norvig.com/21-days...

Pagliacci: OBS! tl;wr. Det er vel akuratt det jeg sliter med med min læring innenfor pr...

Torbjørn: La oss anta to ulike definisjoner av Template Method pattern - de to ytterpunkte...

Lars-Petter: Hei igjen. Siden du inviterer til å ta diskusjonen i bloggen, og har tatt deg t...

Torbjørn: Lars-Petter: Det er én måte å se det på. Det er absolutt fortsatt Template M...

Lars-Petter: Hei. Har du ikke i prinsippet her gått over fra Template Method (arv) til Strat...

Christian Abildsø: I alle fall i C#, så føles dette som kode som blir mer fleksibel men vanskelig...

Torbjørn: Hei Henrik, og takk for tilbudet. Ble oppmerksom på Rasberry Pi for under en uk...

Henrik Sandaker Palm: Ang. større hobby prosjekt. Du er som er en slik rakker på programmering har j...

Øivind Nilsen: Slutt å bruke mobilnummeret mitt som eksempel !...

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • En historie om programmering
  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • Kategorier

  • .net ninja (37)
  • Bøker (17)
  • Diverse prosjekter (35)
  • DSL (10)
  • Erlang (10)
  • F# (5)
  • Hardware (1)
  • Jobb (77)
  • Julekalender (51)
  • kjempekjekt.com (23)
  • LISP/Clojure (33)
  • NNUG / community (60)
  • O/RM & databaser (10)
  • Off topic (116)
  • OO-design/clean code (30)
  • Podcasts (14)
  • Polyglot (77)
  • Ruby (27)
  • Silverlight / RIA (3)
  • Software/verktøy (20)
  • Softwareutvikling (21)
  • Testing / TDD (30)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (32)
  • WPF (9)
  • WTF (12)
  • Last ned Wallpaper

    Programmeringsbloggens tøffe skrivebordsbakgrunn med snippets fra ulike språk laster du ned her!

    Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Meta