Stil och sånt

Jag hamnar ofta i diskussioner om varför jag tycker att det är viktigt att man inte blir alldeles för kreativ när man skriver kod och varför det är viktigt med att hålla koden ren, snygg och konsistent. Nedan har jag försökt organisera några tankar om detta och försökt att förklara varför jag tycker som jag gör och organisera argumenten.

TES: Stil och konsistenta idiom i programmering är av central betydelse för kodens kvalitet.

Även om det kan kännas uppenbart vad man menar med stil och idiom kan det vara på sin plats att förklara vad dessa två begrepp betyder.

Stil är den egenskap av kod som inte påverkar den syntaktiska tolkningen. En parser bryr sig inte alls om var måsvingarna efter if-sats sitter, vilka variabelnamn man använder, hur kommentarerna formatteras, var man lägger in mellanrum, vilket indenteringsdjup man använder.

Idiom är den samling kodkonstruktioner som man använder som skiljer sig syntaktiskt, men inte semantiskt.

   for (i = 0; i < 17; i++) {
   }
  
gör samma sak som:
   i = 0;
   while (i < 17) {
     i++;
   }
  
Dessa två konstruktioner är idiom för samma sak. De är semantiskt lika, dvs. de gör samma sak, men de är inte syntaktiskt lika, en kompilator kommer att bearbeta dem på olika sätt (åtminstone i början).

Givetvis är gränsen mellan idiom och stil ganska flytande. Om man är petig kan man säga att även identeringsdjupet ändrar hur programmet parsas, men så länge man inte leker djävulens advokat och sysslar med hårklyverier borde det vara ganska uppenbart vad som menas.

Kreativitetens fingeravtryck

Det är väldigt många människor som kan lyssna på ett kort stycke musik och snabbt avgöra vem som är kompositör, vem det är som framför stycket, vem som är dirigent och när inspelningen gjordes. De kan göra detta utan att någonsin hört just detta stycke, men jämföra små, nästan ohörbara detaljer med samma detaljer som de har hört tidigare. Egentligen är två musikstycken samma sak, det är bara lite noter på några linjer och lite konstiga tecken framför och ibland bakom noterna, ibland tar linjerna slut och man ritar till några extra linjer om man råkar hamna för långt ifrån det området som är friskt och sunt (borde man inte ha haft de linjerna där från början? Vad var det för kommitté som designade notskrift?). Hur kan ett musikstycke som inte är annat än lite toner och sånt tjafs avslöja nånting om vem det är som är dirigent eller vem det är som framför det? Djävulen ligger som vanligt i detaljerna. Genom att vrida och vända lite på smådetaljer kan man få ett gäng noter att låta helt olika. Det är det som är stil och samma sak gäller både när man pratar, skriver, programmerar eller målar bilder.

Häromdagen lyssnade jag på en föreläsning om ett ämne som fascinerar mig och som jag har studerat själv och ville veta mer om. Föreläsaren var väldigt kunnig och visste precis vad han pratade om och berättade saker som jag tidigare inte visste och ville veta mer om. Tyvärr var han också dödstråkig. Det var nånting med hans tonläge och hela framställning som skrek: Lyssna inte på mig, det här är tråkigt, ignorera mig, sov istället.. Jag var tvungen att konsumera större mängder kaffe i pausen för att inte somna under andra timmen. Samma dag var jag på en föreläsning i ett ämne som jag redan kan, som jag lika gärna själv kunde föreläsa om, där det inte sades ett enda ord som jag redan inte kunde eller visste bättre. Men föreläsningen var ändå spännande och kul för att föreläsaren kunde framställa det på ett bra sätt. Jag vill inte spekulera om vad den praktiska skillnaden mellan föreläsningarna var, men min upplevelse av dem var tvärtom mot vad den borde ha varit om vi bara tittade på innehållet i dem.

