Hoppa till huvudinnehållet
Institutionen för informationsteknologi

PKD 10/11: Minneslista för programutveckling

Följande punkter kan du använda som en minneslista när du skall utveckla program. Minneslistan ser kanske stor ut, men det beror på att en erfaren programmerare gör mycket av detta i huvudet utan att tänka speciellt på det, medan den som är ovan kan behöva det stöd som en detaljerad förteckning ger.

1 Program(krav-)specifikation

1.1 Se till att du vet och förstår vad programmet skall göra.
1.1.1 Lägg speciell vikt vid att du förstår vad som skall hända vid "otypiska fall" som tomma datamängder.
1.1.2 Ställ frågor till beställaren om det behövs.
1.1.3 Om det är något som du inte kan ta reda på, gör ett rimligt antagande och dokumentera det!
1.2 Skriv ned vad programmet skall göra om det inte redan är nedskrivet (se även punkt 7)

2 Programdesign (problemlösning)

2.1 Tänk igenom så att du själv förstår hur problemet skall lösas.
2.1.1 Har någon annan redan löst problemet? Om det t.ex. finns en inbyggd funktion i ML som gör det som behövs så behöver du inte göra något själv!
2.2 Konstruera datastrukturer.
2.2.1 Bestäm en lämplig representation för de data som programmet skall hantera - antingen med hjälp av inbyggda datatyper eller med hjälp av datatyper som du definierar själv.
2.2.1.1 I många fall är datastrukturerna för programmets in- och utdata (funktionsargument och -värde) redan bestämt i kravspecifikationen. I så fall konstruerar man bara ev. interna datastrukturer.
2.2.1.2 Om man skall konstruera en abstrakt datatyp så kan man nöja sig med att bara bestämma den abstrakta datatypens gränsyta (vilka de primitiva funktionerna skall vara och vad de skall göra). Att koda de primitiva funktionerna blir då ett delproblem (steg 2.3.2) och representationen behöver inte bestämmas förrän man löser detta delproblem.
2.2.2 Om du kan göra det rimligt enkelt och utan att krångla till programmet för mycket, försök undvika att samma information kan representeras på olika sätt. Ett sätt är att bestämma en datastrukturinvariant som utesluter alla representationer utom en.
2.2.3 Om datastrukturer kan ha instanser som inte representerar någon information alls (t.ex. naturliga tal som representeras av heltal eller en datastruktur för rationella tal där nämnaren kan vara 0), bestäm en datastrukturinvariant för att utesluta dessa fall.
2.2.4 Skriv en beskrivning av hur information representeras av dina datastrukturer och vilka datastrukturinvarianter som finns.
2.3 Konstruera algoritmer
2.3.1 Använd flödesschemor, dataflödesdiagram, pseudokod eller andra hjälpmedel för att konkretisera hur programmet skall fungera.
2.3.2 Dela upp problemet i delproblem som du löser separat (stepwise refinement). Gå igenom steg 2 igen för varje deluppgift.
2.3.2.1 Det är ok att göra övriga steg - såvitt det är möjligt - innan man börjar med delproblemen, men det finns alltid en risk att man senare märker att uppdelningen i delproblem var olämplig och måste göras om. Har man då gått för mycket i förväg kan man bli tvungen att kasta arbete som redan är utfört.
2.3.2.2 Begränsa inte i onödan specifikationen för ett delproblem. Om det är möjligt att med små medel låta programkoden för delproblemet lösa en mer generell uppgift som än som egentligen behövs, låt den göra det. Då kanske du kan återanvända koden i andra sammanhang.
2.4 Skriv funktionsspecifikationer.
2.4.1 Bestäm funktionernas namn och typ.
2.4.2 Bestäm namn på funktionsargumenten att användas i funktionsspecifikationen.
2.4.3 Bestäm för- och eftervillkor
2.4.3.1 Tänk på att dessa - speciellt förvillkoret - inte skall tala om hur funktionen skall användas utan hur den faktiskt fungerar. Rekursiva funktioner kan ha basfall som ligger utanför den användning av funktionen som du tänkt dig. Se till att förvillkoren godkänner dessa fall.
2.4.3.2 Kontrollera att alla argument nämns i eftervillkoren om det inte finns speciella skäl till att de inte gör det!
2.4.4 Ta med något typiskt anrop och resultat som körexempel.
2.5 Skriv testfall.
2.5.1 Exemplen från funktionsspecifikationerna skall ingå bland testfallen.
2.5.2 Ifall kravspecifikationen innehåller exempel så skall de ingå bland testfallen.
2.5.3 Testfallen skall täcka både typiska och otypiska användningar av programmet. Tänk speciellt på gränsfall!
2.5.4 Ifall kravspecifikationen ger möjlighet för programmet att ge alternativa svar så kan du försöka skriva testfallen så att de tar hänsyn till det, eller så skissar du bara resultatet och fyller i detaljerna i steg 5 när du vet exakt hur programmet arbetar.

3 Kodning (av varje funktion)

