Utenfra-og-inn programmering

Outside-in development, eller utenfra-og-inn programmering som jeg kaller det, er et av elementene i Dan Norths Behavior-Driven Development (BDD). Mens testdreven utvikling (TDD) i bunn og grunn bare betyr at du skal skrive tester før du skriver kode, forteller BDD gjennom utenfra-og-inn prinsippet hvordan du bør designe systemet ditt ved hjelp av testdreven utvikling (eller Code-by-Example, som Dan kaller det).

Prinsippet er ganske enkelt: Du begynner med det du vet at du trenger – det kravspesifikasjonen eller User Storien sier at du skal lage – adferden du skal produsere. For eksempel blir du bedt om å lage en webside hvor man kan administrere ett eller annet.., la oss si at vi skal administrere lagerbeholdningen i en butikk. Ok, da begynner vi ytterst med brukergrensesnittet.

Vi definerer da gjerne et View, som har ansvar for å vise lagerbeholdningen, og la brukeren gjøre operasjoner mot denne (legge til/slette/etc). Og så arbeider vi oss innover. Vi lager typisk en controller/presenter som har som oppgave å populere viewet med data og håndtere kommandoer fra brukeren via viewet. Når vi har den på plass ser vi at vi trenger mer – data fra en repository f.eks., så da lager vi den.

Gjennom denne prosessen oppdager vi de objektene brukergrensesnittet trenger å sammarbeide med, de objektene vi trenger på neste nivå, de objektene vi trenger på nivået etter det – slik fortsetter det til vi er i mål og alt fungerer. På denne måten følger vi YAGNI-prinsippet (You Ain’t Gonna Need It), fordi all koden vi skriver enten brukes av brukeren direkte (viewet i dette tilfellet), eller av annen kode som allerede er skrevet. Alt vi gjør gir dermed verdi her og nå.

rect3489

En historie fra virkeligheten..

Kontrasten til denne fremgangsmåten er typisk at vi begynner med en datamodell, gjerne i form av en relasjonsdatabase, og arbeider oss utover. Vi tror vi vet hva vi trenger i neste ledd, og lager en modell vi håper passer. Etterhvert som vi arbeider oss utover i lagene må vi hele tiden tilpasse oss det som ligger innenfor.

I forrige uke skulle vi løse en oppgave hvor vi skulle ta imot en innkommende SMS-melding, ta noen avgjørelser basert på meldingens innhold og tidligere hendelser i systemet, og sende ut en ny SMS.

Jeg brukte TDD, og begynte med å lage en “meldingshåndterer” som tok imot SMS’en. Jeg jobbet meg innover og oppdaget andre ansvar som håndtereren trengte; en klasse for å lese meldingsinnholdet, en klasse for å ta avgjørelser basert på innholdet, en klasse jeg kunne bruke for å spørre om tidligere hendelser, en klasse som kunne konstruere nye meldinger etc.

Min kollega begynte i andre enden. Han definerte et par databasetabeller, et par entitetsklasser, og laget en repository med metoder for CRUD-operasjoner mot tabellene og mapping mot entitetene. Da vi etter ikke alt for lang tid sammenlignet hva vi hadde gjort, så vi raskt at det var en klar mismatch. Logikken jeg hadde laget hadde ikke noe behov for alle CRUD-operasjonene min kollega hadde laget – jeg trengte bare noen få og dessuten mye enklere metoder. Og entitetsklassene ble forkastet. Utenfra-og-inn hadde hjulpet meg til å lage et design som var mer fokusert på adferden vi skulle levere. Modellen man tror man trenger når man tenker innenfra-og-utover stemmer ofte ikke med det designet testdreven utvikling gir deg.

I ALT.NET miljøet sier man ofte at du kan/bør designe hele systemet ditt før du oppretter databasen. Databasen er der bare for å persistere tilstand, eller ta vare på historikk for rapportering. Den er ikke essensiell for programvaren. De bruker uttrykket persistence ignorance – forretningslogikken, kjernen i programvaren, skal ikke bry seg om hvordan data lagres. Dette står i sterk kontrast til hvordan de fleste utviklingsprosjekter jeg har vært borti foregår, hvor det første man gjør når man skal begynne på en ny feature er å definere hvordan tabellen skal se ut.

“Mocking”

mockery For å gjøre utenfra-og-inn programmering på en effektiv måte må man gå i små steg, og få tilbakemelding hele tiden gjennom testene man skriver. Hvis man skal utvikle alle klassene man trenger for å tilfredstille en feature før man kan sjekke om den fungerer som forventet, gjør man det ekstremt vanskelig for seg selv. Det er her mocking kommer inn i bildet.

(Illustrasjonen til høyre er hentet fra Michael Feathers og Steve Freemans foredrag Test Driven Development: Ten Years Later. Anbefales som en historisk oppsummering av TDD.)

Det vi populært kaller mocking betyr at når vi utvikler en betsemt enhet/klasse/modul så isolerer vi oss bort fra avhengighetene til denne enheten/klassen/modulen. I eksempelet mitt hvor jeg skal lage et webgrensesnitt for å administrere lagerbeholdning vil jeg ikke tenke på repository-klassen når jeg utvikler controlleren. Men controlleren trenger helt klart en repository. Vi lager da en falsk repository som controlleren kan bruke i testene vi skriver.

Når vi er ferdig med controlleren, og skal utvikle repository-klassen, er det repositoriens avhengigheter vi “mocker ut”. Følger man dette helt strickt, vil en feil i en testklasse alltid korrespondere til en feil i den éne testede klassen. Klassen som testes/utvikles isoleres fra resten av systemet.

Jeg praktiserte lenge TDD helt uten mocking, og klarte ikke se verdien av å begynne med det. Jeg trodde mocking var noe man gjorde om man ikke klarte å gjøre TDD på en orntlig måte. Det var først når jeg begynte å følge Model-View-Presenter (og lignende presentasjonsmønstre) at jeg så verdien, og nå bruker jeg mocking hele tiden. I min neste blogpost vil jeg gå mye mer i dybden på dette, og snakke om hvilke muligheter man har tilgjengelig for avhengighetsisolering i .NET.

Konklusjon

Utenfra-og-inn er en måte å kode på hvor vi driver frem designet av systemet gjennom tester. Vi går i små steg, og gjør oss ferdige med det vi holder på med før vi går videre. Ting vi ser vi trenger underveis mocker vi. Gjennom denne fremgangsmåten skiller vi bedre hva vi trenger fra hvordan det skal løses – vi fokuserer på det logiske designet og adferd, og det fysiske designet (implementasjonen om du vil) er underordnet og kommer etterhvert. Vi drives primært av forretningsbehov, og vokser systemet vårt organisk med de tingene vi oppdager at vi trenger.

Dette er uten tvil en herlig måte å jobbe på, og alle som ikke kjenner seg igjen i denne fremgangsmåten bør forsøke den.

Knagger: , , , ,

Kategorier: Testing / TDD.
RSS feed for kommentarene. Tilbaketråkk.