Kod är precis som musikstyckena eller föreläsningarna ovan. Stilen, idiomen och algoritmerna förmedlar långt mycket mer information än vad kompilatorn får ur den. En erfaren kodkonnesör kan känna igen enskilda programmerare, se om det var grupper eller enskilda som skrev ett visst stycke, kan se var reparationer gjordes, ana till sig den historiska utvecklingen, kan se om författarna var erfarna eller nybörjare, ibland se att en författare hade mer erfarenhet av andra språk än det som användes, vilka miljöer författarna kommer från, etc. På samma sätt kan en implementation av bubblesort vara rolig och spännande att läsa, medan man behöver träla sig igenom en malloc med tidskomplexiteten O(1). Visst kan det vara roligt med lite variation, men skillnaden mellan ett musikstycke och en bit kod är att musikstycket oftast är till för att underhålla, medan koden är till för att lösa ett verkligt problem. Underhållningskod får se ut hur som helst och den dagen ett musikstycke styr min hjärt- och lungmaskin kommer jag att kräva nanoprecision av dirigenten och kadaverdisciplin av orkestern. Tills dess kan vi hålla oss till verkligheten och propagera för kvalitetskod och kreativ musik, inte tvärtom.

TES: Kod är till för att läsas

Ett vanligt missförstånd är att man programmerar för att få ett stycke kod genom en kompilator och få den att börja exekvera. Det är givetvis trevligt när ens kod bygger och går att använda, men det borde inte vara det primära målet. För att förstå vad det primära målet med kod är räcker det att studera sig själv (om man är programmerare) eller andra programmerare för att inse vad man spenderar mest tid på. Den hårda insikten blir snabbt att man faktiskt spenderar mest tid på att läsa koden. För att göra saken värre, visar det sig att man oftast spenderar mer tid på att läsa andras kod snarare än sin egen (egen kod vet man ändå hur det är tänkt att den ska fungera). Skrivning utgör kanske bara någon procent av tiden man lägger ner på att få ett program klart och körbart. Majoriteten av tiden lägger man på att leta efter buggar, försöka hitta det stället där nånting magiskt händer, försöka förstå hur ett API egentligen ska användas, förstå randfall och andra konstigheter i koden och så vidare. När man till slut har plöjt igenom ett par romaner kan man sätta sig och klottra ner sin baksidestext.

Ofta går man tillbaka till ett kodstycke efter många år. Ett standardskämt bland programmerare brukar vara: 'Jag tittade på den där koden och utropade förskräckt: "Vad var det för idiot som skrev det där!", innan jag insåg att det var jag som skrev det för ett år sen/för en månad sen/för en vecka sen/igår.'. Alla erfarna programmerare skrattar hjärtligt åt såna skämt, men inte för att det ligger någon paradox i dem eller för att det är särskilt roligt. Man skrattar för att man känner igen sig och är lättad för att "det var inte jag den här gången". I många fall är det en mardröm att försöka förstå kod som man skrev bara några veckor tidigare. Helt vansinnniga konstruktioner blandas med idiotiska algoritmer, med fullständigt sinnesjuk indentering och cirkusapor som har flyttat runt alla parenteser. En positiv tolkning av det hela kan vara att det visar att man ständigt lär sig nya saker och förbättrar sin stil och sina kunskaper. Men hur kan man veta att den förändring man utsätts för är en förbättring? Skulle det inte kunna vara så att det är den negativa tolkningen som gäller? Att man bara fumlar slumpmässigt och springer runt i cirklar utan att egentligen veta vad man håller på med? Att det enda som gör den gamla koden konstig är att den inte ser ut som min kod gör ut idag, men inte nödvändigtvis bättre eller sämre.

