Pomodoro Technique på minst 2 och högst 4 konferenser i höst

2008-07-3 by Staffan Nöteberg

Jag har fått bekräftat från Øredev att jag ska hålla seminarium om Pomodoro Technique där. Sedan tidigare är det klart att jag ska prata på Agile2008 i Toronto och jag har skickat in förslag till två konferenser ytterligare:

Utöver detta så ska jag vid två tillfällen hålla tvådagarskurser i ämnet Agile på Informator (http://informator.se) - 15-16 september och 13-14 november

Mer om det som jag ska prata om finns på min mer aktiva blog: http://brainmoda.wordpress.com

Redundans

2007-11-29 by Staffan Nöteberg

Redundans är information som upprepar redan etablerad information utan att tillföra någon ny. Redundant information är därför överskottsinformation. Sådan information spelar dock en viktig roll i den förklarande halvan (The Explaining Half). Den förtydligar eller säkrar information. Men det kan också ta onödig plats eller bidra till oönskad upprepning.

Maximal redundans innebär total avsaknad av nytt. Allt det som finns i en förklaring kan härledas på annat vis. Det bidrar inte till att förklara lösningen.

Minimal redundans inträffar när allt i en förklaring är ny information. Inget i en förklaring bekräftar det som redan var känt.

Lajos har skrivit metoden printBL:

void printBL () {
  System.out.println ("Employee: " + name);
  String temp = "";
  for (Employee boss = this.boss; null != boss; boss = boss.boss) {
    temp += boss.name + " ";
  }
  System.out.println ("Bosses: { " + temp + "}");
}

Anico tycker att förklaringen inte är tillräckligt uppenbar. Hon vill att läsaren ska förstå den även med en mycket liten ansträngning. Därför ändar hon namnet på metoden till printBossList och byter också namnet den lokala variabeln temp mot bossList.

void printBossList () {
  System.out.println ("Employee: " + name);
  String bossList = "";
  for (Employee boss = this.boss; null != boss; boss = boss.boss) {
    bossList += boss.name + " ";
  }
  System.out.println ("Bosses: { " + bossList + "}");
}

Har Anico tillfört ny information? Genom att analysera Lajos implementation av metoden så hade en normalbegåvad programmerare kunnat dra den säkra slutsatsen att metoden skriver ut en lista med chefer och att den lokala variabeln är den sträng med chefsnamn som ska skrivas ut.

Anicos ändringar tillför redundans i syfte att förtydliga information. Även den som bara tittar hastigt på metoden blir övertygad om förklaringen.

Det finns en mänsklig instinkt att strukturera ny information med hjälp av redan förankrad kunskap. Vi vill tolka det okända med hjälp av det redan kända, få det som vi anar bekräftat. Ett visst mått av redundans krävs för att kunna utvinna kunskap ur information.

Antag att Anico hade vägrat att använda radbrytningar när hon skrev metoden:

void printBossList(){System.out.println ("Employee: "+name);String
bossList="";for(Employee
boss=this.boss;null!=boss;boss=boss.boss){bossList+=boss.name+"
";}System.out.println("Bosses: { "+bossList+"}");}

Den kompakta versionen är både svårare att läsa och förstå. Men den innehåller samma information om lösningen. Blocket är fortfarande ett block även om det skrivs kompakt.

Anicos ursprungliga version hade två egenskaper som signalerade att det var ett block. Dels började och slutade blocket med måsvinge. Dels var blocket formaterat efter teamets kodkonventioner för block, med måsvingarna på varsin rad och indenterad kod däremellan. Två egenskaper som ger samma information betyder redundans. En redundans som i det här fallet gör det svårare att missförstå Annicos förklaring av lösningen.

Brusig och riskfylld redundans

All redundans förtydligar inte. Etele lägger till en kodkommentar till den tredje raden:

  String bossList = ""; // initializing to empty string

Kodkommentaren är så redundant att den ökar bruset. Det är inte troligt att någon som inte ser koden till vänster skulle läsa kommentaren till höger och först då förstå.

Redundancy as noice and contradiction

När redundansen gör att det krävs mindre ansträngning för att förstå förklaringen och när den minskar sannolikheten för missförstånd, då förtydligar den. Det kan t ex vara:

  • strukturell enkelhet
  • regelbundenhet
  • namngivna designmönster
  • symmetri mellan kommunicerande moduler
  • namn strukturerade som i problemdomänen
  • begränsning av antal metaforer
  • kodkonventioner

Etele får i uppgift att ändra programmet så att listan med chefer indenteras ett tabulatorsteg:

  String bossList = "\t"; // initializing to empty string

Det som en gång var redundans har nu blivit en motsägelse. Kodkommentaren säger att strängen är tom. Koden säger att den innehåller ett tabulatortecken. Redundans i ett program som förändras är ofta en risk. Det är inte tillräckligt skäl för att inte använda redundans, bara en faktor för att balansera den.

Redundans som checksumma

Ibland finns det ett annat syfte med redundans än att förtydliga. Det är när man vill säkra information. Någonting förklaras på två sätt med uttrycklig avsikt att det ska gå fel om de två förklaringarna blir motstridiga. Programmeraren använder redundansen för att väcka uppmärksamhet när något är ologiskt. Anico skriver en metod:

void setBoss (Employee boss) {
  if (equals (boss)) {
    throw new IllegalArgumentException (name + " can't self boss");
  }
  this.boss = boss;
}

Metoden heter setBoss vilket övertygar läsaren om att den formella parametern är ett objekt med information om den anställdes chef. Den börjar sedan med ett test för att se om chefen och den anställda är samma person. Det ska det ju naturligtvis inte vara, i så fall hade metoden hetat något annat.

Om testet går fel så kommer programmet att krascha. Testet tillför ingen ny information som inte framgår av metodnamnet. Den säkrar informationen i en checksumma, redundans för att uppmärksamma ett ologiskt förhållande.

Exformation – det välkända som explicit exkluderas

Den danska fysikern Tor Nørretranders myntade uttrycket exformation - en förkortning för “explicitly discarded information”. I den förklarande halvan är exformationen all den relevanta information som programmeraren avsiktligt inte har med, men som krävs för att förstå förklaringen. Han har tagit bort exformationen eftersom han anser att det är kunskap som han delar med den som ska underhålla programmet. Den finns i huvudet på både den som skriver programmet och den som underhåller det.

Lajos skriver en klass för att hantera information om anställda:

class Employee {
  String name;
  int monthlySallary;
  Date employmentDate;
  Employee boss;
}

Ett av fälten heter monthlySallary. Ingen valuta är angiven. För den anställde är det naturligtvis väsentligt ifall han får sin månadslön som 7 000 dollar eller som 7 000 euro.

Eftersom kunden är ett företag i Finland utan anställda utomlands, så vet alla i teamet att det är euro som avses. Valutan är exformation i Lajos förklaring. Det minskar redundansen i förklaringen och gör den mer effektiv.

Exformation

För att exformationen inte ska leda till missförstånd så måste läsaren ha en tillräckligt stark repertoar (Repertoir). Alfred blir medlem i samma team som Lajos. Han har inte samma problemdomänkunskap som Lajos. T ex så känner han inte till att kunden är från Finland och kan därför omöjligt veta att valutan är euro. Fortfarande är det dock exformation eftersom Lajos avsiktligt har valt bort den informationen.

Program skrivna i dynamiskt typade språk har mer exformation är program skrivna i statiskt typade språk. Alfred skriver om klassen i Ruby:

class Employee
  attr_accessor :name, :monthlySallary, :employmentDate, :boss
end

Att namnet är en sträng, att monthlySallary är ett heltal, att employmentDate är ett datum och att boss en anställd framgår inte längre av förklaringen. Det har blivit exformation. Om man bara läser Ruby-programmet så är det lika sannolikt att boss är en sträng med ett namn som att det är ett objekt av typen Employee. Därför kan man heller inte veta vilka meddelanden som boss-objektet kan acceptera. Men så länge som hela teamet vet att boss är en Employee så är det exformation som effektiviserar förklaringen.

Transparens kontra abstraktion

Abstrahera är tekniken att förkorta. Onödig redundans tas bort, andelen information ökar.

Etele extraherar koden som skapar listan med chefer. Hon gör den till en egen metod som heter createBossList:

void printBossList () {
  System.out.println ("Employee: " + name);
  String bossList = createBossList ();
  System.out.println ("Bosses: { " + bossList + "}");
}

private String createBossList () {
  String bossList = "";
  for (Employee boss = this.boss; null != boss; boss = boss.boss) {
    bossList += boss.name + " ";
  }
  return bossList;
}

För den som inte vill veta exakt hur listan med chefer skapas så är det nu mindre redundans i printBossList. Det framgår fortfarande att listan skapas och tilldelas till variabeln bossList.

Med abduktivt resonerande väljer man den hypotes som, om den är sann, bäst förklarar de fakta man har. En bra abstraktion gör att abduktivt resonemang alltid leder rätt. Det räcker med att se anropet till en metod som heter createBossList för att abduktivt dra slutsatsen att det skapar en lista med chefer och inte t ex en lista med namn på georgiska jordbruksmaskiner. Man behöver inte se algoritmen.

Det är inte alltid som det går att skapa abstraktioner med självklara betydelser. Om två eller flera förklaringar är lika sannolika så är förklaringen entropisk. Det är ett tillstånd där det inte går att förutsäga vad som ska hända. Läsaren vill då kunna falla tillbaka på ett deduktivt resonemang istället – en situation där kända fakta leder till nödvändiga slutsatser. För att uppnå det behövs transparens som komplement till abstraktion.

Transparens är egenskapen att det syns hur något fungerar. När en mekanism som har abstraherats inte fungerar så som önskat, då måste det finnas transparens för att kunna förändra den.

Abstraction and redundancy

Anico får i uppgift att införa kommatecken mellan chefernas namn. Det framgår inte av anropet i printBossList huruvida det redan är kommatecken mellan namnen. Och det går absolut inte att förändra den egenskapen i den abstraherade metoden printBossList. Istället så behöver Anico navigera sig ner till metoden createBossList och där göra förändringen:

private String createBossList () {
  String bossList = "";
  for (Employee boss = this.boss; null != boss; boss = boss.boss) {
    bossList += boss.name + (null != boss.boss ? ", " : " ");
  }
  return bossList;
}

Redundansbehovet beror inte bara på läsarens repertoar utan även på hur detaljerad förklaring hon behöver. Helst ska förklaringen ha en dynamisk redundansfaktor. Den som vill veta det övergripande ska kunna göra det utan att lägga ner mycket tid och utan att behöva en stark repertoar. Men den som måste veta detaljer ska också kunna nå dem på ett smidigt sätt. För att lyckas med det krävs enkel navigering i kombination med balans mellan abstraktion och transparens.

Abstraktion minskar mängden detaljer som man måste kunna för att kunna förstå eller tillämpa något. Transparens gör att programmerarna kan förstå och lösa detaljerade problem. Abstraktionen gör förklaringen lättöverskådlig. Transparensen skapar fullständig ordning i förklaringen, entropin är låg. I en perfekt balans så är abstraktionen alltid det första man ser medan transparensen är lätt att hitta via navigering.

Arousal – hur eggande är en förklaring?

En viktig faktor vid inlärning är arousalen. Det är ett begrepp som är relaterat till tristess, uppmärksamhet, stress och oro. Arousalen är den energipotential som en person har vid ett givet tillfälle, den psykologiska beredskapen för aktivitet. Den påverkas av saker som händer runt personen och den påverkar personens möjlighet att prestera.

En lösning med en förklaring som har för mycket redundans upplevs av läsaren som tjatig och långtråkig. Det påverkar arousalen negativt. Samma saker återkommer om och om igen och dessutom är kanske exformationen mindre än vad den borde vara.

En förklaring med minimal redundans är å andra sidan obegriplig och även den en arosalsänkare. Om läsaren har en svag repertoar inom området så finns heller ingen möjlighet att dra fördel av exformation. Läsaren kan inte skapa sig en mental struktur från förklaringen. Han läser en chiffrerad text utan tillgång till nyckeln.

Arousal and redundancy

För att maximera arousalen ska förklaringen ha tillräckligt med ny information för att ta läsaren vidare till nästa kunskapsnivå, men samtidigt ha lämpligt med redundans för att ge läsaren struktur och sammanhang.

Arousalen är inte ett mått på hur lämplig läsaren är att ta emot budskapet, utan på hur entusiastisk han är för att få ta del av förklaringen. Yerkes-Dodsons lag visar ett empiriskt förhållande mellan arousal och förmågan att prestera.

Arousal optimum

Det är sannolikt att olika typer av uppgifter har olika arousala optimum. Svåra eller intellektuellt utmanande problem kan kräva en lägre arousal för att uppnå koncentration, medan uppdrag där uthållighet är en viktig förmåga kräver högre arousal för att öka motivationen.

Repertoar

2007-11-20 by Staffan Nöteberg

En människas repertoar är de förmågor hon har - det som hon klarar av att göra. Programmerarens repertoar spänner över många områden, som exempelvis problemdomän, datalogi, programmeringsspråk, verktyg, ramverk och processer. Förmågorna fördelar sig mellan den analytiska och den konstruktiva repertoaren. Även teamet har olika typer av repertoarer. Många av förmågorna används i den förklarande halvan (The Explaining Half).

Det engelska ordet repertoire betyder “ett lager med pjäser, sånger etc.” (engelska: a stock of plays, songs etc.). Det kom till engelskan från det franska répertoire under 1800-talet. Det ordet härstammar i sin tur från det latinska repertorium som betyder inventarium (engelska: inventory). Repertoire har två delar. Efter det intensiva prefixet re kommer den orddel som är en variant på latinets paerere - att producera eller ta fram (engelska: produce, bring forth). Repertoaren består alltså av ett inventarium av förmågor, redo att användas vid rätt tillfälle.

Datalogisk repertoar

Runo har implementerat fibonacci-funktionen:

int fibonacci () {
  if (n == 0) {
    return 0;
  } else if (n == 1) {
    return 1;
  } else {
    int temp = n;
    n = temp - 1;
    int r1 = fibonacci ();
    n = temp - 2;
    return r1 + fibonacci ();
  }
}

Han har använt en fri variabel n som måste tilldelas rätt värde innan funktionen anropas. Med hjälp av Elizas datalogiska repertoar så vet hon att det finns åtminstone två saker som gör en förklaring med fria variabler mindre övertygande om vad det är för lösning. Å ena sidan så kan Runos funktion förstöra för någon annan kod som inte förväntar sig att n förändras. Å andra sidan så kan Runos funktion sluta att fungera ifall den körs parallellt med någon annan funktion som manipulerar n. Ingen av de misstankarna kan uteslutas genom att läsa Runos funktion. Eliza ändrar därför koden:

int fibonacci (int n) {
  if (n == 0) {
    return 0;
  } else if (n == 1) {
    return 1;
  } else {
    return fibonacci (n - 1) + fibonacci (n - 2);
  }
}

Nu syns det i koden att lösningen fungerar. Eliza använde sin repertoar till att göra förklaringen mer övertygande om lösningen.

Programmeringsspråksrepertoar

I PHP finns två snarlika nyckelord: echo och print. Det är emellertid stor skillnad på dem. Endast print är en funktion som returnerar något:

$ret = print "Hello World"; // $ret is set to 1

Vidare så kan echo utan parenteser ta multipla parametrar som sätts samman:

echo  "count ", 1, 2, 3;   // comma-separated without parentheses
echo ("count 123");        // just one parameter with parentheses

Men, print kan bara ta en parameter:

print ("count 123");
print  "count 123";

De här skillnaderna är Runo mycket medveten om, det ingår i hans repertoar för programmeringsspråket PHP och han kan göra en mer övertygande förklaring av lösningen genom att välja den av print och echo som passar bäst i en specifik situation.

Verktygsrepertoar

En villkorssats med ett ensamt if gör att koden kan köras på två sätt beroende på om villkoret är sant eller falskt. Om det finns ett else-if i villkorssatsen så finns det tre vägar. Om det finns flera if-satser i samma metod så får man multiplicera alla deras antal möjliga vägar för att få fram antalet vägar i metoden. Komplexiteten för metoden ökar alltså ju fler villkorssatser det finns.

För att kunna minska komplexiteten så behöver man först ett verktyg som avslöjar var den är som störst. I den integrerade utvecklingsmiljö som teamet använder finns en funktion som kan peka ut de metoder som har flest traverseringsvägar. Eliza känner till hur man startar den funktionen och hur man läser resultatet. Genom att hon har den verktygskunskapen på sin repertoar så kan hon köra den automatiska analysen och sedan förbättra förklaringen genom refaktorera koden.

Problemdomänsrepertoar

I ett företagsinternt program som administrerar företagets löner har Emilio programmerat följande datastruktur:

class Employee {
  String name;
  int monthlySallary;
  Date employmentDate;
  Set bosses;
}

Naima har god kunskap om hur just det här företaget är organiserat hierarkiskt. Hon vet att en anställd kan ha högst en närmaste chef. Med Emilios förklaring så är det möjligt att lösningen tillåter att en anställd har flera chefer. Naima ändrar därför koden enligt följande:

class Employee {
  String name;
  int monthlySallary;
  Date employmentDate;
  Employee boss;
}

Det var möjligt för Naima att förbättra förklarningen genom att hon har kunskap om problemdomänen på sin repertoar.

Processrepertoar

Teamet har kommit överens om att alla kodrader ska vara täckta av minst ett enhetstest. Att teamet har kommit överens om att följa ett beteende innebär att beteendet ingår i processen. Baserat på vetskapen att testtäckningen ingår i processen så har teamet också kommit överens om att varje kodmodul kan refaktoreras av en godtycklig medlem i teamet. Naima kan ändra i kod som Emilio har skrivit. Om hon skulle förstöra lösningen så kommer testerna att signalera det brottet.

Testtäckningen löser dock inte problemet med spridning av kunskap om de olika kodmodulerna. Naima föreslår därför att medlemmarna i teamet ska lösa uppgifterna i par. Vem man parar med ska variera från dag till dag. På så sätt så kan man arbeta med okänd kod tillsammans med någon som har mer erfarenhet av den.

Naima vet att parprogrammering i processen sprider kunskap. Det finns på hennes processrepertoar. Alla i teamet vet att all kod ska täckas av tester. Det finns på deras processrepertoar. Processrepertoaren kan både vara att man vet hur den egna processen är och att man känner till processlösningar på nya problem.

Analytisk och konstruktiv repertoar

I den förklarande halvan använder programmeraren sin repertoar dels när hon läser och dels när hon skriver kod. Det innefattar analytiska och konstruktiva förmågor.

Runo använder ett verktyg för att generera javabönor till ramverket Spring. Först får han en javaklass av verktyget:

public class EmployeeBusinessObject implements Employee {
  private EmployeeDataAccessObject dao;
  public void setDataAccessObject (EmployeeDataAccessObject dao) {
    this.dao = dao;
  }

  public Employee getBoss () {
    return dao.getBoss ();
  }
}

Sedan får han en XML-fil:

<beans>
  <bean id="employeeDataAccessObject" class="EmployeeDataAccessObject">
    <property name="dataSource">
      <ref bean="systemDataSource"/>
    </property>
  </bean>
  <bean id="employeeBusinessObject" class="EmployeeBusinessObject">
    <property name="dataAccessObject">
      <ref bean="employeeDataAccessObject"/>
    </property>
  </bean>
</beans>

Runo läser de två filerna och förstår att metoden setDataAccessObject motsvarar den property som har namnet dataAccessObject i XML-filen. Om metoden byter namn till setDao så måste även propertyn byta namn till dao. På hans repertoar finns förmågan att förstå spring-filerna. Men han skulle inte kunna skriva de själv från början.

Men det finns också konstruktiva förmågor på Runos repertoar. Det kan vara förmågor som han har, men som han själv inte förstår vad de har för syfte. Han har blivit tillsagd att lägga in en speciell kommentar om företagets upphovsrätt i början av varje fil. Han vet precis hur han ska göra det, vad han ska skriva. Men ingen har talat om syftet med kommentaren.

Analytic and constructive repertoire

En programmerare kan alltså ha förmågan att förstå en förklaring när hon läser den, utan att hon själv hade kunnat tillämpa den för att övertyga om vad en lösning betyder. Men det kan också vara så att hon mekaniskt kan konstruera en förklaring utan att själv riktigt förstå vad den innebär. Naturligtvis är det också möjligt att hon både kan konstruera och förstå en förklaring eller att hon varken kan förstå eller konstruera.

Gruppens disjunktiva och konjunktiva repertoar

Kan man prata om en grupps repertoar? Ja, även gruppen har förmågor. Utifrån sett så klarar gruppen gemensamt av att producera de förklaringar som finns i koden. Om gruppen dessutom kan underhålla koden så har de även den analytiska förmågan att förstå förklaringen till lösningen. Så är dock inte alltid fallet.

Tidigare ingick Ulrich i teamet. Han skrev ett effektivt men komplext trådpoolramverk. Ingen annan i teamet har engagerat sig i att förstå designen av ramverket. Nu när Ulrich har slutat på företaget så kan teamet inte längre underhålla den koden. Gruppen saknar alltså en viktig förmåga på sin repertoar. De kan inte underhålla delar av sin egen kod.

Den grupprepertoar som beskrivs ovan innebär alltså följande: om och endast om minst en person har en förmåga - då har gruppen den förmågan. Den formen kallas för den disjunktiva grupprepertoaren. Eliza, Naima, Emilio och Runo bildar ett utvecklingsteam. Runo har stora kunskaper om problemdomänen. Naima är expert på Java. Emilio och Eliza kan alla viktiga kortkommandon i den editor som teamet använder. Alla tre förmågorna tillhör då teamets disjunktiva grupprepertoar.

Till skillnad från den disjunktiva grupprepertoaren finns de förmågor som alla i gruppen har på sina respektive repertoarer. Både Eliza, Naima, Emilio och Runo vet hur man skriver en for-loop i Java. Den förmågan är därför en del av den konjunktiva grupprepertoaren. Jodok börjar i teamet, med ansvar för användbarhet. Han har aldrig i sitt liv programmerat, men han kör bara systemet och ger synpunkter inom sitt ansvarsområde. Plötsligt tillhör då inte längre for-loopen den konjunktiva grupprepertoaren.

Team repertoire

Den konjunktiva grupprepertoaren är snittet av medlemmarnas repertoar och den disjunktiva grupprepertoaren är unionen av medlemmarnas repertoar. Allting som ingår i den konjunktiva grupprepertoaren ingår också i den konjunktiva grupprepertoaren – men oftast inte omvänt.

Direkt och indirekt repertoar

Många förmågor på repertoaren är man medveten om. Emilio frågar Naima ifall hon känner till designmönstret Observer. Eftersom hon vet precis vad Observer är och flera gånger har implementerat det, så svarar hon “ja”.

Men när Emilio nämner att han har använt designmönstret Visitor så tänker Naima att det är utanför hennes repertoar. Senare ser hon koden som Emilio refererade till. Hon analyserar den och förstår genast precis hur Visitor fungerar. Med hjälp av sina kunskaper om programmeringsspråket och objektorientering så hade faktiskt Naima indirekt designmönstret Visitor på sin analytiska repertoar.

Ulrich hade aldrig skrivit ett ramverk för trådpoolning. Däremot så hade han skrivit kod för poolning av andra resurser. Det i kombination med hans kunskaper om trådar gjorde att han hade förmågan att skriva ett fungerande ramverk för trådpoolning. Det fanns indirekt på Ulrichs konstruktiva repertoar.
Den indirekta repertoaren, eller implikativa repertoaren, är de förmågor som man har som en följd av eller genom kombination av de mer framträdande förmågorna på repertoaren.

Smala och breda förmågor

Olika förmågor kräver olika mycket ansträngning för att kunna adderas till ens repertoar. Alla som programmerar vet vad en villkorssats är. Det är bland det första som man får lära sig på den första programmeringskursen. Det krävs alltså inte så mycket för att lägga villkorssatsen till sin repertoar. Att kunna förstå och tillämpa villkorssatsen är därför en bred förmåga. Det är många som har den på repertoaren.

Att använda typer för att förklaringen av lösningen ska bli ännu svårare att missförstå är inte alltid trivialt. En programmerare med teoretisk kunskap inom typteori och med praktisk erfarenhet av vad som brukar missförstås har större möjlighet att ge en övertygande förklaring av lösningen. Emilio har skrivit den nästan dynamiskt typade funktionen fibonacci:

int fibonacci (Object object) {
  int n = Integer.parseInt (object.toString ());
  return n == 0 ? 0 : n == 1 ? 1 : fibonacci (n - 1) + fibonacci (n - 2);
}

Om funktionen anropas med att objekt som inte kan omvandlas till en sträng och därifrån till ett heltal så kommer funktionen att krascha. Den oron kan inte testas bort eftersom indata kan bero på något som användaren har matat in eller som har lästs från en fil under körning.

Naima är medveten om att den typen av problem kan dyka upp. Hon ändrar därför funktionen till följande:

int fibonacci (int n) {
  return n == 0 ? 0 : n == 1 ? 1 : fibonacci (n - 1) + fibonacci (n - 2);
}

Nu kan funktionen bara anropas med heltal. Naima, men inte Emilio, hade kunskapen att förklaringen är mer övertygande med en mer begränsad domän för en funktion. Hon har lärt sig detta i en designbok. Eftersom det krävs att man har investerat i den kunskapen för att kunna skriva sådana program så är den en smalare förmåga.

Fortfarande så kommer funktionen att hänga i en evig loop om den anropas med negativa tal. För att även lägga till en begränsning av detta i förklaringen så måste någon i teamet vara ännu duktigare på design. Ju mer den kunskapen har kostat i ansträngning, desto smalare är den förmågan.

Troper

2007-11-6 by Staffan Nöteberg

Retoriken fokuserar på skillnaden mellan vilket budskap som förmedlas och hur det förmedlas. Aristoteles kallade det för logos (det logiska innehållet) och lexis (stil med vilken det förmedlas). De formaliserade sätten att använda stil, att spela på våra känslor, kallas för stilfigurer. De delas in i scheman och troper.

Stilscheman är syntaktiska dispositioner, till exempel effektfulla omkastningar av ord. Troper är däremot semantiska. Ett etablerat uttryck för ett koncept ersätts effektfullt med ett uttryck som normalt förknippas med annat koncept – en vändning i ett ords betydelse.

Det finns fyra huvudtroper som alla andra troper kan härledas ifrån: metafor (strukturell likhet), metonymi (funktionell närhet), synekdoke (fysisk närhet) och ironi (motsats). De förekommer alla i den förklarande halvan (The Explaining Half).

Metafor och Metonymi

Metaforer uttrycker typiskt en abstraktion i en mer väldefinierad modell. Det okända förklaras med det kända. Vad som är känt och okänt, väldefinierat och abstrakt är naturligtvis subjektivt. Det beror på gruppens repertoar (Repertoire).

Likheten mellan källdomänen och måldomänen är strukturell. Det finns inte någon fysisk eller funktionell närhet i följande vardagsexempel:

  • databasen är ett arkiv
  • bitmönstret på hårddisken är en fil
  • kontakten på datorn är en port
  • gruppen som beslutar om nya uppgifter är ett Control Change Board

I en metonymi ersätts ett uttryck med ett annat som har funktionell närhet. Kenny säger “Jena är duktig på tangentbordet” och menar då att Jena är duktig på att programmera. Handlingen att programmera ersätts med objektet tangentbordet. Till skillnad från tropen metafor, där det sker en transferering från en domän till en annan orelaterad domän, så ersätter metonymin ett uttryck med uttrycket för en av dess aspekter eller kvaliteter – tangentbord och att programmera. Några andra metoymiexempel från vardagen:

  • producent för produkt: “jag kör Microsoft” istället för “jag kör Windows”
  • plats för institution: “Indien implementerar den funktionen” istället för “medarbetarna i Indien implementerar den funktionen”
  • institution för händelse “Imorgon är det CCB” istället för “Imorgon har CCB-gruppen möte”

I den förklarande halvan så namnges både datastrukturer och algoritmer med ord från vardagliga domäner. Ibland sker det på ett metaforiskt sätt och ibland på ett metonymiskt. Jena har skrivit en klass som heter Employee:

class Employee {
  String name;
  int salary;
}

Klassen ska användas i ett lönesystem. Alla vet att den egentligen inte är en fysisk anställd person, utan bara en datastruktur för att organisera information om en anställd person. Ett mer korrekt namn på klassen hade varit EmployeeInformationDataStructure. Kommer namnet Employee från en godtycklig domän med analoga strukturer? Nej, programmet hanterar ju anställda – Jena har alltså använt namnet på en roll (Employee) istället för namnet på en datastruktur som organiserar information om personer i den rollen (EmployeeInformationDataStructure).

Emplyee

Uttrycket Employee är inte hämtad från en godtycklig domän men liknande strukturer. Det kommer från just den verksamhet som programmet ska automatisera. Därför är det en metonymi.

Kenny jobbar också med lönesystemet. Tyvärr känner han sig inte bekant med lönedomänen. Han har däremot stor erfarenhet av monteringsjobb. Därför väljer han att använda termer från montering vid löpande band när han namnger sina artefakter i lönesystemet. Han ser strukturella analogier mellan komponenttunnor och de datastrukturer som organiserar information om konton för bonus, löner och andra utgifter:

public class ComponentBin {
  int amount;
  void takeOut (int amount) {
    this.amount -= amount;
  }
  void putIn (int amount) {
    this.amount += amount;
  }
}

Kontot är en komponenttunna, ta ur är att debitera, lägga i är att kreditera. De två domänerna – datastrukturerna i lönesystemet och montering vid löpande band – är helt orelaterade. Därför är det en metafor.

payroll

Metafor eller metonymi?

Vilka är fördelarna med metaforer jämfört med metonymier i den förklarande halvan?

  • Gemensam vision: om måldomänen är kändare i gruppen än vad källdomänen är så kan alla fortfarande vara överens om hur systemet fungerar och hur det ska bli
  • Inspiration: koncept som inte finns i källdomänen kan upptäckas med hjälp av angränsande koncept i måldomänen
  • Abstraktion: flera källdomäner som har analoga strukturer kan beskrivas med en gemensam måldomän – t ex i designmönster

Den första punkten behöver inte vara så långsökt som att använda löpande band för lönesystem. Det vimlar av begrepp i den förklarande halvan som beskriver tekniska koncept. Metaforen med filer och mappar är ett sådant exempel. Utan den så skulle alla som läser koden behöva ha detaljerad kunskap om hur en fysisk hårddisk fungerar med block, sektorer och index. I det exemplet finns även fördelen med abstraktion: om hårddisken ersätts med ett annat verktyg som fortfarande erbjuder samma typ av hierarkiska struktur så gör abstraktionen att bytet sker utan att den förklarande halvan behöver ändras.

Men det finns också många nackdelar med att använda metafor istället för metonymi:

  • domänexperter och programmerare har olika uttryck för samma koncept
  • extrapolerade metaforer kan leda till felaktiga ontologier (Ontology) och systembeteenden
  • koncept som dyker upp i systemets andra version finns inte i måldomänen

Naiva och döda metaforer samt liknelser

Ibland kallas metonymierna i den förklarande halvan för “The naïve metaphor” (sic) vilket naturligtvis är helt inadekvat eftersom de inte ens är metaforer.
Metaforer som är så accepterade att ingen längre tänker på att de är metaforer kallas för “döda metaforer”. Dit hör ord som socket, trådar, port och även namnen på de allra populäraste designmönstren.

De båda huvudtroperna metafor och metonymi skiljer sig från tropen liknelse (Similie). I en liknelse görs jämförelsen explicit. Kontot är som en komponenttunna, ta ur är som att debitera, lägga i är som att kreditera:

class LikaAComponentBin {
  int amount;
  void likeTakingOut (int amount) {
    this.amount -= amount;
  }
  void likePutingIn (int amount) {
    this.amount += amount;
  }
}

Exemplet känns konstruerat. Det beror på att metafor och metonymi är så mycket vanligare än liknelser i den här användningen.

Synekdoke

En synekdoke ersätter uttrycket för en helhet med en uttrycket för en av dess delar eller tvärtom. Det kan också vara en instans som ersätter en kategori eller tvärtom. Medan metonymin använder den funktionella närheten så använder synekdoken den fysiska närheten. Här följer några vardagliga exempel:

  • del för helhet: “fyra ögon upptäcker mer än två” istället för “två personer upptäcker mer än en”
  • helhet för del: “använd huvudet för att komma på lösningen” istället för “använd hjärnan för att komma på lösningen”
  • kategori för instans: “vi behöver två nya maskiner” istället för “vi behöver två nya datorer”

Alla varianterna av synekdoke är vanliga i den förklarande halvan. Ofta är de oavsiktliga och gör förklaringen mindre övertygande. Koden blir svårare att underhålla. Erhard har skrivit en klass som heter RobotProxy:

class RobotProxy {
  void moveLeftHand (int x, int y) {
    // some control code
  }
  void moveRightHand (int x, int y) {
    // some control code
  }
}

Med hjälp av metoderna moveLeftHand och moveRightHand kan ett program styra den högra och vänstra handen på en fysisk robot av typen R1. Instanser av klassen fungerar som en proxy mot en specifik hårdvara. Men hur är det med alla robotar som inte kan styras av just den här klassen? RobotProxy är en synekdoke som använder namnet på en kategori (robot) för en specifik instans (roboten R1).

robot

Synekdoker kan också växa fram i den förklarande halvan om programmerarna inte är vaksamma och ändrar namn vart efter en klass förändras. En klass som ursprungligen modellerar något från verkligheten kan senare få nya attribut som gör att den också representerar ting som inte täcks av det ursprungliga namnet.

Till den förklarande halvan hör även UML-diagram. Här används synekdoke avsiktligt för att få dem mer överskådliga. Att generera kompletta diagram från kod ger en detaljnivå som skymmer de viktiga resonemangen. Det är därför mer övertygande att noga välja ut vilka delar som representerar flöden, datastrukturer och andra delar av lösningen, när man ritar sina diagram.

Ironi

I tropen ironi används ett uttryck i motsatt betydelse till den bokstavliga. Sokrates ironiserade genom att spela dum och låtsas söka hjälp. På så vis avslöjade han okunnigheten hos motståndaren.

I den förklarande halvan förekommer ironi som ett predikat. Programmeraren markerar att han anser att något är sant just nu. Syftet är att fånga upp tillfällen när predikatet i själva verket är falskt. Immanuel skriver följande funktion:

int fibonacci (int n) {
  assert n >= 0 : "negative arguments not allowed";
  return n == 0 ? 0 : n == 1 ? 1 : fibonacci (n - 1) + fibonacci (n - 2);
}

I assert-satsen hävdar Immanuel att n inte är negativt. Han tycker inte att behandlingen av negativa n är ett normalt flöde. I så fall hade han använt en if-sats. Egentligen så vet han ju inte om n är negativt eller inte eftersom värdet beror på klientkoden. Det är Immanuel medveten om att han inte vet, ändå garanterar han för koden som följer efter assert-satsen att n inte är negativt.

assert

Ungefär på samma sätt fungerar automatiska tester, som ju faktiskt också är en del av den förklarande halvan. Testerna ber testobjektet utföra olika saker och säger sedan tvärsäkert att olika tillstånd existerar. Hela metodiken att låta tester driva utvecklingen bygger på att den som skriver testet påstår saker som han vet inte stämmer. Erhard skrev följande testkod, innan han ens hade implementerat metoden getLeftHandX:

class RobotTest extends TestCase {
  void testMoveLeftHand () {
    RobotProxy robot = new Robot ();
    robot.moveLeftHand (1, 1);
    assertEquals (robot.getLeftHandX (), 1);
  }
}

Återstoden av uppgiften blir sedan att ändra i testobjektet (RobotProxy) så att villkoret uppfylls – testet passerar och den gröna lampan tänds.

Ironi förutsätter att det finns ett binärt motsatsförhållande. Men det är ibland subjektivt. Att hävda att allt som inte är vitt är svart stämmer ibland och ibland inte. Därför kan ironi vara beroende av programmerarens världsuppfattning.

Ironi är inte samma sak som lögn. En trop har som uppgift att dekorera det logiska innehållet i ett meddelande. Just tropen ironi gör det genom att peka på ett motsatsförhållande. Erhard talar implicit om vad som inte är acceptabelt genom att explicit skriva vad han kräver. Lögner ska däremot förleda den som läser och är således ingen trop över huvud taget.

För att ironi inte ska misstolkas som ett bokstavligt meddelande så får det bara nyttjas vid tillfällen där både programmeraren och den som underhåller programmet är överens om att ironi är användbart. Automatiska verifieringar och designkontrakt är sådana väletablerade omgivningar för ironi.

Ironi kan annars fungera fatiskt (Fatic) på ett farligt sätt. Hugo har skrivit följande ironiska kodkommentaren:

    // This is probably the fastest algorithm in the world

Eugen underhåller senare koden och ser kommentaren. Eftersom han inser att det är ironi så känner han gemenskap med gruppen: vi som förstår att det är ironi. Han föreslår på nästa projektmöte att algoritmen ska optimeras. Innan det hinner genomföras så hinner Vendela bli ansvarig för just detta program. När hon läser kommentaren så vet hon inte att den är ironisk. Hon stryker därför optimeringen från uppgiftslistan: snabbast i världen borde väl räcka?

Konativ funktion

2007-10-23 by Staffan Nöteberg

Den konativa funktionen är vanlig i programmering. Programmeraren försöker prägla viljan hos den som läser koden att utföra en handling. Funktionen talar också om vem den tänkta läsaren är.

“Vår hudkräm ökar även din inre skönhet”. Reklam kännetecknas av säljarens uppmaning till köparen att just köpa produkten. Den konativa funktionen (Conative Function) i ett meddelande är författarens försök att skapa en drift eller strävan hos läsaren att utföra en handling.

I den förklarande halvan (The Explaining Half) är den konativa funktionen ofta närvarande. Adam har implementerat en funktion som svarar på huruvida en dividend är delbar med en devisor:

boolean isDivisor (int dividend, int divisor) {
  return divisor * (dividend / divisor) == dividend;
}

Men, att dividera med noll är normalt matematiskt odefinierat. Programmet kommer att krascha om en klient försöker. Därför lägger han till kodkommentaren:

boolean isDivisor (int dividend, int divisor) {
  // TODO: add error handling for zero divisor here
  return divisor * (dividend / divisor) == dividend;
}

Han uppmanar då den som underhåller koden att göra något: att implementera felhantering. Att kommentaren har en uppmaning till läsaren gör att den har en konativ funktion.

Beda får senare se koden och ersätter TODO-kommentaren med en metodkommentar:

/**
 * @pre divisor is not zero
 */
boolean isDivisor (int dividend, int divisor) {
  return divisor * (dividend / divisor) == dividend;
}

Nu tillhör det kontraktet att divisorn inte är noll. Det är en uppmaning till klientprogrammeraren: anropa inte denna funktion med noll som andra argument.

Vid körning beter sig programmet likadant i båda versionerna. Den lösande halvan är densamma. Men författaren försöker med hjälp av den förklarande halvan att skapa en drift hos läsaren att göra något. Adam uppmanar den som underhåller koden att införa felhantering. Beda uppmanar den som anropar koden att inte anropa med noll.

Den konativa funktionen avslöjar vem som är mottagare

Den konativa funktionen synliggör också att det finns en läsare av den förklarande halvan. Adams uppmaning visar att han anser att någon kommer att läsa i syfte att underhålla koden. Från Bedas kommentar förstår man att hon räknar med att klientprogrammeraren kommer att läsa kommentaren.

Conative function

En kodkommentar kan mycket explicit berätta både vad som ska göras och vem som är den förväntade läsaren. Uppmaningen att analysera ifall det behövs en optimering är riktad till medlemmar i projektet:

// the project should consider if this function could be optimized

Mottagaren kan också pekas ut mer implicit. Om språkbruket avviker från det normala så är det en indirekt signal om att målgruppen består av individer som har särskilda förutsättningar att förstå just detta språkbruk. Bara en person som vet vad en “rfm circulation” är kan lägga till den kontrollen i koden:

// add a rfm circulation check here

Konativa funktionen viktig i dynamiskt typade språk

I dynamiskt typade språk kan inte kompilatorn avgöra om det är rätt typ av indata. Sandor har implementerat fibonacci-funktionen:

def fibonacci (natural)
  if natural == 0 or natural == 1
    return natural
  else
    return fibonacci (natural – 1) + fibonacci (natural - 2)
  end
end

Genom att kalla den formella parametern “natural” så uppmanar Sandor klientprogrammeraren att bara anropa med positiva heltal och inte med t ex en sträng. I ett statiskt typat språk så skriver Sandor:

int fibonacci (int n) {
  return n == 0 ? 0 : n == 1 ? 1 : fibonacci (n - 1) + fibonacci (n - 2);
}

Begränsningen att den formella parameterns typ är heltal blir en uppmaning till klientprogrammeraren att inte anropa med en sträng.

Men fortfarande så kan metoden anropas med negativa tal. Även det kan Sandor begränsa i sin uppmaning, om han använder ett statiskt typat språk. Han implementerar en ny typ som heter Natural och som bland annat har egenskapen att den inte kan vara negativ. Den nya typen använder han sedan i funktionen:

int fibonacci (Natural n) {
  return n.equals (0) ? 0 : n.equals (1) ? 1 :
        fibonacci (n.sub (1)) + fibonacci (n.sub (2));
}

Fatisk funktion

2007-10-8 by Staffan Nöteberg

Innehållet i den förklarande halvan (The Explaining Half) har också en fatisk funktion. Programmerarnas medvetna val av stil och jargong i koden, visar vem de kommunicerar med. Fatisk kommunikation tillför ingen ny information utan bekräftar bara att kommunikationskanalen är öppen mellan programmerarna.

Leonard har just kommit till jobbet. I korridoren möter han Janina. De får ögonkontakt och Janina säger “Hej”. Leonard funderar på om det är någon mening att svara. Leonard och Janina känner varandra mycket bra, de är med i samma projekt och delar till och med rum. I och med ögonkontakten så är Leonard säker på att Janina vet att Leonard har uppfattat att Janina är där – så vad tillför det om han hälsar tillbaka? Han väljer att svara med ett “Hej”. Den här hälsningen har en fatisk (Phatic) funktion.

Leonards hälsning är redundant, men den är inte brus. En del yttranden har enbart till uppgift att underhålla kontakten mellan två personer. Den tillför ingen ny information utan bekräftar bara att kommunikationskanalen är öppen; sprider en känsla av gemenskap. Alla vi som säger hej till varandra i korridoren accepter att vi tillhör samma grupp.

Det finns många sorters fatisk kommunikation i ett program; redundanta delar i den förklarande halvan som har till uppgift att stärka gemenskapen. Tre exempel är kodkonventioner (Code Conventions), jargong (Jargon) och obligatorisk, men onödig dokumentation.

Det fatiska i kodkonventioner

Projektets process innefattar särskilda formateringsregler. Parenteser ska börja på samma rad som en funktions signatur:

int fibonacci (int n) {
  return n == 0 ? 0 : n == 1 ? 1 : fibonacci (n - 1) + fibonacci (n - 2);
}

Leonard följer inte den regeln, han tycker att det blir tydligare om man flyttar ner den inledande parentesen. Detta eftersom den då hamnar i den första kolumnen, precis som den avslutande parentesen:

int fibonacci (int n)
{
  return n == 0 ? 0 : n == 1 ? 1 : fibonacci (n - 1) + fibonacci (n - 2);
}

Fatisk kommunikation innefattar även att man är medveten om sin egen status i gruppen och att man accepterar andras status, samt att de accepterar din. Leonard accepterar inte regeln för kodkonventioner. Han visar medvetet att han står utanför gruppen. Han bryter den fatiska kommunikationen. Därmed inte sagt att Leonards kod är svårare att förstå för de andra i gruppen.

Det kunde ju också ha varit så att han bröt mot formateringsregeln eftersom han inte kände till dess existens. De andra medlemmarna i gruppen kommer då eventuellt uppfatta det som att Leonard förnekar sitt medlemskap i gemenskapen. Fatisk kommunikation är nödvändig för att upprätthålla sammanhållningen i en gemenskap. Konventionellt beteende stärker sociala relationer mellan människor.

Manfred lägger till ett automatiskt stöd som en del av proceduren att checka in kod i arkivet. Just innan koden lagras i arkivet så formaterar ett verktyg om den enligt kodkonventionen. Leonard känner då inte längre igen sin egen kod när han checkar ut den från arkivet. Den har mot hans vilja formaterats enligt projektets regler.

Plötsligt så har kodkonventionen inte längre en fatisk funktion. Alla vet att var och en kan ha formaterat som de själva ville. Manfreds verktyg gör att koden ändå kommer att följa reglerna när den checkas ut. Formateringen är inte längre författarens uttryckliga avsikt. Naturligtvis så fyller kodkonventionen andra funktioner än de fatiska. Det är t ex lättare att hitta i kod som är konsekvent formaterad.

Jargong som fatisk funktion

Jargong är ett gemensamt språk, inom en grupp, som är svårt att förstå för utomstående. Det kan t ex vara förkortningar som gruppen använder.

På samma sätt som slang så kan jargong skapa genvägar när man vill uttrycka idéer som är viktiga och återkommande inom gruppen. Ett ord som används i identifierare för olika variabler, metoder och klasser kan få en mer exakt betydelse inom gruppen, än vad det har utanför gruppen.

Fatisk hälsning med en Julius Caesar-tumme

Men jargong kan också ha en fatisk funktion. Alla som förstår de här uttrycken är med i gruppen. Det är ryskfyllt för personer utanför gruppen att använda jargongen. För om de gör det fel så är det en tydlig markör för utanförskap.

I en sjukhusapplikation så kallas patientens journal konsekvent för PH. Bokstäverna stod ursprungligen för Patient History:

void addRecordToPh(PhRecord phRecord) {
  db.save(name, phRecord);
}

Trots att PH i andra sammanhang är en beteckning på surhetsgraden och att det i det här fallet egentligen inte är en patienthistoria, utan just en journal så fortsätter gruppen konsekvent att använda uttrycket PH. Harriet väljer att ändra till det längre namnet PatientJournal:

void addRecordToPh(PhRecord patientJournalRecord) {
  db.save(name, patientJournalRecord);
}

Hon motiverar det med att det blir lättare för nya läsare att förstå fullständiga och korrekta uttryck. Det är på bekostnad av den fatiska funktionen som det förkortade och felaktiga uttrycket fyllde. Alla som förstod och använda gruppens betydelse av PH var med i gemenskapen.

Manfred utökar då processen till att även innefatta en hyperlänkad terminologikatalog: han startar en Wiki där han börjar beskriva projektets viktigaste koncept. Plötsligt blir begrepp som PH inte längre jargong, eftersom vem som helst kan läsa på Wikin vad det egentligen betyder. Fortfarande fyller dock PH en fatisk funktion. Den som använder begreppet PH visar att han pratar med gruppen.

Obligatoriska och onödiga kodkommentarer

I processen ingår att alla formella parametrar ska dokumenteras. Ibland tillför det något till den förklarande halvan, ibland är det bara brus. Ett trivialt exempel på det senare är:

/**
* @param n
*          integer parameter
* @return fibonacci value
*/
int fibonacci (int n) {
  return n == 0 ? 0 : n == 1 ? 1 : fibonacci (n - 1) + fibonacci (n - 2);
}

Om man på detta sätt följer steg i processen som man inte tror på eller inte förstår, bara för att man upplever att de har en fatisk funktion. Då får man väldigt lätt brus. Alternativet, att ta bort den meningslösa dokumentationen, hade å andra sidan gjort att den fatiska funktionen hade försvunnit. Att skriva kod utan de obligatoriska kodkommentarerna är, ur fatiskt perspektiv, som att inte hälsa i korridoren.

Det är inte bara i onödig dokumentation som det finns fatiska funktioner. Man kan ha en explicit vi- eller jag-du-jargong i kodkommentarerna:

// we need to invoke the framework initializer before going on here

Även det sprider en känsla av gemenskap. Budskapet är tydligt riktat till alla som läser den här koden. Kommunikationskanalen mellan programmerarna bekräftas. Förutom informationen om att ramverket ska initialiseras så har kodkommentaren en fatisk funktion.

Den förklarande halvan

2007-10-5 by Staffan Nöteberg

Allt det som får programmet att utföra det som användaren beställde är den lösande halvan. Allt som inte påverkar hur programmet beter sig är den förklarande halvan . Lösningen betyder något för användaren, medan förklaringen betyder något för den som läser källkoden.

Vid design av lägenheter i ett nytt hus gör byggnadsingenjören Biola en detaljerad konstruktionsplan. Planen är ett pappersark som ska användas som instruktion när de fysiska lägenheterna byggs. Det är en projektion från lägenhetens tre dimensioner till pappersarkets två. Biolas uppdrag är alltså inte att producera lägenheter, utan att ta fram en instruktion om hur lägenheterna ska byggas.

Hon har en rad symboler i sin repertoar (Repertoire). Symbolerna representerar t ex avlopp, bärande pelare eller fönster. Det är samma lösning oavsett vilka symboler som hon använder, men förklaringen av lösningen är olika.

Building Plan

Biola ritar med blyerts på pappersarket. Om hon istället hade använt en dator så hade hon kunnat göra en tredimensionell plan. Hon hade då förmodligen valt samma lösning – avloppet på samma ställe i lägenheten – men förklaringarna på pappersarket och i datorn är olika.

När lägenheten senare är färdig, så flyttar Nicholas in. Han bryr sig inte om vilken förklaring Biola gjorde på pappersarket. Det viktiga för honom är vad lösningen har för egenskaper. Sitter fönstret i söderläge?

Kodexempel

Fibonacci-funktionen är uppkallad efter den medeltida matematikern Leonardo Fibonacci från Pisa. Han använde den serie som funktionen bildar för att visa hur tillväxten av en idealiserad kaninpopulation ser ut. Lisa har implementerat en funktion som ger fibonacci-värdet av ett positivt heltal. Så här ser hennes källkod ut:

int fibonacci (int n) {
  if (n == 0) {
    return 0;
  } else if (n == 1) {
    return 1;
  } else {
    return fibonacci (n - 1) + fibonacci (n - 2);
  }
}

Ronny har också implementerat en funktion:

int l (int l) {
  return l == 0 ? 0 : l == 1 ? 1 : l (l - 1) + l (l - 2);
}

Faktum är att Ronny och Lisa har gjort samma lösning men de har presenterat olika förklaringar. Ronny har valt en kompakt förklaring, där både funktionen och den bundna heltalsvariabeln heter gemena L. Han har använt den trinära operatorn för att styra programflödet och han har bara en retursats.

När programmet senare används i produktionen så körs det av Kent. Han bryr sig inte om vilken förklaring som Ronny eller Lisa gjorde i källkoden. Det viktiga för honom är vilka egenskaper lösningen har. Är det 121.393 kaniner i 26:e generationen?

Det är en semantisk dikotomi

Programmeraren producerar en mängd artefakter, i huvudsak källkod och dokumentation. Inuti varje artefakt finns den semantiska dikotomin (Semantic Dichotomy). Allt det som får programmet att utföra det som användaren beställde är den lösande halvan (The Solving Half). Allt som inte påverkar hur programmet beter sig är den förklarande halvan (The Explaining Half). Lösningen betyder något för användaren, medan förklaringen betyder något för den som läser källkoden. Om programmet beter sig felaktigt så ligger felet i den lösande halvan. Konkreta exempel på den förklarande halvan är:

  • Kodkommentarer
  • Valda variabelnamn
  • Debug-loggning
  • Statiskt typat API som begränsar hur koden kan anropas
  • UML
  • Testkod
  • Assert-satser
  • Design, t ex inkapsling (Encapsulation), återanvändning (Reuse) och ett ansvar (Single Responsibility)

Semantic Dichotomy

Dikotomins gräns går inte mellan bokstäver eller ord? Nej, det är intentionen hos programmeraren som avgör vilken halva en förändring av artefakten tillhör. Om programmeraren rättar en defekt så uppdaterar han den lösande halvan, medan en refaktorering bara påverkar den förklarande halvan.

Notera att även en Obfuscator, ett program som skyddar intellektuell egendom genom att t ex på ett förvillande sätt byta namn på alla variabler, enbart påverkar den förklarande halvan.

Mål och syfte för den förklarande halvan

Målet, det förväntade resultatet, för den förklarande halvan är att den som läser källkoden ska bli övertygad (Convinced) om vad lösningen kommer att göra.

Syftet, varför programmeraren anstränger sig med den förklarande halvan, är mer komplext. Vem har nytta av att koden kan övertyga en läsare? När har man nytta av det? Svaret är att det finns många intressenter och de är intresserade vid olika tidpunkter. Här följer tre exempel:

Redan när Lisa skriver sin ursprungliga kod så läser hon hela tiden det som hon skriver. Hon granskar att lösningen är fullständig och att den gör det som hon vill. Den förklarande halvan av koden ska övertyga henne. Att läsa koden för att förklaringen ska övertyga läsaren om vad lösningen betyder kallas för kodkalkylering (Code Calculi).

En månad senare kommer ett nytt krav: ett Exception ska kastas när fibonacci-funktionen anropas med ett negativt tal. Lisa tittar då på koden som hon tidigare skrev. Den förklarande halvan övertygar henne om vad lösningen betyder och hon kommer fram till att hennes funktion ska inledas med:

  if (n < 0) throw new IllegalArgumentException(n + " < 0");

Det här är bara första gången som Lisa har nytta av att förklaringen kan övertyga henne om vad lösningen betyder. Varje gång som hon har behov av förändringar i koden så behöver hon bli övertygad.

Året efter får Nadja i uppgift att ändra meddelandet i det Exception som Lisa la till. Nadja läser då koden i hopp om att förklaringen ska övertyga henne om vilken lösning Lisa har valt. Hon förstår att koden ska ändras till:

  if (n < 0) throw new IllegalArgumentException (
      "Negative arguments are not allowed: " + n + " < 0");

Den förklarande halvan ska övertyga om vad den lösande halvan betyder, den ska inte övertyga om att lösningen är korrekt.