This is an archived page. Back to the blog.
C++-arkivet
 

Vad är bra kod?

(30 september 2009) Vi börjar med tipset:

Du skriver varje kodrad en gång, men den kommer att läsas hundratals gånger.
Investera lite tid idag för att tjäna mycket tid i framtiden:
Skriv din kod för att andra ska kunna läsa den.

Tips: hur skriver man bra kod?

Inspiration

Jag skriver denna artikel efter att ha läst boken "Code Complete: A Practical Handbook of Software Construction" av Steve McConnell (ISBN 978-0735619678, 960 sidor). Boken täcker det mesta inom programvaruutveckling och fokuserar på vad som är bra kod. Det är inte de gamla vanliga pekpinnarna utan nytänkande i nästan varje kapitel. Jag tror att alla kan bli bättre programmerare av att läsa den. Rekommenderas starkt! Code Complete

Vadå bra?

Man kanske undrar lite när man ser rubriken "vad är bra kod?", för "bra" är normalt sett relativt och olika för olika personer. När du skriver på ditt hobbyprojekt hemma är det förmodligen bra om det går snabbt att skriva koden. Och inget fel med det.

Mjukvaruföretag däremot, har en helt annan definition på vad som är bra. Och då handlar det förstås om pengar. Bra kod är den som är mest kostnadseffektiv i det långa loppet. Så hur skriver man kod som kostar lite pengar? I det korta loppet är det billigt att skriva sin kod på snabbast och enklast möjliga sätt. Gör alla privata medlemmar publika och några globala variabler senare så har du rättat det där felet på rekordtid. Problemet är bara vad detta innebär i det långa loppet. Vad händer när du eller din kollega måste in och lägga till lite ny funktionalitet? Om ni överhuvud taget vågar röra koden kommer ni att lägga en evighet på att förstå vad den gör. Sen inser ni förmodligen att den måste skrivas om från början...

Kod är ju något som hela tiden ändrar på sig, växer och utvecklas. Därför behöver koden tillåta ändringar. Man behöver kunna underhålla och bygga på den. För att få ned kostnaderna för mjukvaruutveckling i det långa loppet behöver vi alltså bra mjukvara att bygga på.

Men... Vadå bra?

Så åter till frågan: "Vad är bra kod?" Vad är det som gör koden lätt att förstå och underhålla? Det är kod som hjälper läsaren! Kompilatorn har inga problem att förstå koden, men läsaren har stora problem. Skriv all kod på en rad så får du se.

"Men mina kollegor är ju jättesmarta", säger du. Varför behöver vi hjälpa läsaren? För att hjärnan inte räcker till för att hantera mer än ett miniexempel. Det sägs nämligen att arbetsminnet kan hålla reda på ungefär sju saker samtidigt. Ta en titt på din kod och tänk på alla kombinationer av if/else, funktionsanrop, variabeltilldelningar osv. De många detaljerna gör att hjärnan snabbt blir överlastad.

spaghetti

Vi tar ett exempel: Antag att du vill ta reda på vad en funktion performAction() gör. Några observationer:

  • Namnet performAction säger dig ingenting.
  • All kod får inte plats på skärmen samtidigt.
  • Alla variabler heter i, j, k, a, b och c.
  • För att minnas vad variabeln a representerar måste du ta upp en av de värdefulla sju platserna i ditt arbetsminne.
  • Alla funktioner som anropas från performAction heter execute, handle och process, eller ännu värre, foo och bar.
  • Namnet på funktionen execute säger dig ingenting. Du måste söka upp definitionen och spendera tid på att förstå vad den gör. Sen måste du ta upp en massa plats i ditt arbetsminne för att komma ihåg vad den gör.
  • Har execute bieffekter (såsom utmatning eller skrivning till globala variabler) så måste du hålla detta i huvudet.

Känner du igen dessa symptom? Om inte performAction är väldigt kort finns det ingen chans för dig att snabbt förstå vad den gör. Och här kommer själva kruxet: förstår vi inte vår programkod in i minsta detalj får vi problem av olika typer. Kanske inte idag, men någon gång i framtiden. Din dåliga kod riskerar att resultera i en ond cirkel:

  • Massor av fel kommer att uppstå. Det är som att läsa ett främmande språk, stavfelen kan vara hur tydliga som helst, men förstår man ingenting har man ingen chans att hitta dem.
  • Det är svårt att testa bort dessa fel. Man måste förstå vad koden ska göra om man ska veta vad man måste testa.
  • Man kan inte rätta felen om man inte förstår den omkringliggande koden. Då uppstår oftast nya fel.

Hjälp läsaren

Alltså, koden måste skrivas för att hjälpa läsaren. Men hur? Jo, man behöver minska komplexiteten för läsaren genom att avlasta läsarens arbetsminne. Man kan göra detta på massor av sätt. Ett första tips är förstås att undvika obegripliga namn som i exemplet med performAction. Här kommer jag att diskutera två andra sätt:

  • Använd lokala variabler
  • Blanda inte abstraktionsnivåer

Använd lokala variabler

