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;

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;

"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.

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;

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").

Fullständig uppräkning

Hur man inte bör skriva Hur man bör skriva
-- 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 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

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                            

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                            



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).


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