3.1 Använd flödesschemor, dataflödesdiagram eller andra hjälpmedel för att konkretisera hur funktionen skall fungera.
3.2 Behövs rekursion? I så fall
3.2.1 Bestäm hur funktionen kan beräknas med hjälp av värdet för "enklare" indata. Använd flödesschemor, rekursiva ekvationer eller kända mönster. Svansrekursion med ackumulatorvariabel eller vanlig rekursion? Välj argumentet (argumenten) att göra rekursion över.
3.2.2 Bestäm rekursionsvarianten, samt det värde den minskar mot, och dokumentera dessa.
3.2.3 Tänk alltid på möjligheten att använda alternativ till rekursion som funktionerna map, foldr etc.
3.3 Bestäm de fall av indata som behöver hanteras på olika sätt och skriv kod för dem. (Använd matchning och/eller if/case-uttryck för falluppdelningen.)
3.3.1 Vid rekursion, se till att det finns kod för basfallet (eller basfallen) och att den koden inte gör rekursiva anrop. Tänk på att rekursionen i en del problem kan avbrytas även innan varianten gått ned till sitt minimivärde.
3.3.2 Vid rekursion, när du skriver kod för det allmänna fallet (fallen), använd resultatet från rekursiva anrop (med enklare indata) för att beräkna funktionsvärdet.
3.3.2.1 Tänk på att "enklare indata" betyder ett lägre värde på varianten.
3.3.3 Om det behövs, skriv defensiv kod för de fall när argumenten inte uppfyller förvillkoret.
3.4 Om man behöver använda olika delar av en datastruktur som beräknas av en uttryck så kan man undvika att räkna om uttrycket flera gånger genom att i en lokal deklaration binda en variabel till uttryckets värde.

4 Kodgranskning

4.1 När du granskar koden får du (och måste) förutsätta att förvillkoren är uppfyllda och att ev. datastrukturinvarianter är uppfyllda för alla argument.
4.2 Läs igenom koden. Ser det vettigt ut? Förstår du själv det du har skrivit?
4.3 Kontrollera att det finns kod som hanterar alla olika värden som argumenten kan ha (och som är tänkbara enligt punkt 4.1).
4.3.1 Denna koll måste du även göra för case-uttryck och liknande som finns inne i funktionen du granskar.
4.4 Granska alla funktionsanrop och övertyga dig om att de anropade funktionernas förvillkor är uppfyllda (återigen tänk på 4.1).
4.5 Med kännedom om eftervillkoret hos alla funktionsanrop, övertyga dig om att programmet beräknar ett värde som uppfyller eftervillkoret hos den funktion du granskar.
4.5.1 Använd inte programkoden för de anropade funktionerna, utan bara deras eftervillkor från funktionsspecifikationen.
4.6 Kontrollera att rekursionsvarianten minskar i alla rekursiva anrop!
4.7 Kontrollera att alla datastrukturer som du konstruerar uppfyller sina invarianter (återigen tänk på 4.1)

5 Testning

5.1 Kontrollera att de testfall du skrivit tillsammans gör att all kod i programmet utförs. Om inte, skriv kompletterande testfall.
5.2 Skapa kod för automatisk testning. Om testfallen inte tar för lång tid att köra så kan du lägga testkoden i slutet av programfilen.
5.3 Lägg upp testningen så att du testar en funktion innan du testar de andra funktioner som använder den.
5.4 Se till att du använder aktuella versioner av funktioner när du testar. Ladda om programfiler vid behov. För att vara bergsäker, starta om ML.

6 Felsökning

6.1 Om du felsöker program med abstrakta datatyper så underlättar det ofta att (tillfälligt!) göra om de abstrakta datatyperna till vanliga datatyper.
6.2 Kontrollera först att testfallet är riktigt! Tänk på att kravspecifikationen ibland kan tillåta alternativa svar.
6.3 Granska den felande funktionen igen med tanke på just de indata som användes i det misslyckade testfallet.
6.4 Förvissa dig om att ev. anropade funktioner gör rätt!
6.5 Om inget annat fungerar, utför beräkningen i funktionen steg för steg för hand (eller interaktivt genom att mata in uttryck till ML) och se var ett felaktigt värde uppstår.

7 Dokumentation (görs parallellt med ovanstående aktiviteter)

7.1 Beskriv programmet ur användarsynpunkt
7.1.1 Vilken uppgift utför det?
7.1.1.1 Finns några begränsningar i hur det utför uppgiften?
7.1.1.2 Finns några kända fel eller brister?
7.1.2 Hur använder man det?
7.1.2.1 Hur skall indata se ut?
7.1.2.2 Hur skall utdata tolkas?
7.1.2.3 Hur är arbetsgången?
7.1.3 Hur installerar man det?
7.1.3.1 Har det några speciella krav på maskinen det körs på, annan programvara etc.
7.2 Beskriv programmet ur programmerarsynpunkt
7.2.1 Ge en översikt över hur programmet arbetar. Uppdelning i moduler (olika funktioner).
7.2.2 Beskriv alla datastrukturer
7.2.2.1 Beskriv hur information representeras av datastrukturerna
7.2.2.2 Beskriv datastrukturinvarianter
7.2.3 Beskriv algoritmerna som används
7.2.4 Beskriv varje funktion.
7.2.4.1 Informationen i funktionsspecifikationen skall ingå.
7.2.4.2 Förklara hur funktionen utför sin uppgift.
7.2.4.3 Speciellt svårförståelig kod bör förklaras i en kommentar på plats i programfilen.

Uppdaterad  2010-10-21 11:31:11 av Lars-Henrik Eriksson.