18 kommentarer til “Utenfra-og-inn programmering”

  1. Kjetil Klaussen Says:

    Bra! Jeg har fulgt med på bloggen din en stund og finner den utrolig engasjerende og velskrevet. Tror kanskje dette er en av de beste BDD-forklaringene jeg har lest så langt. Keep up the good work!

  2. Stian Almås Says:

    Enig med den framgangsmåten. Jeg har også gjort det slik en stund nå. Men du forklarer det bedre enn jeg kunne gjort. Fint med konkrete eksempler også.
    Pene illustrasjoner du har med, forresten. Hva bruker du for å lage de?

  3. Torbjørn Says:

    Jeg bruker InkScape – gratis, open source vektor-tegneprogam. Har brukt det i alle fall i et års tid nå.., meget solid!

  4. Bjarte Says:

    Good shit. Første fornuftige norske blogpost om programmering jeg har lest :) (Har ikke sett meg om så mye heller men..) Likte illustrasjonene også. Skal sjekke ut InkScape.

  5. Torbjørn Says:

    Vel, tusen takk! Men du har ikke lest resten av bloggen min da går jeg ut i fra?! ;)

  6. Thomas Skardal Says:

    Kudos! Ser frem til neste!

  7. Glenn F. Henriksen Says:

    Kanonbra post! Veldig bra forklart.

    Jeg skal prate på NNUG Stavanger om mocking, kunne jeg brukt utenfra-og-inn-tegningen din? Lenker naturligvis til bloggen din.

  8. Torbjørn Says:

    Glenn – alt innholdet på denne bloggen er lisensiert under “Creative Commons : Navngivelse-Del på samme vilkår 3.0″, så du kan kopiere og spre det så mye du vil, og til og med endre det, sålenge du navngir meg og/eller bloggen, og deler på samme vilkår. Så det er ikke noe problem!

    Lykke til med foredraget!

  9. Johannes Brodwall Says:

    Jeg tror jeg skal prøve meg på å være uenig i fremgangsmåten, selv om jeg er enig i poenget ditt.

    To essensielle punkter:
    * Start med brukerhistorien
    * Ta ansvar fra topp til tå av arkitekturen – altså: ikke sag systemet i to

    Men jeg har aldri likt mocking selv. Jeg bruker en annen innfallsvinkel. Her er eksempel fra dagens kodesesjon:
    * Vi skal søke etter kontoer basert på navn og adresse
    * Vi lager en test som sier “gitt at x, y og z finnes i basen (dvs: legg x, y og z i en blank in-memory base), når brukere fyller ut navn slik og adresse slik, og brukeren trykker på ‘søk’ knappen, så skal x finnes i tabellen”
    * Testen feiler naturligvis voldsomt.
    * Vi koder det som skal til på brukergrensesnittet til vi kommer til at vi har skrevet “Repository.find(KontoSpecification.medNavnOgAdresse(…))”
    * SÅ SETTER VI DENNE TESTEN TIL IGNORE og starter å jobbe med en test mot repository-nivået
    * Tilsvarende med andre deltester

    Resultatet er at vi har en system som er koblet samme fra topp til tå, med utgangspunkt i brukerhistorien. Men vi har hatt en test som har feilet (eller vært kommentert ut) i 30-60 minutter! På den andre siden har vi færre indireksjonslag, fordi vi har brukt mindre mocking.

    Å kommentere ut toppnivåtesten framfor å mocke den gir altså en fordel i at man unngår mocking. Men det gjør at vi har feilende tester veldig lenge. Hva tror du? Er det en ok innfallsvinkel? Bedre, dårligere?

  10. Torbjørn Says:

    Johannes: Jeg vil ikke si at den ene fremgangsmåten er bedre eller dårligere enn den andre. Det virker som om dere har en gjennomtenkt teknikk som fungerer for dere, og det er bra. Men det er en forskjell der, som lar meg påpeke poenget med min fremgangsmåte…

    Hva skjer med din approach om du ikke har en lineær avhengighetstruktur, men har et voksende tre med avhengigheter? La oss si at du skal lage en kontroller som har fire avhengigheter. Og hva om disse igjen har flere avhengigheter? Da vil du ikke få noen feedback på om det du har gjort på toppen er riktig før alle disse avhengighetene er implementert – og det kan ta lang tid. Jeg foretrekker å gå i små steg med mye tilbakemelding – jeg tror det bør skalere bedre når kompleksiteten øker.

    UPDATE: Jeg har en kommende post hvor jeg demonstrerer TDD i praksis. Håper du leser den, hvor min fremgangsmåte skal komme klart frem.

    Du sier at jeg ikke skal sage systemet i to. Jeg vil påstå at det er hele poenget med objektorientering: Splitt opp systemet ditt i små, autonome enheter, som kan sammarbeide om å få jobben gjort. Hver enhet er et lite program som gjør én ting, og gjør den godt. Da føles det helt greit for meg – ja, jeg vil faktisk si filosofisk riktig – å utvikle enhetene i vakum, sålenge kravspesifiksjonen til enheten allerede er drevet frem av kodeeksempler/tester.

    Enkelte har påpekt et skille mellom det de kaller klassisk TDD (eller State TDD) og \”mockist TDD\”. I klassisk TDD sies det at man er opptatt av å verifisere tilstandsendringer, og at man bruker mocks kun der hvor det i praksis er vanskelig eller tungvidt å bruke de faktiske implementasjonene. Mockist TDD fokuserer mer på å teste interaksjonen mellom objekter. For meg betyr det at denen formen er mer opptatt av adferd, som er en god ting. Martin FOwler sier (i Mocks Aren\’t Stubs) at BDD er et \”offshoot\” fra mockist TDD.

    Så det virker altså som om du står for en mer klassisk TDD (og da har du muligens flest på din side), mens jeg \”reklamerer\” for BDD (gjengen med mest momentum for tiden). Er du enig i det?

    Takk for en solid kommentar.., jeg liker debatten!

  11. Johannes Brodwall Says:

    Mitt hovedproblem med mocking er at det ofte kan lede til overdesign. Et prima eksempel finnes i en introduksjon til JBehave: http://www.ryangreenhall.com/articles/bdd-by-example.html

    Her ender man opp med store mengder mocking, men det er ikke så ille. Det som er ille er kode som minner om ting jeg har sett i min egen kode når jeg mocker:

    public class Guitar {

    public List notesPlayed = new ArrayList();

    public void play(Tab tab) {
    notesPlayed = tab.notesToBePlayed();
    }

    }

    public class Tab {

    private final List notes;

    public Tab(String asciiTab,
    TabParser parser) {
    notes = parser.parse(asciiTab);
    }

    }

    Her har først Tab og så TabParser vært mocket. De er naturligvis bare flesk i dette designet. Sløsing. Muda!

    Dette er tulleeksempler, men gjenspeiler kode som er skrevet av uerfarne eller overivrige mockere. Mocking er for viderekommende, og bør ikke brukes før du virkelig vet hva du holder på med!

  12. Torbjørn Says:

    Hva er overdesign? Er det om du har flere klasser enn nødvendig? Er det når objektstrukturene blir så komplekse at de blir vanskelige å forstå, og man dermed mister produktivitet? Eller kan det være brudd på subjektive regler og oppfatninger om estetikk?

    Det finnes ikke noe klart svar på dette, hvor mye design som er fornuftig eller ikke, uten å ta hensyn til kontekst.

    Vår oppgave som utviklere er å lage kode som kommuniserer godt, lage gode abstraksjoner. Er ikke et design med ti klasser potensielt dobbelt så kommunikativt som et design med fem klasser? Etter min erfaring er underdesign et større problem enn overdesign, og jeg setter faktisk pris på kode som den du viser i kommentaren. Kan du si noe mer om hvorfor dette er dårlig design?

    Kanskje jeg har gått for langt. Jeg vet ikke.., jeg endrer/utvikler jo både min egen oppfatning og fremgangsmåte hele tiden, så det er vanskelig å være alt for bastant når man ikke får erfaring med å gjør det samme over lang tid (selv om jeg ikke lar det stoppe meg fra å hardnakket argumentere for mine meninger). Det er derfor jeg liker så godt å blogge, sånn at jeg kan få testet mine erfaringer og meninger ut i den store verden.

  13. Gøran Hansen Says:

    Hei Torbjørn!

    Jeg vil også skrive meg inn i fanklubben din! Det er morsomt å lese dine poster. Du er flink til å skrive og kommunisere ditt budskap. Stå på!

    Jeg er også en stor fan av utenfra-og-inn fremgangsmåten, og bruker det selv flittig når jeg utvikler systemer med grafiske brukergrensesnitt. Jeg er helt enig med deg i at dette er en god fremgangsmåte for å oppdage avhengighetene – utenfra og inn, men det er ikke hovedgrunnen til at jeg selv jobber på denne måten.

    Jeg jobber utenfra-og-inn er på grunn av kunden. Kunder flest oppfatter datasystemer som brukergrensesnittet han/hun ser på skjermen. I mange tilfeller bryr de seg ikke om hvordan database som brukes, om det er 3-lags arkitektur, om det er tjenester (SOA), programmeringsspråk, platform osv. Selvsagt er det unntak, men de ødelegger poenget mitt:p Hvis jeg begynner med brukergrensesnittet, kan jeg ta kunden med i feedback loopen fra dag 1. Allerede etter noen linjer med programmering har jeg noe å vise frem til kunden – noe som kunden også forstår, nemlig skjermbilder. Jeg behøver ikke å si til kunden: du må vente en eller to måneder, for vi jobber med å få på plass ”arkitekturen”. Dette stimulerer til tettere samarbeid mellom leverandør og kunde, og minsker risiko. Kunden behøver ikke ”brenne” to måneder med utvikling for å se den første funksjonaliteten. Selvsagt er arkitektur viktig, men alt behøves ikke å gjøres up front – den kan også vokse organisk mens vi utvikler programvaren.

    Når det gjelder persistence ignorance er jeg helt enig. Jeg personlig, begynner ALDRI med relasjonsdatabasen. Jeg tenker ikke engang relasjonsdatabaser – jeg tenker persistering. Men jeg må vell også nevne at jeg i de fleste tilfeller jobber med OO :) Relasjonsdatabaser har sine bruksområder.

    Mocking er nyttig for å isolere avhengighetene, men ikke den enste måten å gjøre det på. Jeg har også gode erfaringer med å skrive FAKE implementeringer som benyttes i stedet for MOCK objekter. Da er det enklere for mindre erfarne utviklere å forstå hva som foregår. Jeg er helt enig med Johannes at MOCKs er for viderekommende. Dersom jeg jobber i et team med utviklere som ikke har erfaring fra TDD og mocking, begynner jeg forsiktig med å introdusere dem for FAKE implementeringer før MOCKs.

    Vi drives helt klart av forretningsbehov. Det er prosjektenes sponsorer (stakeholders) som sitter på pengesekken, og vi må gjøre som de vil. Det er ikke uvanlig at disse er ambivalent, og ønsker endringer fortere enn vi rekker å implementere eksisterende krav. For at vi som utviklere skal klare å være fleksibel ovenfor våre sponsorer, må vi ha en fleksibel kodebase. For å få en fleksibel kodebase så er TDD, BDD og mocking gode ”verktøy”. For å oppdage endringer tidlig er utenfra-og-inn en veldig god taktikk.

    Gøran

  14. Jonas Follesø Says:

    Flott post, med god diskusjon knyttet til den.

    Par kommentarer: Utenfra-og-inn-programmering fungerer like bra på API-er, web tjenester eller hvilken som helst kode. Det er ingen forutsetning at det du lager har et brukergrensesnitt. Og gjennom å skrive testen først, er du hele tiden en “klient” av koden du skal skrive, og på den måten kun implementerer det som strengt tatt er nødvendig. Så teknikken kan like gjerne brukes om du skal lage et API for å snakke med Twitter, eller en GUI applikasjon.

    Kommentar to: Det er en god del forvirring rundt begrepsbruken Mock, Fake, Test-Double, Stub osv. Jeg velger å følge Roy Osherove’s beskrivelse fra NDC. Alt er en stub inntil du gjør en assert. Da blir den en Mock. Og du skal kun ha en Mock per test (en logisk Assert).

    Så dvs. at Mocking vs Stubbing handler ikke så mye om hvilken “teknologi” eller verktøy du bruker, men snarere hvilken rolle objektet får når testen kjøres. De objektene du gjør assert mot blir mocks, de som spiller på lag for å returnere fake data er stubs.

    På prosjektet jeg jobber på nå skriver vi alle Mockene våre for hånd. Det er enkelt, lett og forstå og fleksibelt. Vi bruker public fields for å styre oppførsel.

    F.eks “KunderSomSkalReturneres”, “HentKundeBleKalt”, eller “SkalKasteException” osv.

  15. Torbjørn Says:

    Jonas, jeg er helt enig, dette har ikke bare med GUI å gjøre. I min “historie fra virkeligheten” implementerte jeg f.eks. en meldingshåndterer-modul som skulle kjøre i en win service.

    Når det gjelder begrepsforvissingen så er jeg enig med @kzu – utvikleren bak moq – da han sier at det egentlig ikke gir noen spesiell verdi å kunne skille mellom mocks, fakes, stubs, etc. Se min siste post: http://blog.kjempekjekt.com/2009/08/27/avhengighetsisolering-aka-mocking-i-net/

  16. Bjørn Says:

    Torbjørn, det er fett når man sitter på båten hjem, gjør følgende googlesøk

    http://www.google.no/search?q=crud+moq+repository&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:nb-NO:official&client=firefox-a

    og en kollega dukker opp blandt de første treffene!

  17. Torbjørn Says:

    Har du sett :) Ja, jeg er overaskende godt plassert på enkelte googlesøk. Er f.eks. nesten helt i toppen både på wpf og wcf på google.no, uten at jeg har skrevet ekstremt mye om noen av delene. Teller endel at jeg er en av ytterst få som skriver om systemutvikling på norsk.

  18. Strømlinjeformede enhetstester Says:

    [...] Les også mine mest populære testrelaterte artikler: Utenfra-og-inn programmering og TDD og mocking i praksis. tweetmeme_source = ‘tormaroe’; tweetmeme_style = ‘compact’; [...]

Skriv en kommentar

Tillatte tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


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 !...

Bjørn Einar Bjartnes: Jeg har også latt meg fascinere av Clojure, uten at jeg har kommet så veldig l...

Bjørn Einar Bjartnes: Sweet :) Jeg tror egentlig jeg liker det som det er, med musikk. Litt av utford...

Mulig relaterte linker

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • Template Method del 2: På vei mot funksjonell programmering
  • 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 (20)
  • 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