Kan det vara så att det enda som gör gammal kod konstig och orsakar utrop av förskräckelse är att den inte ser ut som idag? Om man visar ett visst program X (namnet borttaget för att skydda de skyldiga) för något halvt erfarna C-programmerare blir de förskräckta. Ibland hamnar man i diskussioner om vilka kodhemskheter man har upplevt och då åker exemplen fram. "A är ett träsk av spaghettikod och konstiga namn." "B har ett configure-script som tog över två veckor att köra på en Sun 3/80" "C består till 80% av olika ifdef", osv. man slänger hela tiden fram exempel som bara blir värre och värre än det förra, tills någon plötsligt slänger in konversationsdödaren: "Har någon tittat på X?". Då blir det plötsligt tyst och många kloka huvuden nickar och säger: "Jo, X är nog värst.". Det intressanta är att om man indenterar om X så är koden inte så värst hemsk. Den är inte speciellt krånglig, den innehåller inte så många galenskaper, inga bitar i den är fyllda med svart magi som bara författaren kan begripa. Det enda som egentligen är hemskt med X är att koden är indenterad baklänges. Den gör precis tvärtemot hur alla andra gör. Semantiskt är den som vilken annan kod som helst, utseendet gör dock att den som försöker läsa den i orginalversion helt enkelt inte kan vrida på sin hjärna och istället för att läsa och tolka koden ett tecken i taget ger upp och förkastar den som hemsk. Den går helt enkelt inte att läsa.

samma problem kan uppstå i det vanliga språket när någon får för sig att slå på kreativiteten och förgylla vår vardag med ett stycke text som helt saknar interpunktion och versaler ElleR fÖr AtT göRA dEt HelA ÄnNu MEr SpÄnNaNdE LeKeR MED VeRSAlEr en sån text blir jobbig att läsa innehållet kan egentligen vara densamma som en helt korrekt text men den är inte som alla andra det blir konstigt jobbigt oläsligt och man avvisar snabbt texten som oseriös jag har själv varit i situationer då jag har avslagit jobbansökningar med ganska imponerade meritförteckningar eftersom ansökningen var för jobbig att läsa eftersom författaren var alldeles för kreativ eller ibland inte tillräckligt kreativ

