EconomyDeluxe har udviklet sig - det har vist sig, at det var upraktisk ikke at kunne lukke applikationen, fordi alt information lå i hukommelsen. Der er derfor blevet oprettet User Stories til det sammen med nogle mindre funktionalitetskrav.
Der har dog allerede nu meldt sig en bug på banen. Et uheldigt medlem af udviklerstaben har misbrugt designet. Det vil sige, han har sådan set blot anvendt interfacet og dermed fundet et enkelt punkt, hvor designet ikke helt holdt vand. Problemet findes i interfacet til IGeneralLedger:
using System;
using System.Collections;
public interface IGeneralLedger
{
void AddAccount(ILedgerAccount account);
void DeleteAccount(ILedgerAccount account);
IDictionary Accounts{get;}
}
Problemet er måske ikke helt indlysende. Det er et forholdsvist enkelt design - og det skal vise sig at udviklerens dovenskab er problemet. For at gøre det enkelt (for ham selv) har han valgt at publicere IDictionary for Accounts. Det er faktisk meget uheldigt, fordi han dermed har publiceret indgange til data, som ikke er omfattet af test - og dermed må betragtes som en bug. For de læsere, som stadig ikke er helt med:
…
IGeneralLedger _generalLedger = new GeneralLedger();
…
_generalLedger.Accounts.Clear();
…
Heldigvis blev problemet hurtigt opdaget af brugeren, så vi kan nå at ændre interfacet inden der er kommet alt for meget funktionalitet i spil:
using System;
using System.Collections;
public interface IGeneralLedger
{
void AddAccount(ILedgerAccount account);
void DeleteAccount(ILedgerAccount account);
int AccountCount{get;}
bool ContainsAccount(int accountId);
}
Som altid er der et par designvalg inde over. Jeg kunne have valgt at indføre en konvention om, hvilke funktioner udviklere måtte have ‘lov’ til at bruge fra IDictionary’et… Dels giver det sig selv, at det ville blive uoverskueligt, hvis der var flere af den slags konventioner… og chancen for fejl ville være overhængende.
En rigtig grim og dårlig løsning ville være at lave min egen version af IDictionary, som nedarver fra Dictionary<int,ILedgerAccount>, override funktionerne, som ville give mig problemer og så kaste exceptions, hvis de blev brugt. I et svagt øjeblik, kunne man godt blive fristet af den løsning, hvis applikationen var i produktion og den samlede kodemasse var stor, men det er et design-smell, der lugter så grimt, at det under alle omstændigheder er en ulykke der venter på at ske.
Alternativt kunne jeg have valgt at lave mit eget wrapper-object omkring noget, der implementerer IDictionary, og så publicere (mappe) de funktioner, som jeg har brug for, og ville gøre det nemt senere at implementere alle de andre metoder jeg (højst sandsynligt) får brug for. Men i den begrundelse, ligger samtidig begrundelsen (pun intended) for hvorfor jeg ikke har valgt det… YAGNI.
Så har jeg valgt at publicere Contains alene med en int-værdi. Jeg kunne have valgt at lave den med en ILedgerAccount og så slå op om den’s Id lå i samlingen. Det har jeg valgt ikke at gøre ud fra flere hensyn: For det første gør det det uklart for andre udviklere, hvad den slår op i og med hvad - og hvad formålet med funktionen er. For det andet kunne Udvikleren (med rette) være bekymret for hvad der sker, hvis der er logisk lighed men ikke referentiel lighed osv. osv. Intentionen med kode skal være nem at læse - også ud fra interfaces.
Her var jeg heldig, at problemet blev opdaget forholdsvis hurtigt - hvis det havde været tæt på release, ville gode dyr have været rådne. Det var under testen af første iterations release, at fejlen blev fanget af en uforsigtig udvikler. Men det viser meget godt styrken ved iterativ udvikling. De fleste fejl bliver fanget imens det stadig er forholdsvis billigt at rette. Her var det fire linjers kode i min test-suite og 8 i min produktionskode. Prøv for tankeeksperimentets skyld at overveje hvor dyrt det ville have været med en fejl i kontoplanskoden i et fuldt ERP-program…
Jeg har nu haft en god uges tid til at reflektere og øve mig på BDD. Der er stadig områder, som jeg kun har berørt sporadisk, men der danner sig et billede. Jeg er på nuværende tidspunkt overbevist om at BDD er vejen frem. Selv for små projekter, kan det give mening at arbejde efter BDD. Jeg hygger mig stadig med EconomyDeluxe - det er tvivlsomt om det nogensinde når et niveau, hvor jeg vil begynde aktivt at forfølge en frigivelse, men det er stadig hyggeligt at have noget at fokusere sine eksperimenter med. Jeg vil komme med sporadiske opdateringer på, hvor jeg er i processen.
Samtidig har jeg stiftet nærmere bekendtskab med nogle gode værktøjer. Min definition af gode værktøjer er, at de gør mit arbejde lettere. Det kan lyde banalt, men et værktøjs rolle er ikke at ændre på min arbejdsgang eller introducere mig for nye arbejdsgange. De skal lette eller understøtte dem jeg allerede har. Nedenfor vil jeg nævne mine favoritter indenfor forskellige kategorier.
Vinderen for bedste overall test-suite er NUnit. Det er ikke uden grund, at langt de fleste TDD’ere bruger den. Det virker bare - og med tilføjelsen af Rhino Mocks er jeg nået rigtig langt. Jeg har stadig ikke nået at få stiftet bekendtskab med White - men jeg vender tilbage, når jeg får det testet. Det er bygget ovenpå Rhino og NUnit - og har sin force inden for testning af UI (og mere specifikt WPF). Jeg har været kort omkring NMock2, men kombinationen af at dens syntax er lidt knudret og tung samt at den ikke er typestærk gør, at Rhino løber med sejren. (Og en irriterende fejl i seneste version (2.0.0.44), som kaster exceptions ved mocks af interfaces med generiske metoder.
Vinderen for bedste utility er Reflector. Jeg har den konstant kørende i baggrunden som en udvidet hukommelse til at huske metodekald og obskure nedarvninger. ReSharper er jeg endnu ikke blevet dus med. Jeg er ikke i tvivl om at det er et fantastisk værktøj, men det kræver tid at vænne sig til og udnytte. Jeg er stadig i irritationsfasen, hvor den har ‘overtaget’ mine keyboard short-cuts i VS. Jeg håber på, at vi kan få det som et tema på arbejde med “de 7 taste-tryk, der forbedrer min arbejdshastighed med 50% ™”.
Og O/RM… NHibernate. Jeg har kun skrabet i overfladen af dette omfattende værktøj - men kan indtil videre kun sige… wow. Jeg har været omkring SubSonic og LiNQ. I det felt er NHibernate lysår foran. Lækkert, lækkert værktøj. Lidt knudret at sætte op og syntaxen er heller ikke 100% strømlinet, men hvad den taber på syntaxen mere end gør den op for i performance, vedligeholdelse og opsætningsmuligheder.
Nogle hårde, men lærerige måneder, som fundementalt har ændret min holdning og tilgang til kodning. Alt er ikke lyserødt, men fremtiden virker mere overskuelig…
Nedenfor er vist min første implementation af kode i EconomyDeluxe:
using System;
using Domain.Interfaces;
using System.Collections.Generic;
using System.Collections;
public class GeneralLedger:IGeneralLedger
{
private Dictionary _accounts;
public GeneralLedger()
{
_accounts = new Dictionary();
}
public void AddAccount(ILedgerAccount account)
{
if (account == null)
{
throw new ArgumentNullException(”Null account supplied”);
}
if (!account.IsValid)
{
throw new ArgumentException(”Invalid account”);
}
if (_accounts.ContainsKey(account.AccountId))
{
throw new ArgumentOutOfRangeException(”AccountId is already present”);
}
_accounts.Add(account.AccountId,account);
}
public void DeleteAccount(ILedgerAccount account)
{
if (account == null)
{
throw new ArgumentNullException(”Null account supplied”);
}
if (!_accounts.ContainsKey(account.AccountId))
{
throw new ArgumentOutOfRangeException(”AccountId is not present”);
}
_accounts.Remove(account.AccountId);
}
public IDictionary Accounts
{
get{return _accounts;}
}
}
Man kan med rette sige, at mit første eventyr ud i BDD har været forholdsvis simpelt. Det er dog ikke en begrænsning for at kunne se styrken i paradigmet. Jeg har implementeret de første to User Stories i programmet - og brugeren har sagt god for det. Brugeren har noget at vise sin chef (okay, han bliver sikkert ikke voldsomt imponeret, men bare vent til den næste iteration…) - fix og færdig funktionalitet. Programmet kan det, vi har bedt det om at kunne. Det er en af de ting, jeg er mest begejstret for i BDD - ting opfører sig som man har specificeret. Og hvis de ikke gør, så vil en af den række specifikationstests afsløre det ved næste gennemkørsel af test-suiten.
Koden er stort set selv-dokumenterende. Indtil videre har jeg ikke behøvet skrive kommentarer til koden. Ikke at det er en hindring for at gøre det - jeg bør gøre det senere, men jeg har ikke tænkt mig at trætte dine øjne med det - kodeeksemplerne er lange nok i forvejen. Nu kan man vælge at tro mig eller lade være, men ovenstående kodestump tog mig under 5 minutter at skrive. Der er ikke et gram unødig kode. Der er ingen bloat. Der er lige nøjagtig nok kode til at brugeren er glad, hvilket gør mig glad (skizophreni er nu en underlig ting, at skulle forholde sig til).
Men nogen vil måske sige, at det er totalt overkill, at jeg har brugt over 4 gange så mange linjer på test/dokumentation/UL som på kodelinjer i implementationen. Jeg er ikke enig. Min påstand er, at den x5 faktor med antal linjer stadig er mindre end hvis jeg var startet med at implementere fra starten uden User Stories, fordi jeg så ville have skullet ændre ting midt vejs, slette unødig kode, som virkede som en god ide på det tidspunkt.
Og ikke mindst, jeg slipper for at dokumentere kode, som ville have været mere knudret, fordi jeg uanset mine gode intentioner ville være startet med at løse 8 forskellige funtionalitetskrav på en gang (”Når nu jeg alligevel er ved at lave properties… så kan jeg ligeså godt oprette de her tre andre, som jeg sikkert får brug for… ooh, og alle ved at man skal bruge en IsDirty…) og det ville have været starten på bloat. Hvis det ikke er et krav fra brugeren - så er det per definition bloat. Brugeren har talt - og han sagde at det vigtigste for ham var at han ville kunne oprette og slette finanskonti. Ville det så ikke være fjollet at begynde at lave et avanceret logging system? Ja, skåret ud i pap, men det er hele essensen i, hvorfor de fleste projekter ender på de evige bit-marker. Det er brugeren der betaler - og jo hurtigere han kan se, at han får det han har bedt om - jo mere villig er han til at betale.
Oh ja, og det bedste af det hele? Jeg er ikke det mindste i tvivl om, at min kode virker, fordi jeg har ikke skrevet noget kode, som ikke er testet. Jeg har rent faktisk en code-coverage på 100%!