Sidste uge var der Dotninjas Photowalk ved Vallø Slot. Til trods for dårligt vejr og et lidt sølle fremmøde, havde vi 3 ninjaer en fremragende tur, der blev afsluttet med frokost på en café i Køge.

Billeder blev der heldigvis også taget lidt af.  Her er et udpluk fra mit eget galleri.

 DSC_0552DSC_0570   DSC_0586 DSC_0599 DSC_0594

Vi er nok mange, der kan blive enige om, at iTunes er lige så dårligt, som iPhone er fed. Forleden kom jeg dråben, der fik mit bæger til at flyde over, og iTunes blev afinstalleret.

Det var i forbindelse med, at jeg forsøgte at opgradere min Windows Vista Ultimate til Windows 7 Ultimate.  Inden installationen blev jeg advaret af Windows 7 installationsprogrammet om, at iTunes måske ikke ville fungere korrekt efter opgraderingen, men det valgte jeg at se stort på.  "Der kan vel ikke ske noget ved, at det ligger der", tænkte jeg.

Installationen forløb glat.  Efter opgraderingen var slut, og maskinen genstartede første gang, fik jeg den frygtede Blue Screen of Death.  Heldigvis er Windows 7 cool nok til at rulle tilbage til den oprindelige installation, når den ikke kan genstarte.  Sejt.

Jeg fjernede et par programmer såsom MagicDisk, og prøvede igen.  Samme resultat.

Jeg fjernede et par andre programmer og hev stikkene til printere og andet løst hardware ud.  Samme resultat.

Husk at hver gang gik der 1-2 timer, før maskinen genstartede.

Jeg afinstallerede iTunes.  Det virkede!

iTunes kommer ikke tilbage på min hjemmemaskine foreløbig.  Til gengæld er jeg indtil videre glad for Windows 7.

Måske er jeg ved at blive gammel, eller måske er jeg blot kommet til den indsigt, at bliver man længe nok på samme arbejdsplads, så er så godt som alle nye og spændende projekter også ensbetydende med endnu et projekt, der skal vedligeholdes over tid. Selv projekter, der blot skal skydes af en enkelt gang, har en tendens til med jævne mellemrum at dukke frem til en ny omgang. Hvad enten det skyldes det ene eller det andet, så er ord som standardisering, genkendelse, testbar begyndt at fylde mere og mere når nye projekter angribes - der er jo stor sandsynlighed for at jeg selv ender med aben, når min egen kode på sigt skal vedligeholdes :-)

 Jeg har noget tid anvendt StyleCop til at forcere en ensartethed ned over min kode. Ja, det kan være mega-irriterende at være tvunget til at skrive kommentarer og flytte rundt på metoder og properties for at tilfredsstille StyleCop, men i sidste ende bliver koden lettere at læse, når man gør det på samme måde på tværs af projekter. For at komme lidt videre i samme spor købte jeg Robert C. Martins Clean Code: A Handbook of Agile Software Craftsmanship

Mine forventninger til bogen var lidt de samme som da jeg i sin tid tog StyleCop i brug, nemlig at det kan godt være at man ved hvordan det skal gøres, men nogle gange kan det være en fordel at blive mindet om det endnu engang. De forventninger lever bogen til fulde op til, men meget mere synes jeg heller ikke der er at hente, hvis man programmeret i nogle år. Bogen anvender eksempler i Java, men kan sagtens læses med tanke på C# eller VB.Net.

En af de ting, jeg har taget til mig fra bogen finder man allerede i første kapitel. Martins kalder den "The Boy Scout Rule" og går i sin enkelhed ud på, at man skal efterlade sin kode pænere end da man fandt den. Med andre ord; hver gang man er i et hjørne af sin kode, man ikke har været i længe, så brug lige et øjeblik på at gøre koden lidt pænere. På den måde bliver hele projektets kode gradvis bedre med tiden.

Andre udsagn finder jeg knapt så logiske. Eksempelvis argumenterer han under navnekonventioner for, at et interface ikke skal prefixes med stort I. Det distraherer er argumentet, men her vil jeg nok fortsætte med at følge StyleCops regler, der netop påtvinger I som prefix. De fleste regler er dog gode at få genopfrisket, og meget praktisk er disse regler samlet i kapitel 17 - Smells and Heuristics. Langt de fleste, der har programmeret nogle år vil formodentlig kunne nøjes med de 30 sider som dette kapitel udgør, og disse 30 sider kan med fordel skimmes med jævne mellemrum. 

Det virker som om forfatteren har haft lidt svært ved at beslutte sig for hvem bogen skal skrives til. På den ene side genopfriskes helt basal viden som at navnet "modificationTimestamp" er bedre end "modymdhms" (selvom ymdhms afledt af formatet - yy/mm-dd hh:mm:ss). På den anden side forventes en grad af indsigt, når der pludselig introduceres en stump unit test kode ca. 20 sider før kapitlet om unit tests. Ca. 150 sider af bogens 400 sider er to marathon eksempler, hvor bogens regler og refaktoreringsmetoder hældes ned over to rigtige cases. Sider, der i bedste fald blot ender med at blive skimmet - selv hvis man uøvet ud i at vedligeholde kode.

Alt i alt en udmærket, men lidt kedelig bog, til at minde én om at gøre al kode lidt bedre, og ikke blot opfylde kravsspec på kort sigt. Hvis jeg var chef ville jeg tvinge mine medarbejdere til at læse den - jeg tror mange, ville have gavn af en opfrisker. Vi ender på 4 små ninjastjerner pga. value-for-money. Set til kr. 243,-

Det er endelig lykkedes mig at finde et lille projekt, som jeg kan køre strengt efter TDD metodikken, med andre ord testen skrives først. I den forbindelse er der noget mere fokus på access modifiers end når vægten er på integrationstests, men man må jo hænge i og gøre plads til injektere fake objekter efter bogen.

Mens jeg er godt i gang med implementering af validering i et business objekt støder jeg så ind i en "hvorfor-nu-det" situation, som stadig ikke er helt klar for mig. Vi har følgende setup eksempliceret ved det klassiske books eksempel.

I klassen "BookBusinessObject" ønsker vi at teste om en given bog (CurrentBook) findes i en liste af bøger (ListOfBooks). Listen kan fx initialiseres via opslag i en database men for at lette presset lidt på databasen gemmes listen i en statisk liste (books). Den unge dansker skriver sin unit test og får en fin grøn lampe fra NUnit med følgende kode;

    1 using System.Collections.Generic;

    2 using NUnit.Framework;

    3 using Rhino.Mocks;

    4 

    5 public class Book

    6 {

    7     public string Author { get; set; }

    8     public string Title { get; set; }

    9 }

   10 

   11 public class BookBusinessObject

   12 {       

   13     private static List<Book> books;

   14 

   15     public Book CurrentBook { get; set; }

   16 

   17     public virtual List<Book> ListOfBooks()

   18     {

   19         if (books == null)

   20         {

   21             InitializeBooks();

   22         }

   23 

   24         return books;

   25     }

   26 

   27     private static void InitializeBooks()

   28     {

   29         List<Book> bookList = new List<Book>();

   30         bookList.Add(new Book { Title = "Microsoft .NET: The Programming Bible", Author = "O'Brien, Tim" });

   31         bookList.Add(new Book { Title = "XML Developer's Guide", Author = "Gambardella, Matthew" });

   32 

   33         books = bookList;

   34     }

   35 

   36     public bool IsBookInList()

   37     {

   38         foreach (Book b in ListOfBooks())

   39         {

   40             System.Console.WriteLine(b.Title);

   41         }

   42 

   43         return this.ListOfBooks().Contains(this.CurrentBook);

   44     }

   45 }

   46 

   47 [TestFixture]

   48 public class BookBusinessObjectTest

   49 {

   50     [Test]

   51     public void IsBookInList_BookInList_ReturnTrue()

   52     {

   53         // arrange

   54         List<Book> fakeBookList = new List<Book>();

   55         Book fakebook = new Book { Title = "TDD for dummies", Author = "John Doe" };

   56         fakeBookList.Add(fakebook);

   57 

   58         BookBusinessObject businessObjectMock = MockRepository.GenerateStub<BookBusinessObject>();

   59         businessObjectMock.Stub(x => x.ListOfBooks()).Return(fakeBookList);

   60 

   61         businessObjectMock.CurrentBook = fakebook;

   62 

   63         // act

   64         bool included = businessObjectMock.IsBookInList();

   65 

   66         // assert

   67         Assert.IsTrue(included);

   68     }

   69 }

Der hviles et par minutter på laurbærerne inden næste test skrives. Det er ikke nok at vide om en given bog findes i listen, der skal også tilføjes andre valideringsregler så der kan med fordel oprettes en IsValid() metode, der kalder alle business objektets valideringsregler og giver en samlet vurdering af om business objektet er valid.

I unit testen for IsValid() kan det være en fordel at vi i stedet for at lave en fakeBookList blot laver et fake metodekald til IsBookInList() der altid returnerer true. Dermed skal der ikke sættes en masse op for at test IsValid(), hvis vi antager at IsBookInList() blot er én af mange ting, der skal være opfyldt.

Så for at Rhino Mocks kan fake retursvaret fra IsBookInList() ændrer vi accessoren i IsBookInList() fra "public" til "public virtual", men hov!! Det giver problemer, for nu siger NUnit;

TestCase 'BookBusinessObjectTest.IsBookInList_BookInList_ReturnTrue' failed:
  Expected: True
  But was:  False

0 passed, 1 failed, 0 skipped, took 2,64 seconds (NUnit 2.4).

Og det store spørgsmål er; hvorfor nu det?!?

Hjælpelinjerne 38-41 angiver, at fakeBookList ikke bliver injekteret da ListOfBooks() er tom. Ændres accessoren i stedet til "internal virtual" så er testen igen OK. Hvor er det at filmen knækker? Og hvad er best practice?