oFTA SÄGER MAN ATT DET RÄCKER MED ATT VARA KONSISTENT I SIN STIL FÖR ATT KODEN SKA BLI LÄSBAR, dET ÄR EN HALVSANNING, vISST KAN VI KOMMA ÖVERENS OM ATT DET ÄR EN GEMEN I BÖRJAN AV MENINGEN. EN PUNKT SKILJER BISATSER. ETT KOMMA AVSLUTAR MENINGEN OCH SÅ VIDARE, mEN DET BILR FÖRSKRÄCKLIGT JOBBIGT ATT LÄSA ÄNDÅ, mAN KAN VÄNJA SIG EFTER NÅGRA SIDOR. ELLER KANSKE NÅGRA MENINGAR. MEN DET UPPKOMMER ETT ANNAT PROBLEM ÄVEN OM MAN VÄNJER SIG SNABBT, dET FINNS ÄVEN ANDRA TEXTER MAN BEHÖVER LÄSA, vI KAN INTE LÄRA OSS ATT LÄSA PÅ NYTT FÖR VARJE NY TEXT VI LÄSER. VI MÅSTE KUNNA ANVÄNDA SAMMA LÄSFÖRMÅGA FÖR FLERA OLIKA TEXTER, iNOM PROGRAMMERINGEN ÄR DET VÄLDIGT VANLIGT ATT PÅ SAMMA GÅNG LÄSA OCH HOPPA MELLAN KODSTYCKEN FRÅN FLERA OLIKA KÄLLOR SAMTIDIGT, tESTA ATT HOPPA MELLAN EN TEXT SKRIVEN PÅ VIT BAKGRUND )ETT PAPPER( OCH EN TEXT MED SVART BAKGRUND )STÄLL OM SKÄRMEN(. DET BLIR VÄLDIGT ANSTRÄNGANDE FÖR ÖGONEN, lIKA ANSTRÄNGANDE BLIR DET FÖR HJÄRNAN ATT BYTA KODSTIL HELA TIDEN,

Ibland går det trender i snabbläsning. Det säljs böcker och kurser som lär människor att läsa snabbt. Det är ganska lätt att lära sig tricket att läsa en textrad med två ögonkast. Vissa läser hela rader på en gång, riktiga experter kan läsa en stycke i taget och vissa kan ta in en hel sida med ett ögonkast. Det intressantaste med snabbläsning är att om man kontrollerar läsförståelsen visar det sig att de som läser snabbast tar också in texten bäst. De lär sig helt enkelt att gå direkt från text till kunskap utan att passera språk/grammatik/interpunktionsfilter. Jag anser att samma sak gäller kod. Man förstår koden bäst om man kan läsa den snabbt och inte behöver fördjupa sig i konstigheter och detaljer. Om ett stycke kod ser ut som "alla andra" slipper man fundera på randvillkor, specialfall, osv. Man matchar med en blick och går vidare tills man kommer fram till saker som inte går att greppa med en blick, det är då man antingen hittar de instressanta bitarna i koden som faktiskt gör nånting (majoriteten av all kod är bara skelett, byggställning, isoleringsfluff och fasad, de bitarna som faktiskt gör nånting är få och spridda), eller springer på buggar. En erfaren programmerare som läser kod som han är van vid fångar ofta upp buggar genom att se ett stycke kod och mumla "det här ser skumt ut" innan han egentligen har begripit vad koden gör.

Kodkulturer
Det finns inga enhetliga regler för hur kod skrivs även om vi begränsar oss till ett enda språk. Vissa nypåfunna språk som Java och Python har mer eller mindre homogena stilregler, Java för att nästan all exempelkod följer Suns riktlinjer och programmerare har ännu inte lyckats bli kreativa och Python eftersom språket kräver en viss sorts indentering av programmeraren, men även inom dessa kulturer förekommer det rebeller som antingen bryter mot kutym (inom Java) eller försöker vara så kreativa som möjligt inom de mer eller mindre strikta reglerna (Python).

Frågan man kan ställa sig är: "Varför?". Det måste väl ändå vara ganska svårt att gå över från en kodstil till en annan, man måste vänja sig vid att läsa saker på ett annat sätt, man måste ställa om tempot, man måste blanda sin kod med annan kod som ser helt annorlunda ut. Ofta är det för att vara annorlunda. Man vet att man inte kan bidra med nånting nytt, man vet att man inte är tillräckligt kreativ för att implementera nånting intressant, man vet att man inte är tillräckligt smart för att uppfinna några nya trick och algoritmer, men man kan alltid ändra på ytan. I vissa andra fall är det en markering om att man vill vara annorlunda än alla andra. I de fallen kan man tala om en viss sorts kultur. Se bara på större företag. Varje gång ledningen byts ut blir det en omorganisation. Inte för att det egentligen spelar någon roll, utan för att en omorganisation visar att man är där och Gör Nånting.

Om man tittar på operativsystemskärnor (specifikt på Unix-liknande operativsystem), upptäcker man att de liknar varandra. Det är inte bara för att många av dem delar rötter eller kod, ofta är det så för att en utvecklare som är upplärd i en miljö och går över till en annan miljö tar med sig stilen, man kan ju inte lära en gammal hund att sitta. Efter ett tag har korspollineringen mellan systemen blivit så extensiv att stilarna är mer eller mindre konsistenta mellan projekt som i princip inte har nånting med varandra att göra. Stilen i Linux-kärnan är i princip densamma som den officiella (och väldokumenterade) stilen för BSD-kärnor, som är i princip samma stil som användes för orginal-Unix. Men det är inte bara stilen som är lik, många programmeringstrick är mycket lika varandra utan att vara dokumenterade eller tvingande nånstans. Ofta beror det på hur själva miljön beter sig. En operativsystemskärna är för det första en enorm klump kod, alltså vore det dåligt om allting hette samma sak, väldigt många structar har fält som har prefix med en eller ett par bokstäver. Jag har aldrig sett något stildokument som rekommenderar det, men de flesta gör så ändå eftersom de vet konsekvenserna av att inte göra det. Om man tittar på länkade listor i kärnor brukar de oftast implementeras av in-line fält i structar och inte externa cons-celler. Det är inte heller dokumenterat nånstans, men kostnaden och restriktionerna för malloc i en kärna gör det mycket bekvämare att göra på ett sätt och inte ett annat. Om man ser på felhantering så är det väldigt mycket goto som används, mest för att man ofta behöver låsa och frigöra resurser och vara noga med felhantering, då är det bekvämast att samla all uppstädning på slutet av en funktion och hoppa dit om saker gick snett, istället för att upprepa mer eller mindre samma kod om och om igen för varje fall då nånting kan gå snett.

Om man tittar på kod för stora program med grafiska användargränssnitt är den också konsistent på något konstigt sätt. Gnome ser ut som KDE som ser lite ut som OpenOffice som ser lite ut som Mozilla som liknar exempelkod från Microsoft. Om man jämför Mozilla med en operativsystemskärna är det inte bara som att läsa litteratur på ett annat språk, men även från en annan plantet, från ett annat universum och på fel våglängd. En kärnahacker känner sig handikappad i användargränssnittskod. Inte bara för att koncepten är okända men också för att det finns en fantastisk mängd konstig UpperCaseOnani som sticker i ögonen, fullständigt kaos av funktioner, överlagringar, minnesallokeringar och annat smått och gott som inte går att begripa. När en användargränssnittsprogrammerare tittar på kärnakod så blir han på samma sätt förvirrad av att allting ser likadant ut, allting heter samma sak och koden är platt och består av enorma funktioner som gör allt.

Om man tittar på all kod som sysslar med 3D-grafik ser man att det handlar om artister som saknar all disciplin, vett och sans och man förstår direkt varför alla moderna spel är så buggiga, men det kanske är förtal så jag låter bli att utveckla den tanken för mycket.

Det kan finnas många förklaringar på hur dessa kulturer uppstår. När det gäller operativsystemskärnor beror det nog mycket på att alla följer K&R och det är en ganska begränsad skara elitister och alla som är annorlunda bli helt enkelt utfrysta. Användargränsnittskulturen kommer nog från Microsoft och deras stil och alla gymnasiekurser som bygger på Microsoftgränssnitt. Och så vidare.

Standardkonstruktioner - idiom
	int foo[4711];
	for (i = 0; i < sizeof(foo) / sizeof(foo[0]); i++)
		blahonga(foo[i]);
     
   typedef signed int Integer;
   typedef Integer *Index;
   typedef Integer MyArray[4711];
   #define MyArray2Reference(i) (&i[0])
   MyArray foo;
   Index foop = MyArray2Reference(foo);
   do
    blahonga(foop++);
   while (foop < &foo[4711]);
     

Dessa två kodbitar gör samma sak (tror jag). Den första begriper jag direkt. En person som skulle ge mig den andra kodbiten (ja, jag har sett liknande konstruktioner i verkligheten med typedef-onanin och macrona gömda bakom fem lager includefiler) skulle antingen åka på omskolningsläger eller få sparken om han envisades att fortsätta göra så. Den första ser man direkt hur många gånger loopen kommer att snurra, vilket element som kommer att vara det sista, även om inte inte känner till storleken på "foo" vet man att loopen inte kommer att gå utanför arrayen, etc. Det går inte att se i det andra exemplet. Man kan tänka sig en programmeringskultur som skulle kunna begripa det andra exemplet direkt och tycka att for-loopen är konstig, men det är inte min kultur.

Vi läser inte kod genom att läsa varje bokstav i taget och applicera en strikt parser på den. Vi läser kod genom att göra övergripande pattern-matching mot tidigare kodstycken som vi har läst. Därför är det trivialt att hitta buggen i det första kodbiten i följande exempel, medan det andra är mycket svårare.
	int foo[4711];
	for (i = 0; i <= sizeof(foo) / sizeof(foo[0]); i++)
		foo[i]++;
     
			int foo[4711], *foop;
		foop = &foo[4711];
	again:
(*--foop)++;
	if (foop != foo) goto again;
     

Jag skulle kunna erbjuda pengar till den som kan läsa den andra kodsnutten och på mindre än 30 sekunder säga hur den kommer att göra fel. Jag tror att jag skulle kunna få behålla mina pengar ändå.

En erfaren programmerare läser en for-loop med en blick och vet exakt vad den gör, vad randfallen är och hur den kommer att bete sig. Jag kan tugga mig igenom 20MB diff på någon timme (gjorde det häromdagen) och hitta fel i den så länge den är i en stil som jag är van vid.