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.