TDDD11
TDDD11 > Dålig kontra bra programkod
Göm menyn

TDDD11 Programmering, grundkurs

Dålig kontra bra programkod

När man skriver program finns det saker som är möjligt att göra och det finns saker som är bra att göra. De exempel som finns nedan visar på saker som man "kan" skriva i Ada, men som kanske inte är så bra och dessutom en variant som är vettig att skriva istället.

En viktig sak att förstå är att "bara för att man kan" skriva på ett sätt är det kanske inte vettigt att göra detta. Det finns fall där det "sämre" alternativet är vettigt att använda, men i denna kurs är vi inte ute efter specialfallen utan vi vill att ni skriver kod med i första hand läsbarhet och tydlighet som fokus.

När man skriver sitt program skall man tänka på att det är viktigt att kunna läsa sitt (eller andras) program smidigt. Läsbarhet är något som underskattas väldigt mycket (speciellt när man är ny på detta med programmering). En van programmerare vet att man sparar tid på att lägga den extra lilla tid det tar att göra det snyggt från början.

Här finns ett antal exempel som visar hur man "inte skall skriva" och hur man "skall skriva". Diskussioner om dessa varianter är det bra att prata med assistenten om.

OBS! I uppgift Ada.O1.1 skall man INTE använda någon "loop". Där får t.ex. "duplicering av kod" finnas.

Flera satser på samma rad

Att skriva flera satser på samma rad ställer till det för en programmerare som är van. Man läser nämligen generellt sett bara i vänsterkanten i koden för att hitta det man söker.

Hur man inte bör skriva Hur man bör skriva
Put("Mata in ett heltal: ");  Get(I);
Put("Mata in ett heltal: ");
Get(I);
Put("En text.");  New_Line;
Put("En text.");
New_Line;

-- Eller i detta specialfall (med
-- "String" som utskriftsdatatyp).

Put_Line("En text.");
X := 2;  Y := 5 * X;  Z := X * Y;
X := 2;
Y := 5 * X;
Z := X * Y;




if A = 1 then Put("Text");
end if;


while B < 3 loop Do_The_Thing;
end loop;


procedure Huvudprogram is
  C : Character;
begin Get(C);
end Huvudprogram;

-- Kontrollsatser, (under)program, mm. ska skrivas på 
-- "rätt" sätt, bla. genom att inte blanda deras delar 
-- med andra satser på samma rad 

if A = 1 then
  Put("Text");
end if;

while B < 3 loop
  Do_The_Thing;
end loop;

procedure Huvudprogram is
  C : Character;
begin
  Get(C);
end Huvudprogram;

Indentering (ger mycket mer läsbarhet om man gör det bra)