Det här med att lokala variabler är att föredra framför globala variabler har ju alla hört förut. Och det låter ju jättelätt, så vad är problemet? Problemet är bara att det inte är så lätt! Det är faktiskt hårt arbete och kräver disciplin. Låt mig försöka förklara vad jag menar.

Varför använder man globala variabler överhuvud taget? En anledning är att det är bekvämt! Antag att jag skriver ett program som hanterar anställda. Jag har en massa funktioner som arbetar på klassen Employee (t.ex. en som heter prepareSalaryPayout). Mina funktioner anropar varandra och skickar med den anställde, kanske i många led. Till slut tröttnar jag på att lägga till Employee i alla funktionsdeklarationer och -definitioner. Jag överväger att spara undan den aktuelle anställde i en variabel employee i klassen Application. Då blir allt lätt och jag slipper skriva så mycket! (Och ja, variabelnamnet lämnar också en del att önska.)

Vissa är mindre lokala än andra

Så vad är problemet? Det är ju inte en global utan en medlemsvariabel... Visst, visst, men varför lade jag till den? Anledningen var ju inte att Application behövde en medlemsvariabel. Anledningen var att jag ville utbyta information mellan mina funktioner. Och jag var för lat för att skicka den mellan funktionerna. Ur funktionernas synvinkel kunde variabeln lika gärna varit global.

jordglob

Den som läser funktionen prepareSalaryPayout kommer att se variabeln employee användas. Jaha, var kom han ifrån? Var finns variabeln deklarerad och vilken typ har den? Man hittar sen employee i Application och då uppstår nya frågor. Vilka funktioner går sönder om jag ändrar den? Vem ändrar den förutom jag? Vem läser den?

Nu kanske det låter som att poängen var att globala variabler är roten till all ondska. Men poängen är faktiskt en helt annan: Jag sparade lite tid när jag valde en variabel som var mer synlig än nödvändigt. Men jag skriver bara koden en gång. Varje rad kod kommer förmodligen att läsas hundratals gånger i framtiden. Varje gång någon läser min kod kommer de att spendera onödig tid på att försöka förstå koden. Och obehaget av sidoeffekter hos min variabel kommer att bestå. Så ha nästa version av din applikation i åtanke när du skriver din kod!

Synlighet

Hur som helst är det aldrig fel att föredra lokala variabler framför globala. Lokala och globala variabler kommer i flera olika förklädnader. Här följer en kort lista på olika synlighet hos variabler:

  • globala variabler - ger bieffekter mellan alla delar av ditt program, lättast att skriva men svårast att förstå
  • klassvariabler - ger bieffekter mellan objekt (om privat variabel, annars lika illa som globaler)
  • medlemsvariabler - ger bieffekter mellan medlemsfunktioner
  • funktionsargument - bra
  • lokala variabler i funktion - bättre
  • variabler i lokalt scope - ännu bättre
  • definition precis innan användning - bäst, lättast att hitta och förstå

Längst ner i listan hittar vi variabler där definitionen finns nära användningen av variabeln. För en global variabel gäller det omvända. Försök att hålla dina variabler så lokala som möjligt!

Blanda inte abstraktionsnivåer

Din högsta chef lägger sig (förhoppningsvis) inte i om du glömmer const lite här och där. Han är mer fokuserad på högnivåmål såsom försäljningen på den asiatiska marknaden. Skulle högsta chefens budskap ligga på alla nivåer samtidigt ("Vi ska avancera på den asiatiska marknaden! Använd const!"), så skulle budskapet bli förvirrat. Det skulle vara svårt att avgöra vad som är viktigt. Samma gäller för din kod.

Ta ett exempel från Code Complete: Du har en klass EmployeeHandler med funktioner addEmployee och removeEmployee. Du vill nu lägga till möjligheten att iterera över dina anställda. Inuti klassen har du en lista. Du lägger till funktionen getNextItemInList(). Den som läser din kod ser detta och tänker "Item? List?". Funktionerna add/removeEmployee pratade om anställda, vilket är på en helt annan abstraktionsnivå än item och list. Ett bättre namn skulle vara getNextEmployee().

Hierarkiskt ordnad

Kod som inte håller sig på en abstraktionsnivå blir lätt ganska spretig. Det är lätt att de viktiga bitarna drunknar i informationsmängden. Ta exemplet med debuggning av din kod. Du stegar igenom en funktion prepareSalaryPayout. Koden blandar detaljerade begrepp (strängmanipulation, utskrifter etc.) med högnivåbegrepp (anrop till funktion som beräknar lön, skatt etc.). Vissa rader du stegar förbi gör 25% av det totala arbetet. Andra rader gör 0,25% av arbetet. Det behövs en ansträngning för att inte missa det viktiga.

Det omvända är när programmets olika delar var och en håller sig inom en abstraktionsnivå. Då bör du kunna hitta en del av koden som bara handlar om högnivåbegrepp. På så sätt kan du lätt fokusera på den del som är fel och därifrån gräva dig ned. Koden blir hierarkiskt ordnad på ett helt annat sätt.

Till sist

Det är svårt att ge en uttömmande lista på saker som gör din kod lättare att läsa. Det handlar mer om den inställning man har. Så när du skriver kod nästa gång, ha läsaren i åtanke hela tiden!



Relaterade artiklar: