Hosstående opslag hænger på herretoilettet hos Microsoft i Hellerup. Det er en detaljeret, illustreret gennemgang af, hvordan man vasker hænder. Hvis vi ser bort fra den reelle risiko for en TL;DR-reaktion, må den minutiøse beskrivelse være tilstrækkelig til at tilgodese selv den mest nidkære bureaukrats behov for information, og ingen skulle således være i tvivl om, hvordan man vasker hænder i Hellerup.
Opslaget er mere underholdende end gavnligt i mine øjne, men det er desværre også et billede på den kode, de fleste af os til tider skriver. Jeg har læst og skrevet masser af kode, der i detaljeringniveau kan måle sig med ovenstående. Jo tættere vi kommer på metallet, desto tyndere bliver vores abstraktioner, og dermed bliver vi nødt til at redegøre for flere detaljer.
Vi kan således ikke helt undgå denne problematik, men jo mere vi kan styre uden om den slags desto bedre.
Problemet er, at det kan være svært at se, hvad målet er. Vi har specificeret, hvad der skal gøres, men det kan være svært at se, hvad det egentlig er, vi gerne vil opnå, og dermed kan det være svært at sige noget konkret om implementeringen. Fungerer den efter hensigten? Er der bivirkninger? Kan den optimeres om nødvendig? Kan den paralleliseres?
Hvad er formålet med instruktionerne i opslaget? Eller sagt på en anden måde: Hvad er det ønskede slutresultat, hvis vi følger proceduren? Er det rene hænder, reduktion af sæbebeholdning eller en sindrig form for håndaerobics i vand? Er det, vi ønsker at opnå, blot en delmængde af det samlede resultat? Hvordan skelner vi i så fald mellem de nødvendige og overflødige trin i processen for at opnå det ønskede?
Det kan være svært at adressere disse spørgsmål, da svarene ligger gemt i detaljerne.
Lad os se på et eksempel mere. Betragt følgende kode.
var numbers1 = new[] { 2, 4, 6, 7, 8, 9 };
var numbers2 = new[] { 1, 2, 3, 4, 5, 6, 9 };
var numbers3 = new[] { 1, 5, 7, 9 };
var result = new List<int>();
foreach (var n1 in numbers1) {
foreach (var n2 in numbers2) {
if (n1 == n2) {
bool found = false;
foreach (var n3 in numbers3) {
found |= n2 == n3;
}
if (!found) {
result.Add(n2);
}
break;
}
}
}
Hvad laver ovenstående? Med mindre man har set konstruktionen et utal af gange, skal de fleste af os nok lige tænke en gang eller to for at gennemskue, hvad formålet med koden er. Problemet er, at der er mange detaljer, så vi er næsten tvunget til at løbe et lille eksempel igennem, før vi kan gennemskue resultatet af koden. Det er ikke et stort problem, men vi skal bruge flere ressourcer på at forstå koden, end vi behøver.
Der er andre nærliggende spørgsmål, der ligeledes kan være svære at svare på her: Kan vi optimere koden, hvis behovet opstår? Hvor mange uafhængige operationer er der i koden? Kan disse paralleliseres?
Som du sikkert har gennemskuet, er indholdet af result alle de elementer, der er i både numbers1 og numbers2, men ikke i numbers3.
Det lugter lidt af mængdeoperationer, og med den erkendelse kan vi pludselig tale om problemet i domænespecifikke termer. Vi kan tilmed omskrive koden til at bruge .NETs mængdeoperationer. Ovenstående kan således udtrykkes som:
result = numbers1.Intersect(numbers2).Except(numbers3).ToList();
Vi kan selvfølgelig glæde os over, at dette er meget kortere, men det er kun en af gevinsterne her. Ved at bruge domænespecifikke abstraktioner kan vi kommunikere i et sprog, der giver mening i forhold til problemet, og vi er fri for at bekymre os om, hvad der skal til for at opnå det ønskede resultat. Vores kode er således mere deklarativ. Vi specificerer, hvad vi vil have, fremfor hvordan vi vil finde frem til resultatet i et sprog, der relaterer til vores problemdomæne.
Vi kan også let identificere antallet af nødvendige operationer, og derved har vi bedre mulighed for at identificere overflødige trin. I og med at vi arbejder med abstraktioner, er det derimod svært at sige noget om hvorvidt disse er implementeret optimalt, men med mindre vores målinger viser, at koden ikke kører optimalt, er vi sikkert bedre tjent med at bruge kode, der er testet og dokumenteret fremfor vores egen hjemmebryggede kode.
Hvis vi forestiller os, at result i stedet repræsenterer listen af kunder, der skal have et specielt tilbud. Således kunne numbers1 repræsentere de kunder, der interesserer sig for hi-fi, numbers2 kunne være listen af kunder, der interesserer sig for vvs-artikler, og numbers3 kunne holde styr på de kunder, der ikke har betalt til tiden. I så fald kunne vi bruge vores mængdeoperationer til at sende brev til relevante kunder omkring et nyt spabad med surround sound (hvis det ikke findes, skal det nok komme).
Koden ville essentielt være den samme, men den nuværende navngivning er helt i skoven. Koden taler om tal og mængder, og vi har således ingen forbindelse mellem koden og domænet. Så selvom .NET tilbyder os de nødvendige operationer, ville vi med fordel kunne indkapsle mængdeoperationerne i en eller flere metoder, hvis navne kan bygge bro til domænet.
Så hvad er lektien her?
Undgå udpenslende kode. Se om problemet kan udtrykkes på en måde, så vi kan bruge eksisterende funktionalitet. Hvis det ikke er tilfældet, så skab de nødvendige abstraktioner. I begge tilfælde bør abstraktioner navngives (evt. indpakkes) i konstruktioner, der giver mening i problemdomænet.
Følger vi disse trin, får vi kode, der er lettere at læse. Vi får muligvis mere kode, men hver del bliver kort, præcis og formuleret i et sprog, der passer til problemet. Vi får lettere ved at ræsonnere om kodens egenskaber, da den er lettere at overskue. Kode, der er let at forstå, er også lettere at vedligeholde og udvide.
Abstraktioner som typer og metoder er blandt af de vigtigste værktøjer i vores værktøjskasse, fordi de giver os mulighed for at opsplitte kompleksitet i overkommelige størrelser og navngive disse i et sprog, der afspejler vores problemdomæne. Brug dem!
For 3. gang i træk kan jeg sole mig lidt i indforstået, nørdet verdensberømmelse, idet jeg netop har fået besked om, at jeg har modtaget Microsofts MVP Award for 2010. Men selvom jeg har prøvet det før, er jeg alstå ikke mindre stolt af den grund.
Jeg holder desuden en lignende præsentation i forbindelse med ANUGs Visual Studio 2010 Launch Event. Det er et heldagsarrangement med masser af spændende emner. Deltagelse er gratis og 