Att inte indentera (skjut in sin kod på raden) på ett strukturerat sätt gör att det blir svårt att läsa programmet. Man förlorar mycket tid på att själv inte se vad man skrivit så gör det hela tiden (går enkelt att fixa med "TAB" i Emacs om man har "Ada-mode" påslaget.

Hur man inte bör skriva Hur man bör skriva
-- Ingen indentering alls.

procedure Name is

A : Integer;
B : Float;

begin
Put("...");
Get(A);
if A > 10 then
Put("Större än");
else
Put("Mindre än eller lika med");
end if;
end Name;

-- Felaktig indentering.

procedure Name is

            A : Integer;
 B : Float;

        begin
Put("...");
   Get(A);
 if A > 10 then
Put("Större än");
    else
    Put("Mindre än eller lika med");
    end if;
        end Name;
-- TIPS: Följ det som ges som exempel i kursen.

-- Mycket mer läsbart. Följer den kodstandard
-- som är i Ada.

procedure Name is
    
  A : Integer;
  B : Float;

begin
  Put("...");
  Get(A);
  if A > 10 then
    Put("Större än");
  else
    Put("Mindre än eller lika med");
  end if;
end Name;
-- Svårlästa paketinkludering.

-- Exempel 1:
with PAKET1; with PAKET2;
use PAKET1; use PAKET2;
with PAKET3; use PAKET3;	    
-- Exempel 2:
with PAKET1, PAKET2;
use PAKET1, PAKET2;
with PAKET3; with PAKET3;	    
-- Lättare att läsa.
with PAKET1;		use PAKET1;
with PAKET2;		use PAKET2;
with PAKET3;		use PAKET3;

OBS! Lätt att plocka bort paket som inte används
och dessutom lätt att se att man gjort både "with"
och "use" på paketen. Läsbar kod gör att man
dessutom inte gör fel som i exempel 2 till vänster.

"Tät kod" eller "gles kod" och lite "stil" på programmet och "rätt ordning"

Hur gör jag min kod med tydlig med hjälp av lite extra "space"? Tänk inte MYCKET "space" utan lagom.

Hur man inte bör skriva Hur man bör skriva
x:=3.2*Sin(x)+7.5*Cos(X)/1.2*4.5;
x := 3.2 * Sin(x) + 7.5 * Cos(X) / 1.2 * 4.5;
Put("Mata in. ");
Get(I);	    
Put("Du skrev in: ");
Put(I);
Skip_Line;
New_Line;
Put("Mata in. ");
Get(F);	    
Put("Du skrev in: ");
Put(F);
New_Line;
Skip_Line;
-- Heltal
Put("Mata in. ");
Get(I);
Skip_Line;  -- OBS! Denna hör ihop med "Get".

Put("Du skrev in: ");
Put(I);
New_Line;

-- Flyttal
Put("Mata in. ");
Get(F);
Skip_Line;  -- OBS! Denna hör ihop med "Get".

Put("Du skrev in: ");
Put(F);
New_Line;
Put("Mata in. ");

Get(I);	    

Put("Du skrev in: ");

Put(I);

Skip_Line;

New_Line;



Put("Mata in. ");

Get(F);	    

Put("Du skrev in: ");

Put(F);

New_Line;

Skip_Line;
-- Heltal
Put("Mata in. ");
Get(I);
Skip_Line;  -- OBS! Denna hör ihop med "Get".

Put("Du skrev in: ");
Put(I);
New_Line;

-- Flyttal
Put("Mata in. ");
Get(F);
Skip_Line;  -- OBS! Denna hör ihop med "Get".

Put("Du skrev in: ");
Put(F);
New_Line;

Onaturligt formulerade "if"-satser

När det gäller "if"-satser ser man väldigt ofta konstruktioner som är "onaturliga". Detta beror antagligen på attt man vill vara säker på att man får rtt värde, men det visar på att man inte riktigt har fårstått hur villkoren i "if"-satsen fungerar.

Ett villkor (mellan t.ex. "if" och "then") skall ha ett resultat som är ett "booleskt värde" och om detta värde är True så kommer den grenen att utföras. Man behöver alltså inte testa om detta är lika med True eller False igen.

För läsbarhet är det dessutom viktigt att skriva "if"-satsen med rätt stil, exempelvis med "if", "villkor" och "then" på egen rad så som skrivet nedan. Se exemplen nedan för rätt stil.

Hur man inte bör skriva Hur man bör skriva
if VILLKOR = True then
if VILLKOR then
if VILLKOR = False then
if not VILLKOR then
if VILLKOR then
  ...;
elsif not VILLKOR then
  ...;
end if;
if VILLKOR then
  ...;
else
  ...;
end if;
if VILLKOR_1 then
  ...;
elsif not VILLKOR_1 and VILLKOR_2 then
  ...;
elsif not VILLKOR_1 and not VILLKOR_2 then
  ...;
end if;
if VILLKOR_1 then
  ...;
elsif VILLKOR_2 then
  ...;
else
  ...;
end if;
-- Om man vill testa flera saker som beror av
-- varandra.

-- HÄR ÄR DET IBLAND SVÅRT ATT HITTA SINA FEL
-- (om man t.ex. gör saker i en tidigare "if"
-- som påverkar resultatet av vilkoren senare!

if VILLKOR_1 then
  ...;
end if;

if not VILLKOR_1 and VILLKOR_2 then
  ...;
end if;

if not VILLKOR_1 and not VILLKOR_2 and VILLKOR_3 then
  ...;
end if;
if VILLKOR_1 then
  ...;
elsif VILLKOR_2 then
  ...;
elsif VILLKOR_3 then
  ...;
end if;
if A = B or C = D then
if (A = B) or (C = D) then
if (X > Y) or (X = Y) then
if X >= Y then
if VILLKOR then
  null;  -- Tom sats som inte gör något.
else
  ...;
end if;
if not VILLKOR then
  ...;
end if;
if VILLKOR then
  ...;
else
  null;  -- Tom sats som inte gör något.
end if;
if VILLKOR then
  ...;
end if;
if (A = 1) or (A = 2) or (A = 3) or (A = 4) then
if A in 1 .. 4 then
if (A < 1) and (A > 4) then
if A not in 1 .. 4 then
if Character'Pos(Ch) = 48 then
if Ch = '0' then

Vilken loop skall jag använda?

När det gäller val av upprepningssatser är det väldigt ofta så att man väljer en väg som gör att man kommer till program som är lite "knasigt" uppbyggt. Det finns ofta bättre sätt då. Vi tar några exempel där man "bygger" en annan variant av "loop"-sats utan att man kanske tänker på detta.

Innan vi tar dessa så tar vi några frågor som man kan ställa sig för att komma mer rätt på en gång i sina val av iterationssatser (upprepningssatser).

  1. Skall detta upprepas oändligt många gånger (eller tills strömmen bryts)?
        Detta ger direkt "loop".
  2. Vet du antal gånger du skall upprepa detta (eller kan räkna ut det)?
        Detta ger direkt "for".
  3. Vet du att du skall utföra detta minst en gång?
        Detta ger direkt "loop".
  4. Annars!
        Detta ger "while".

Hur man inte bör skriva Hur man bör skriva
-- Att bygga en "while" m.h.a. en "loop".

loop
  if VILLKOR then
    exit;
  end if;

  ...;
end loop;
while not VILLKOR then
  ...;
end loop;
-- Att bygga en "while" m.h.a. en "loop".

if VILLKOR_1 then
  loop
    ...;

    if not VILLKOR_1 then
      exit;
    end if;
  end loop;
end if;
while VILLKOR_1 then
  ...;
end loop;
-- Att bygga en "for" m.h.a. en "loop".

i := 1;
loop
  ...;
  i := i + 1;

  if 1 > 10 then
    exit;
  end if;
end loop;
for i in 1 .. 10 loop
  ...;
end loop;
-- Att bygga en "for" m.h.a. en "while".

i := 1;
while i <= 10 loop
  ...;
  i := i + 1;
end loop;
for i in 1 .. 10 loop
  ...;
end loop;
-- Varför ha samma kod på två ställen
-- (två "Put" och "Get") som gör samma
-- sak (markerat med "-- ***" nedan)?

-- Dessutom i detta fall varför ha
-- två Get för att läsa ETT tal?

Put("Mata in: ");    -- ***
Get(X);              -- ***

while X < 0 loop
  Error_Message;

  Put("Mata in: ");    -- ***
  Get(X);    -- ***
end loop;
-- Fel tänkt! Se även under fliken
-- "Duplicering av kod" nedan.

loop
  Put("Mata in: ");    -- ***
  Get(X);              -- ***

  if X >= 0 then
    exit;
  end if;

  Error_Message;
end loop;

-- Eller en variant utan "if"
-- (med "exit"-variant).

loop
  Put("Mata in: ");
  Get(X);

  exit when X >= 0;

  Error_Message;
end loop;

Felaktigt sätt att använda en "for"-sats

Det dyker ibland upp kod där man ser att någon/några av satserna inuti en "for"-sats modifierar antingen själva styrvariabeln eller något som påverkar någon av gränserna för det intervall som "for"-satsen skall agera över. Detta kan ställa till det mycket om man inte vet vad man gör. Det är dessutom så att detta behandlas olika i olika programspråk. Vi säger därför att detta inte är ok i vår kurs.

I nedanstående fall skall man alltså fundera på en annan lösning av problemet.

Hur man inte bör skriva
-- Att försöka ändra styrvariabeln.

for I in 1 .. 5 loop
  ...;

  I := 1;

  ...;
end loop;
-- Att försöka ändra minvärdet.

for I in A .. B loop
  ...;

  A := B + 1;

  ...;
end loop;
-- Att försöka ändra maxvärdet.

for I in A .. B loop
  ...;

  B := B + 1;

  ...;
end loop;

Onaturligt sätt att anropa operatorer

En operator är en "form av funktion" som anropas lite speciellt. Det "går" att anropa dessa på "samma sätt" som en funktion, MEN detta är INTE något man gör i det generella fallet. Det är väldigt specifika fall detta görs och det skall ni inte göra i denna kurs.

OBS! Tänk så här: "Bara för att det går behöver man inte göra det (om det är en sämre variant)".

Hur man inte bör skriva Hur man bör skriva
if ">"("Love", "Hate") then
if "Love" > "Hate" then
A := "+"(5, 3);
A := 5 + 3;

De lite speciella "unära" operatorerna som anger tecknet (+/-) för t.ex. heltal.

Hur man inte bör skriva Hur man bör skriva
A := "+"(5);
A := "-"(5);
A := +5;
A := -5;

"Onödigt" att mellanlagra data

Om man tittar på vad en funktion eller en operator gör så kan man se att det är onödigt att mellanlagra det resultat som kommer tillbaka om man inte skall använda detta flera gånger (i vissa fall är det bra att stoppa in datat i en variabel, men det finns också nackdelar med detta i vissa fall).

Hur man inte bör skriva Hur man bör skriva
-- Varför ha 4 variabler istället för 1?

A := Sin(X);
B := Sin(Y);
C := Cos(Z);
S := A + B + C;


S := Sin(X) + Sin(Y) + Cos(Z);
-- Varför ha mellanlagra funktionsvärdet?

A := Factorial(N);
Put(A);


Put(Factorial(N));
-- Även t.ex. "if" klarar av att hantera uttryck ...

A := Factorial(N);
if A = 6 then


if Factorial(N) = 6 then




Put(Integer(Sin(X) * 3.2 / Cos(Y)) mod 4);
-- Ibland vill man dock mellanlagra data för att kunna
-- få bra identifierare som beskriver vad det är man
-- beräknat.

Good_Name := Integer(Sin(X) * 3.2 / Cos(Y)) mod 4;
Put(Good_Name);

Duplicering av kod

Hur man inte bör skriva Hur man bör skriva
-- Varför ha samma kod på flera ställen
-- som gör samma sak (markerat med
-- "-- ***" nedan)?

Put("Mata in: ");    -- ***
Get(X);              -- ***

Put("Mata in: ");    -- ***
Get(X);              -- ***

Put("Mata in: ");    -- ***
Get(X);              -- ***
-- Man bygger om detta till en "for".

for I in 1 ..3 loop
  Put("Mata in: ");    -- ***
  Get(X);              -- ***
end loop;
-- Varför ha samma kod på två ställen
-- (två "Put" och "Get") som gör samma
-- sak (markerat med "-- ***" nedan)?

-- Dessutom i detta fall varför ha
-- två Get för att läsa ETT tal?

Put("Mata in: ");    -- ***
Get(X);              -- ***

while X < 0 loop
  Error_Message;

  Put("Mata in: ");    -- ***
  Get(X);              -- ***
end loop;
-- Man bygger om detta till en "loop".

loop
  Put("Mata in: ");    -- ***
  Get(X);              -- ***

  exit when X >= 0;

  Error_Message;
end loop;
-- Varför ha samma kod på två ställen
-- (två "Put" och "Get") som gör samma
-- sak (markerat med "-- ***" nedan)?

-- I detta fall inte enkelt att bygga om.
-- till en "loop" av något slag.

loop
  Put("Mata in: ");    -- ***
  Get(X);              -- ***

  ...;

  Put("Mata in: ");    -- ***
  Get(X);              -- ***

  ...;
end loop;
-- Gör ett underprogram som löser
-- denna del i koden och anropa
-- underprogrammet två gånger.

-- Antag att underprogrammet heter
-- "Skriv_Ut_Ledtext_Och_Mata_In".

loop
  Skriv_Ut_Ledtext_Och_Mata_In(X);

  ...;

  Skriv_Ut_Ledtext_Och_Mata_In(X);

  ...;
end loop;
-- Samma sak i två eller flera grenar
-- i en "if"-sats.

if ... then
  Put("En text");
  Put("Lite till");
  Put("Avslutning");
else
  Put("En text");
  Put("Avslutning");
end if;

-- Detta kan t.ex. vara intressant när man
-- skall skriv ut om något or ok eller ej.

if Sanning then
  Put("Det är sant");
else
  Put("Det är inte sant");
end if;
-- Man har bara kvar det som skiljer.


Put("En text");
if ... then
  Put("Lite till");
end if;
Put("Avslutning");




-- Det är bara "inte" som skiljer. I vårt lilla
-- "minikosmos" kan detta var rimligt.

Put("Det är");
if not Sanning then
  Put(" inte");
end if;
Put(" sant");

-- Detta kan dras till absurdum så att man tar
-- detta ner till teckennivå, men det är alltså
-- inte meningen. T.ex. behöver ni inte ta delar
-- av ord beroende på böjningsformer, men det
-- skulle kunna vara intressant i vissa fall
-- (i "verkligheten").
-- Utskrift av samma sträng flera gånger.



begin

  Put("#################################");
  Put("Detta är mitt program");
  Put("#################################");
  ...;
  Put("#################################");
  ...;
  Put("#################################");
  
-- Flytta innehållet till konstant.

  Separator : constant String :=
    "#################################";
begin

  Put(Separator);
  Put("Detta är mitt program");
  Put(Separator);
  ...;
  Put(Separator);
  ...;
  Put(Separator);
  

Fullständig uppräkning

Hur man inte bör skriva Hur man bör skriva
-- Flera if-satser (eller elsif)
-- som jämför samma variabel.

if I = 1 then
  ...;
elsif I = 3 then
  ...;
elsif ... then
  ...;
end if;


-- Ett intervall av data.

if N = 1 or N = 2 or N = 3 or N = 4 then

-- Lite spridda data (i detta fall
-- udda talen mellan 1 och 7).

if N = 1 or N = 3 or N = 5 or N = 7 then

-- Lite spridda data.

if N = 1 or N = 3 or N = 5 or N = 8 then
  ...;
end if;




-- Udda talen mellan 1 och 7 igen.

if N = 1 or N = 3 or N = 5 or N = 7 then

-- Använd "case" istället för "if".

case I is
  when 1 =>
    ...;
  when 3 =>
    ...;
  when others =>
    ...;
end case;

-- Använd intervall istället.

if N in 1 .. 4 then


-- Kanske tänk annorlunda.

if (N in 1 .. 7) and (N mod 2 = 1) then

-- Använd "case" istället för "if".

case N is
  when 1 | 3 | 5 | 8 =>
    ...;
  when others =>
    ...;
end case;

-- Skapa ett underprogram.

if Udda_Tal_Mellan_1_Och_7(N) then














  I : Integer;

begin

  Put("Välj ett nummer på en veckodag ");
  Get(I);
   
  Put("Den dagen heter: ");

-- Denna uppräkning vill vi inte behöva göra
-- varje gång namnet på en dag skrivs ut.
  if I = 1 then
    Put("Mon");
  elsif I = 2 then
    Put("Tue");
  elsif I = 3 then
    Put("Wed");
  elsif I = 4 then
    Put("Thu");
  elsif I = 5 then
    Put("Fri");
  elsif I = 6 then
    Put("Sat");
  elsif I = 7 then
    Put("Sun");
  end if;
    
-- Detta fält kan vi återanvända överallt
-- i koden, istället för att göra uppräkningen
-- varje gång vi behöver den
  Veckodagar : constant array (1 .. 7) of String(1 .. 3) :=
    (1 => "Mon",
     2 => "Tue", 
     3 => "Wed",
     4 => "Thu",
     5 => "Fri",
     6 => "Sat",
     7 => "Sun");

  I : Integer;

begin

  Put("Välj ett nummer på en veckodag :");
  Get(I);
   
  Put("Den dagen heter: ");
  Put(Veckodagar(I));



Parameterlistor

Här finns det två sorts parameterlistor

  1. Den formella parameterlistan (i definitionen [eller deklarationen] av underprogrammet.
  2. Den aktuella parameterlistan. Den som är i själva anropet av underprogrammet (t.ex. i huvudprogrammet).

Hur man inte bör skriva Hur man bör skriva
Put(I, 2);
Put(F, 2, 3, 0);
Put(I, Width => 2);
Put(F, Fore => 2, Aft, 3, Exp => 0);
Subprog(A,B,C,D,E);
Subprog(A, B, C, D, E);

-- Om det blir långa rader kan man bryta efter ','.

Subprog(A, B, C,
        D, E);
procedure Subprog(P_1:in Type_1;P_2,P_3:in out Type_2) is

procedure Subprog(P_1:in Type_1;
                  P_2, P_3:in out Type_2;
                  P_4:out Type_1;
                  P_5:out Type_3) is                            

procedure Subprog(P_1      : in Type_1;
                  P_2, P_3 : in out Type_2;
                  P_4      : out Type_1;
                  P_5      : out Type_3) is


procedure Subprog(P_1 : in  Type_1;
                  P_4 : out Type_1;
                  P_5 : out Type_3) is                            

function Subprog(P_1:Type_1; P_2,P_3:Type_2) return Type_3 is


function Subprog(P_1:in Type_1;P_2,P_3:in Type_2) return Type_3 is



function Subprog(P_1 : out Type_1) return Type_2 is
-- Mycket mer läsbart.




procedure Subprog(P_1      : in     Type_1;
                  P_2, P_3 : in out Type_2;
                  P_4      :    out Type_1;
                  P_5      :    out Type_3) is                            




procedure Subprog(P_1 : in     Type_1;
                  P_4 :    out Type_1;
                  P_5 :    out Type_3) is                            

function Subprog(P_1      : in Type_1;
                 P_2, P_3 : in Type_2) return Type_3 is

function Subprog(P_1      : in Type_1;
                 P_2, P_3 : in Type_2)
         return Type_3 is

-- Inte "out" i någon form i funktioner
-- (även om det går är det inte ok i kursen).

Många rader kod

I kursen eftersträvar vi att varje program är kortare än 20 rader (max ~20 satser mellan begin och) för ökad läsbarhet. Detta uppnås genom att dela upp program i underprogram.

Hur man inte bör skriva Hur man bör skriva
-- Ett långt och svårläst program.

procedure Exempel is
   
   I : Integer;
   N1, N2 : Integer;
begin
   Put("Mata in ett positivt heltal: ");
   loop
      Get(I);
      exit when I > 0;
      Put_Line("Ej positivt...");
   end loop;
   Put("Ditt tal i kvadrat: ");
   Put(I * I, Width => 0);
   New_Line;
   Put("Första (ditt heltal) fibbonaccitalen: ");
   N1 := 1;
   N2 := 1;
   Put(1, Width => 0);
   for J in 2 .. I loop
      Put(" ");
      Put(N2, Width => 0);
      I := N1;
      N1 := N2;
      N2 := I + N2;
   end loop;
   New_Line;
   Put_Line("En fin liten kvadrat: ");
   for Y in 1 .. I loop
      Put("  ");
      for X in 1 .. I loop
	 Put("##");
      end loop;
      New_Line;
   end loop;
end Exempel;
-- Dela upp programmet i underprogram.

procedure Exempel is
   
   procedure Get_Positive(...) is
      ...;
   begin
      ...;
   end Get_Positive;
   
   ...; -- Resterande underprogram 
   
   I : Integer;
begin
   -- Lättare att se vad programmet gör. 
   Get_Positive(I);
   Put_Square_Of(I);
   Put_Fibonacci(I);
   Put_Square(I);
end Exempel;

Saker som är utanför kursen

Nedan följer en lista över de saker vi upptäckt som är utanför kursen. Alltså sådant som inte tas upp i kursen och därför inte är tillåtet att användas. Denna lista kommer fyllas på allt eftersom fler saker upptäcks.

  • Paketet Ada.Strings.Fixed
  • Paketet Ada.Strings.Unbounded
  • Expression functions

Sidansvarig: Torbjörn Jonsson
Senast uppdaterad: 2022-01-24