Structured
Programming
& Modula-2
Project
Gestructureerd
programmeren
Wat wil dat nu allemaal in de praktijk zeggen? Welnu, we proberen hier
enkele veel voorkomende foutjes van beginnende programmeur op te
sommen, en hoe je ze het best anders aanpakt. Hou hiermee rekening bij het
programmeren van je project!
Tip: naamgeving van
variabelen
Schrijf niet:
VAR
a,b,g,h,k,r,t,u,w,z : CARDINAL;
Dan weet je na een paar weken niet meer wat alles wil zeggen. Je kan i,j,k gebruiken voor tellers
van FOR lussen en x,y,z
voor coordinaten, maar voor alle andere variabelen gebruik je best
duidelijke namen. Lokale variabelen in procedures kan je nog afkorten,
maar doe dit *nooit* met globale variabelen. Het is misschien iets meer
typwerk, maar het is veel minder leeswerk (omdat de code duidelijker
is) en code wordt vaker
gelezen dan geschreven, dus netto haal je er winst uit!
Tip: gebruik Arrays
Schrijf niet:
VAR
speler1, speler2, speler3, speler4: SOMETYPE;
Maar wel:
VAR
spelers: ARRAY[1..4] OF CARDINAL;
Of nog beter:
CONST
AANTALSPELERS = 4;
VAR
spelers: ARRAY[1..AANTALSPELERS] OF SOMETYPE;
Nu kan je gemakkelijk het aantal spelers aanpassen. En je kan
(verschrikkelijke) code zoals het volgende
IF (n = 1)
THEN
TekenSpeler(speler1);
ELSIF (n = 2) THEN
TekenSpeler(speler2);
ELSIF (n = 3)
THEN
TekenSpeler(speler3);
ELSIF (n = 4)
THEN
TekenSpeler(speler4);
END;
vervangen door
TekenSpeler(spelers[n]);
En je code wordt veel korter, properder en gewoon
beter.
Tip: gebruik Arrays met Enumeraties
Schrijf niet:
VAR BitmapRechts, BitmapLinks,
BitmapBoven, BitmapOnder : CARDINAL;
Beter:
TYPE
RichtingEn = (Rechts, Links, Boven, Onder);
VAR Bitmap: ARRAY RinchtingEn OF CARDINAL;
Bij uitbreiding:
VAR BitmapKonijnRechts,
BitmapKonijnLinks, BitmapKonijnBoven, BitmapKonijnOnder, BitmapKikkerRechts, BitmapKikkerLinks, BitmapKikkerBoven, BitmapKikkerOnder : CARDINAL;
kan je best vervangen door:
TYPE
RichtingEn = (Rechts, Links, Boven, Onder);
SpelerTypeEn = (Konijn,
Kikker);
VAR
Bitmap: ARRAY SpelerTypeEn,RinchtingEn OF CARDINAL;
Dan kan je gewoon aan de variabelen door Bitmap[Konijn,Rechts] en
dergelijke te schrijven. De kans is trouwens groot dat deze enumeraties
nog elders in het programma nuttig zijn.
Tip: misbruik CASE niet!
Schrijf niet:
CASE x OF
-1: snelheid := snelheid + 10;
| 1: snelheid := snelheid - 10;
END;
CASE y OF
1: DrawLine(10, 100, 100, 200,
_clrRED);
|2: DrawLine(20, 100, 100, 200, _clrRED);
|3: DrawLine(30, 100,
100, 200, _clrRED);
|4: DrawLine(40, 100,
100, 200, _clrRED);
|5: DrawLine(50,
100,
100,
200, _clrRED);
|6: DrawLine(60,
100,
100,
200, _clrRED);
|7: DrawLine(70,
100,
100,
200, _clrRED);
|8: DrawLine(80, 100,
100, 200, _clrRED);
|9: DrawLine(90, 100, 100, 200, _clrRED);
|10: DrawLine(100, 100,
100, 200, _clrRED);
END;
CASE v OF
1: a := a + 1;
|2: a := a + 1;
|3: a := a + 2;
|4: a := a + 2;
|5: a := a +
3;
|6: a
:= a + 3;
END;
Maar schrijf:
snelheid =
snelheid - x * 10;
DrawLine(10 * y, 100, 100, 200, _clrRED);
a := a + ((v + 1) / 2);
Het 2e is wel wat korter nietwaar? We merken vaak dat de code in een
CASE of een IF de parameter waarop getest wordt kan gebruiken om de
code veel korter (en duidelijker!) te maken. Let op voor dit soort
fouten, want ze maken je code veel langer zonder echte toegevoegde
waarde!
Tip: vermijd referentie
naar globale variabelen
schrijf niet
VAR x, y, vx, vy :
CARDINAL;
PROCEDURE
Beweeg();
(* gebruik vx en vy en de tijd om x en y aan te passen *)
END Beweeg;
beter
PROCEDURE Beweeg(VAR x,y : CARDINAL;
vx, vy : CARDINAL);
(* gebruik vx en vy en de tijd om x en y aan te passen *)
END Beweeg;
en vaak nog beter:
TYPE ObjectRc
= RECORD
x, y, vx, vy : CARDINAL;
END;
PROCEDURE BeweegObject(VAR
obj : ObjectRc);
(* gebruik obj.vx en obj.vy en de tijd om obj.x en obj.y aan te passen
*)
END
BeweegObject;
In het 2e en 3e geval zijn de procedures algemener en kan je ze ook
gebruiken voor andere objecten behalve voor dat ene dat globaal
gedefinieerd is. Ja kan op deze manier vaak ook globale variabelen
uitsparen.
schrijf niet:
VAR vijanden : ARRAY[1..AANTAL] OF
VijandType;
PROCEDURE TekenVijand(i : CARDINAL);
(* teken vijanden[i] *)
END
TekenVijand;
beter:
PROCEDURE
TekenVijand(vijand : VijandType);
(* teken vijand *)
END
TekenVijand;
In het 2e geval kan je de procedure ook gebruiken voor vijanden die
niet in de ARRAY zitten. Bovendien moet deze ARRAY misschien zelfs geen
globale variabele zijn in dit geval.
Tip: gebruik Procedures
met parameters
Schrijf niet
PROCEDURE GaLinks();
PROCEDURE
GaRechts();
PROCEDURE GaBoven();
PROCEDURE GaOnder();
PROCEDURE TekenSpeler1();
PROCEDURE TekenSpeler2();
Schrijf wel:
TYPE
RichtingEn = (Rechts, Links, Boven, Onder);
PROCEDURE GaNaar(RichtingEn richting);
PROCEDURE
TekenSpeler(x: CARDINAL);
of: PROCEDURE
TekenSpeler(speler : SpelerType);
Dit is een zware fout! Een procedure kan je parameters meegeven,
doe dit dus als het nuttig is! Typisch zien we dat in het eerste
geval alle procedures toch ongeveer hetzelfde doen.
Tip: gebruik Procedures
om 2x zelfde code te vermijden
schrijf niet
(*
hoofdprogramma *)
IF (player =
1) THEN
(* een hoop code
met speler 1 *)
ELSE
(* ongeveer
dezelfde code voor speler 2 *)
(* nog wat andere dingen *)
END;
Maar wel
PROCEDURE
DoWithPlayer(x : CARDINAL);
BEGIN
(* een hoop code
met speler x *)
END
DoWithPlayer;
en
(*
hoofdprogramma *)
DoWithPlayer(player);
IF (player =
2) THEN
(* nog wat andere dingen *)
END;
Over het algemeen zal altijd gelden dat als er ergens twee keer
ongeveer dezelfde code staat je deze best in een procedure zet. Simpele
variaties als "speler 1 beweegt naar rechts en speler 2 naar links" kan
je meestal gemakkelijk oplossen, bijvoorbeeld:
IF (player =
1) THEN
speed := 1;
ELSE
speed := -1;
END;
(* en nu alle
horizontale verplaatsingen vermenigvuldigen met "speed" *)
Tip: Procedures
Iets als
PROCEDURE
VeryBigProcedure(...);
BEGIN
(* deze procedure is heeeeeellll lang *)
END VeryBigProcedure;
Kan voorkomen in je programma, maar probeer je toch best te vermijden.
Als je een procedure hebt van enkele honderden regels moet je toch eens
nadenken of je die niet beter opsplitst in enkele logische stukken.
Bijvoorbeeld:
PROCEDURE
VeryBigProcedure(...);
BEGIN
REPEAT
InitField(...);
WHILE (NOT
done) DO
done = PlayGame(...);
END;
exitGame =
AskUserToQuit(...);
UNTIL
(exitGame);
END VeryBigProcedure;
Zelfs als de procedures InitField,
PlayGame, AskUserToQuit nergens
anders worden opgeroepen is dit een goed idee. Je ziet nu veel beter de
structuur van je programma. Denk eraan:
code wordt veel vaker gelezen dan ze geschreven wordt! Met deze
code zal waarschijnlijk de procedure PlayGame ook vrij groot zijn,
dus daar kan je dan weer dezelfde gedachte-oefening op doen. Op het
einde zou iedere procedure zich zoveel mogelijk moeten beperken tot 1
taak, en een logisch geheel op zich moeten zijn.