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).
- Skall detta upprepas oändligt många gånger (eller tills
strömmen bryts)?
Detta ger direkt "loop".
- Vet du antal gånger du skall upprepa detta (eller kan räkna
ut det)?
Detta ger direkt "for".
- Vet du att du skall utföra detta minst en gång?
Detta ger direkt "loop".
- 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
- Den formella parameterlistan (i definitionen [eller
deklarationen] av underprogrammet.
- 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