programmering.xml

Denna kod är public domain. Om ni hittar fel eller vill ändra något i koden blir jag jätteglad om ni skickar dessa ändringar till jesper [at] fantasi [punkt] se.


<?xml version="1.0"?>
<article category="software" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="article.xsd">
  <keyword>Programmering</keyword>
  <keyword hidden="true">programmera</keyword>
  <topic subtitle="tankar kring och erfarenheter av programmering">Programmera på 'rätt' sätt</topic>
  <ingress>
    En återkommande fråga från studenter som ska
    lära sig programmera är "Hur ska man tänka när
    man programmerar, var ska man börja?"  Frågan är
    inte lätt att besvara, det finns ingen patentlösning som
    beskriver det korrekta tankesättet. Var och en måste
    hitta sitt eget sätt att tänka på och
    bemästra programmeringsspråket.  Det finns dock ett
    antal saker som kan underlätta på vägen mot
    fullkomlig kontroll av ett språk (som om man någonsin
    skulle få det).  Tänk igenom och försök
    följa dessa 'regler'. Det kommer att löna sig, det lovar
    jag!
  </ingress>
  <text>
    Denna sida innehåller många programmeringsrelaterade
    termer som man efter ett tags programmerande tar för givet
    att alla vet vad de betyder.  För att minska
    förvirringen har jag därför sammanställt en
    ordlista med de vanligaste orden. <a
    href="article.php">Ordlistan hittar du här!</a>
  </text>

  <subtopic>Namnge variabler med förklarande namn</subtopic>
  <text>
    Variabelnamn som <code>pt</code>, <code>n</code> och
    <code>f</code> är sällan motiverade och gör att
    koden blir svårläst. Svårläst kod ger
    lätt upphov till missförstånd och onödiga
    fel. Det är därför viktigt att man namnger sina
    variabler och funktioner med beskrivande namn. Genomtänkt
    namngivning ger lättläst kod vilket i sin tur gör
    att man kan plocka ut en del av koden och direkt se vad den
    gör, utan att behöva läsa igenom hela
    programmet. Detta är precis vad man eftersträvar när man
    programmerar eftersom det underlättar när man vid ett
    senare tillfälle återkommer till koden och ska
    försöka förstå den igen. Det finns i
    allmänhet <em>ingen</em> fördel med korta namn. Att det
    skulle krävas mer minne med långa namn är rent
    trams. Alla namn översätts av kompilatorn till rena
    adresser och de har samma storlek oavsett vad namnet du skrev i
    koden var.
  </text>
  <text>
    Värt att tänka på i detta sammanhang är
    även hur man skriver namnen på sina variabler och
    funktioner. Många programmeringsspråk har sina egna
    regler för hur det ska se ut (konventioner brukar det
    kallas).  Ibland kräver språket att en variabel alltid
    börjar med stor bokstav (Erlang är ett sådant
    språk) i en del andra språk får man göra
    som man vill (till exempel C och Java). Att göra "som man vill"
    är sällan en bra idé i detta sammanhang. För
    nästan alla programmeringsspråk så finns det
    konventioner som visar hur det bör vara. Det är alltid
    en god början att ta reda på vad konventionen
    säger om det språk man vill programmera i.
  </text>

  <subtopic>Använd inte globala variabler</subtopic>
  <text>
    Variabler som lever i hela koden och kan ändras av flera
    funktioner är mycket svåra att hålla koll
    på. När man använder en variabel i en funktion
    förväntar man sig oftast att den ska innehålla
    samma värde efter ett funktionsanrop som den gjorde
    innan. Problemet med globala variabler är att detta inte
    nödvändigtvis är sant. Funktionen man anropar kan ändra i
    den globala variabeln, och dess värde är kanske inte
    längre det man förväntar sig.
  </text>
  <text>
    Även här är läsbarhet en
    nyckelfråga. Det är mycket förvirrande om det
    plötsligt dyker upp en variabel som inte är deklarerad i
    den funktion man läser. Hur ska man veta vad den har för
    typ och värde och var i resten av koden den används?
  </text>

  <subtopic>Använd namngivna konstanter</subtopic>
  <text>
    Av ren självbevarelsedrift bör man tänka på
    att alltid använda namngivna konstanter där det är
    möjligt. Om man har konstanta värden som man
    använder i sitt program tjänar man både tid och
    kraft på att deklarera dessa med namn i stället
    för att skriva det numeriska värdet överallt i
    koden. Det blir annars ett förfärligt jobb att gå
    igenom programmet och leta efter alla ställen där man
    använt detta värde när man senare vill ändra
    på det. En mycket vanlig orsak till fel är att man inte
    hittat alla ställen och ett gammalt värde ligger kvar
    och stör.  Deklarerar man värdet som en konstant och
    använder den i koden kan fel av det slaget aldrig
    inträffa. Det handlar förstås även om
    läsbarhet. En namngiven konstant är lättare att se
    vad den betyder än en magisk siffra i koden.
  </text>
  <text>
    Vid flera tillfällen i ett program kan det hända att man
    vill använda en konstant med en smärre
    förändring. Till exempel kanske man har en konstant
    <code>ANTAL</code> och vill komma åt värdet
    <code>ANTAL<nbsp/>-<nbsp/>1</code>. Skriv då
    <code>ANTAL<nbsp/>-<nbsp/>1</code> i koden! Det ger betydligt
    högre läsbarhet än att skriva 17, bara för att
    man råkar veta att <code>ANTAL</code> är
    18. Subtraktionen kommer att utföras av kompilatorn och
    resultatet i den körbara filen blir exakt det samma. I detta
    exempel har jag skrivit konstanten med STORA bokstäver. Det
    är vanligt i många språk att man följer den
    regeln, men som tidigare nämnts ska man alltid kolla upp
    exakt vad som gäller i kodkonventionen för just det
    språk man arbetar med.
  </text>

  <subtopic>Använd aldrig GOTO</subtopic>
  <text>
    I en del programmeringsspråk finns kommandon som
    ovillkorligen hoppar till ett annat ställe i koden. Detta
    görs utan att ta ansvar för vad som händer runt
    omkring. Man ser den ofta med namn som <code>goto</code> eller
    <code>longjmp</code>. Hopp av detta slag bryter det normala
    kontrollflödet och gör att det blir mycket svårt
    att följa koden, det blir så kallad spagettikod. Det
    finns de som hävdar att man i vissa fall vill använda
    goto av effektivitetsskäl i vissa systemnära,
    tidskritiska delar av ett program. Ingen av dessa personer har
    dock lyckats visa upp ett exempel där man inte kunnat
    lösa det utan goto med samma slutresultat. Man får inte
    glömma att kompilatorn har en hel del hyss för sig
    när den omarbetar koden till ett körbart program.
  </text>

  <subtopic>Använd små, effektiva gränssnitt</subtopic>
  <text>
    Ett program kan ha flera olika nivåer av
    abstraktion. På en låg nivå kommunicerar
    funktioner och metoder med varandra genom sina argument och
    på högre nivåer är det moduler och klasser
    som kommunicerar genom att skicka meddelanden till varandra eller
    anropa varandras metoder. Gränsen mellan olika delar i ett
    program kallas för gränssnitt (interface på
    engelska). För att förenkla användningen och
    förståelsen av ett gränssnitt bör man
    hålla det så rent som möjligt. Låt inte
    klasser ha publika metoder som inte har en specifik (publik)
    uppgift. Låt inte funktioner ta argument som går att
    härleda i funktionen.
  </text>
  <text>
    Man bör se till att vid funktionsanrop skicka med så
    få argument som möjligt. I de flesta fall kan variabler
    deklareras som temporärer i funktionen. Onödiga argument
    ger ett svåranvänt gränssnitt till funktionen och
    kan ge upphov till onödiga fel. När det gäller
    argument är det även en fråga om prestanda. Det
    kostar att skicka argument eftersom dessa måste kopieras
    till stacken, och även om jag i denna text vill framhäva
    kompilatorns förmåga att optimera bort det mesta
    så är det tyvärr mycket svårt för en
    kompilator att optimera bort onödiga argument.
  </text>
  <text>
    Känner man behov av att skicka med väldigt många variabler
    till en funktion kanske det är dags att tänka om.
    Behövs verkligen alla argument? Många argument till en
    funktion är ofta ett tecken på att man
    försöker göra för mycket i samma funktion.
    Går det att dela upp arbetet i mindre delar och lägga
    delarna i separata funktioner? Om man verkligen behöver alla
    argumenten kan man oftast slå ihop flera av dem i en
    struktur eller ett objekt av något slag. De flesta
    programmeringsspråk har någon konstruktion för
    att sätta samman olika värden till en större enhet
    (kallas ofta struct, class, record eller liknande). Använd
    dessa!
  </text>

  <subtopic>Bygg ditt program av små, återanvändbara delar</subtopic>
  <text>
    Ett steg för att undvika massiva indataklumpar till
    funktioner är att bygga upp sitt program av små delar
    som utför en sak var. Dessa byggstenar skrivs lämpligen
    på ett såpass generellt sätt att de går att
    återanvända på flera ställen i programmet
    och kanske även i andra program - det är onödigt
    att uppfinna hjulet flera gånger. Än en gång
    får vi ökad läsbarhet. Denna gång tack vare
    funktionsanrop. Det är mycket lättare att få
    överblick över flera små funktioner än en
    jättestor. De flesta kompilatorer kommer automatiskt att
    lägga in små funktioner direkt på plats i det
    slutliga programmet (<em>inlining</em>) så inte heller detta
    medför någon prestandaförlust. Tvärt om kan
    det ge ökad prestanda då kodstorleken normalt blir
    mindre om man låter bli att duplicera kod.
  </text>
  <text>
    En lärare sa en gång till mig att ingen funktion fick
    vara större än en skärmsida och ingen
    källkodsfil fick vara längre än 100 rader (det var
    på den tiden då en skärmsida var ca 25 rader á 80 tecken).
    Detta är kanske lite extremt, men principen är
    rätt. Tanken är att man ska kunna se hela funktionen på
    en gång utan att behöva flytta texten.  Idag får man plats med mer på
    skärmen och gränserna för hur långa och hur många rader man kan ha
    är lite mer flexibla. I lite större projekt där man ska supporta olika
    plattformar och kanske har källkod distribuerad på olika servrar kan man
    än idag ha glädje av att följa den gamla 80-teckenregeln då man ibland
    tvingas sitta via en ssh-terminal och editera källkoden i emacs eller vi.
    Man ska inte förlita sig på att man alltid har tillgång till de hjälpmedel
    och editorer man använder till vardags och när en funktion börjar
    växa till ett hundratal rader eller en källkodsfil
    närmar sig 600-700 rader är det dags att ställa sig
    frågan: Kan man dela upp detta i mindre delar? Så gott
    som alltid så är det möjligt. En genomtänkt design
    minskar antalet fel i koden.
  </text>

  <subtopic>Representationsoberoende programmering</subtopic>
  <text>
    Funktioner som hanterar data och objekt ska inte bry sig om hur
    data är lagrat i datorns minne. Man ska aldrig utnyttja att
    man vet hur något representeras i ett objekt eftersom
    representationen kan förändras när som helst.
    Låt mig ge ett exempel.
  </text>
  <text>
    Tänk dig att du skriver ett program där du hanterar
    personer. Du bygger upp en struktur med ett antal textfält
    för namn, personnummer med mera. Din representation av
    personnumret är en sträng <code>"######-####"</code>.
    Runt om i ditt program använder du sedan denna struktur
    när du vill ta reda på fakta om en person. Du utnyttjar
    det faktum att personnummret är en sträng och att du
    till exempel vet att det sjunde tecknet är ett bindestreck.
  </text>
  <text>
    Efter fem månaders kodande inser du att du istället
    vill ha en numerisk representation av personnummret som blir lite
    lättare att sortera och snabbare att jämföra med
    vid sökning. Du måste då ändra
    representation i strukturen.  På hur många
    ställen i koden måste du nu ändra för att
    anpassa ditt program till denna nya representation? 
    Förmodligen allt för många!
  </text>
  <text><em>Hur ska man göra då?</em></text>
  <text>
    I stället för att utnyttja att man vet att värdet
    lagras som en sträng skapar man en egen modul för
    datatypen. I ett objektorienterat språk skulle man skapa en klass
    för personnummer, i procedurella språk skapar men en ADT (Abstrakt
    Datatyp). Principen är exakt den samma. Klassen / ADT:n
    innehåller privat data, vars representation vi inte visar
    utåt, och ett antal selektorer. Selektorer är
    funktioner som returnerar det värde man vill ha på den
    formen man vill ha.  Endast selektorerna får använda
    datastrukturen direkt, resten av programmet måste anropa
    någon av dessa selektorer. På så vis, när
    du ändrar representation kommer det endast att beröra
    dessa få och små funktioner, vilka blir mycket enkla
    att ändra eftersom de inte gör något annat än
    att returnera värdet.  Koden i det övriga programmet kan
    fortsätta att använda personnummret som en sträng medan man
    internt kan börja betrakta det som ett tal. Vips så har du
    gjort om flera timmars tråkigt arbete till några
    minuters rent nöje!
  </text>
  <text>
    Detta är (lite förenklat) vad som brukar kallas för
    representationsoberoende programmering. Är man orolig för
    prestandan så går det utmärkt att skriva selektorerna
    som makron.  Normalt är dessa dock så små att de kommer att
    inline:as av kompilatorn ändå så det finns ingen anledning att
    undvika det extra abstraktionslagret.  Den välkända
    år 2000-buggen skulle kunna ha lösts på en
    halvdag om man följt dessa regler från början.
  </text>

  <subtopic>Välindenterad och luftig kod</subtopic>
  <text>
    Som du säkert redan märkt handlar det nästan alltid om att göra
    koden mer lättläst. Alla typer av förbättringar som gjorts inom
    alla typer av språk handlar i botten om att göra koden mer
    lättläst. Det var därför man uppfann funktioner, datatyper,
    strukturer, klasser och högnivåspråk. Det är ingen funktionell
    skillnad mellan ett högnivåspråk som Java och den lägsta nivån,
    assembler. Det är bara lättare att överblicka koden.  Nästa
    steg för att göra koden läslig är rent
    estetisk.
  </text>
  <text>
    I nästan alla språk är det enbart för din egen skull som
    du trycker in en radbrytning lite då och då
    när du programmerar. Kompilatorn bryr sig inte om
    radbrytningar och blanksteg, den kan lika gärna läsa ett
    program som är skrivet på en enda jättelång
    rad. Men vi människor har lite större problem med att
    läsa en sådan text. Därför är det
    viktigt för läsbarheten av ett program att koden har en
    bra layout som bland annat markerar var olika stycken börjar
    och slutar.  (Det finns ett par riktigt märkliga språk
    där radbrytningar och indentering har semantisk betydelse,
    så där kan man inte bryta mot denna regel ens om man
    vill.)
  </text>
  <text>
    Ett viktigt steg i detta är indentering. Många
    programmeringseditorer indenterar koden automatiskt, har man tur
    passar denna automatiska indentering den personliga smaken.  Det
    är svårt att säga att en indenteringsmodell är bättre än en annan
    även om det kanske finns ett par stilar som de flesta är överens
    om att de är sämre än andra. Det är naturligtvis högst
    personligt vad man tycker ser bäst ut och därför kan det
    hända att mina tips på denna punkt inte helt
    stämmer överens med din egen smak. Det viktiga här är
    inte hurvida du väljer att följa mina riktlinjer eller ej, det
    viktiga är att du själv funderar på hur du vill ha det i koden du
    arbetar med och ser till att du strikt följer de regler som du
    själv sätter upp. Punkterna nedan är hur jag brukar göra.
  </text>

  <list>
     <item> Indenteringen är tre tecken. Är den mindre blir
      det svårt att se vad som är vad och är den
      större blir raderna otrevligt långa.</item>

      <item> Raderna är inte längre än 80-100 tecken
      för att man ska få en bra överblick.  Detta
      är inte bara en artefakt från gamla terminaler med 80
      tecken bred skärm, utan har även att göra med hur
      lätt det är för ögat att överblicka
      längre rader text.</item>

      <item> Måsvingar hamnar först på en egen rad efter
      funktionshuvud, if-satser m.m. Det är mycket lättare
      att se vilka som hör ihop och det ger luftigare kod.</item>

      <item> Blanksteg runt operatorer (+ - * / <lt />; <gt />; =
      o.s.v.) och efter komma i argumentlistor.</item>
  </list>

  <subtopic>Kommentera koden</subtopic>
  <text>
    Hur bra man än väljer sina variabel- och funktionsnamn,
    hur väl man än strukturerar sin kod och delar upp den i
    små fina delar så kommer det inte att räcka
    för att någon annan ska förstå hur man som
    programmerare har tänkt. Kommentarer är oumbärliga
    för den som en dag ska läsa igenom koden,
    förstå den och försöka bygga vidare på
    den. Att sätta sig in i ett program som någon annan har
    skrivit är svårt nog, om det dessutom inte finns
    något som talar om vad de olika delarna i koden gör kan
    jobbet bli så svårt att det går snabbare att
    skriva om det hela från början. Detta gäller
    även när man hanterar sin egen kod och det kan
    räcka med ett par veckor för att man ska hinna
    glömma vad man gjort och varför.
  </text>
  <text>
    Kommentarerna ska beskriva vad som händer i koden och vad
    olika funktioner gör. Det viktiga är att tala om
    <em>varför</em>. Alla kan se att <code>i++</code> ökar
    värdet på <code>i</code> med ett, en kommentar av typen
    "Öka värdet med ett" är fullständigt
    meningslös. Det kommentaren ska säga är varför
    värdet ökas.  Skriv kommentaren ur funktionens
    synvinkel. Det är oftast inte intressant att veta till
    exempel var argumenten kommer ifrån, det som är
    intressant är att veta vad de används till i den
    aktuella funktionen.
  </text>

  <subtopic>Exempel</subtopic>
  <text>
    Följande programexempel visar hur man kan skriva ett enkelt
    program på två olika sätt. Programmen är
    skrivna i Java men skulle se exakt lika ut i till exempel C
    eftersom programmet inte utnyttjar någon form av
    objektorientering.  För dig som vill se hur programmet skulle
    se ut om man skrev det så som ett objektorienterat program
    ska se ut finns en version av detta på <a
    href="source.php">Kodsidan</a>.
  </text>
  <text>
    Båda exemplen tar samma indata och ger samma resultat,
    skillnaden ligger i hur lätt det är att
    förstå och underhålla programmen.
  </text>

  <codebox title="Exempel 1">
    public class Cirkel{
     static public void main (String[] argv){
     double x=Double.valueOf(argv[0]).doubleValue();
     System.out.println("Omkrets: "+x*6.28318);
  System.out.println("Area: "+x*x*3.14159);
      }}
  </codebox>

  <codebox title="Exempel 2">
    public class Cirkel
    {
       static final double PI = 3.14159;

       /*
        * Beräkna cirkelns area givet en radie
        */
       static double cirkelArea(double radie)
       {
          return radie * radie * PI;
       }

       /*
        * Beräkna cirkelns omkrets givet en radie
        */
       static double cirkelOmkrets(double radie)
       {
          return 2 * radie * PI;
       }

       /*
        * Här startar programmet.
        * En radie skall skickas med som argument.
        */
       static public void main (String[] argv)
       {
          // Hämta in radien från argumentet
          double radie = Double.valueOf(argv[0]).doubleValue();

          System.out.println("Omkrets: " + cirkelOmkrets(radie));
          System.out.println("Area: " + cirkelArea(radie));
       }
    }
  </codebox>

  <text>
    Döm själv vilket exempel som är lättast att
    följa. Argument av typen "Men det är ju inte svårt
    att se vad som händer i det där programmet, det är
    ju bara sex rader kod!" köper jag inte. Riktiga program är
    inte sex rader långa.
  </text>
  <text>
    Vill man nu bygga ut programmet så att det även kan
    räkna ut mantelytan, volymen med mera av en kon och en
    cylinder (till vilket man använder både omkrets och
    area av bottencirkeln), blir det inget större jobb i exempel
    2, men i exempel 1 kan man lika gärna skriva ett nytt program
    - det blir inte mer arbete. En annan skillnad ligger i
    användandet av en konstant i exempel 2. Det underlättar
    betydligt om man till exempel vill öka noggrannheten på
    PI.
  </text>
  <text>
    Det finns ingen anledning att skriva för kompakt kod. En bra
    kompilator genererar samma körbara fil för båda
    exemplen, eller till och med en bättre för exempel 2
    då den kan förstå vad programmet egentligen
    gör.
  </text>
  <formulabox>
    Överlåt optimeringen åt kompilatorn,
    den är bättre på det, jag lovar!
  </formulabox>
</article>