Overzicht en programmeerstijl
Intro
Om overzicht te houden in programmeerproject (met name de groteren), heeft men programmeerstijlen uitgevonden. Die zijn vergelijkbaar met schrijfstijlen (informeel, formeel(brief), formeel(artikel) en wat dies meer zei.
Het nut van dergelijke stijlen is vooral consistentie; je doet altijd alles op dezelfde manier. Bij programmeerstijlen zijn er ook nog praktische aspecten: de ene stijl ligt meer voor de hand als de andere met een bepaalde taal.
stijlen
Besproken (of liever aangestipte) stijlen (N.B. de beschrijvingen hieronder zijn analogie/schets, geen feiten):
- Bottom Up: evolutionaire stijl, je begint met kleine utility-functies zoals 'open deze file', 'lees die file', en 'kwadrateer dit getal', en bouwt zo door totdat je je doel hebt bereikt. In principe een stijl die goed op LISP/ Scheme aansluit, maar heeft nadelen. Op den duur wordt een gegroeid programma moeilijk om bij tye houden en te debuggen. Een mooi voorbeeld: Van de helgele MSDOS prompt die windows 1.0 ooit was tot win2000. Alhoewel ik vermoed dat ze niet met hele kleine basisfuncties zijn begonnen, geeft het wel aan dat bijhouden en debuggen van zo'n groeiprogram (wat win2000 per definitie is vanwege de 'backward compatibility') op den duur onmogelijk wordt.
- Top Down: analytische stijl, begint met programmeren-op-papier (i.e je probleem/doel analyseren, en 'pseudo-coderen'). In zijn meest pure vorm staat alles, tot en met de punten en komma's aan toe, al vast voordat er ook maar 1 letter geprogrammeerd wordt. Voorbeelden: geen, voor zover ik weet. Deze stijl is er eentje in het kader van 'goede voornemens'. Dat wil zeggen, in de praktijk begint vrijwel iedereen zo, maar na (meestal) kortere of langere tijd is men overgegaan op Bottom Up. Je hebt deze stijl ECHT nodig als je met meerdere personen tegelijkertijd werkt aan hetzelfde project.
- OOP: Object Oriented Programming, een buzzword van het moment (of in ieder gveal kort geleden). Werkzameme bestanddeel: data encapsulation. Een Object bevat data (die is ENCAPSULATED in het object) en daarop werkzame functies, zodat je de data niet meer hoeft te zien (i.e. data hiding). Dit is nuttig in de context van hergebruik van code, i.e. je code voor program1 gebruiken bij program2. Ook wordt de syntax van de basistaal (OOP bedrijf je bovenop de programmeertaal waarin je werkt, dus je kunbt zowel OOP als niet-OOP programmeren in bijv. C) iets declaratiever van uiterlijk (lijkt meer op Prolog dan op C t.o.v. de basistaal).
De een is niet beter of slechter dan de ander, alhoewel de een misschien wel beter bij jou of het probleem waaraan je werkt past, als de ander. In de praktijk vind je vooral mengvormen.
functies, procedures, en return values
Functies zijn stukjes onafhankelijke code die een bepaalde specifieke taak uitvoeren. Bij het ontwerpen van zo'n functie heb je keuzes: hoeveel en welke parameters krijgt deze functie, maar ook welke return-values. Nu, een functie ZONDER return values wordt meestal 'procedure' genoemd. Een procedure is dus feitelijk een speciaal soort functie. Pascal bijvoorbeeld doet alleen maar procedures (omwille van de systematiek), als ik het wel heb. Lisp doet alleen maar functies, i.e. het heeft altijd een return-value, of je hem nu gebruikt of niet. Het kiezen voor functies danwel procedures is voornamelijk een stijlkwestie aangezien je ook om het gebrek aan return-value van procedures heen kunt programmeren, bijv. middels globale variabelen.
Het NIET gebruiken van return-values als ze er toch zijn, is daarentegen meer een gebrek aan stijl :) Vergelijk de volgende stukjes pseudo-code:
1) y = (square x) OF (set! y (square x))
2) (square x y)
Optie 1 is, met gebruikmaking van de return-value van de functie 'square', onmiddelijk duidelijk. Optie 2, in Scheme context, lijkt eerder vatbaar voor de interpretatie "kwadrateer x, en ook y", als je het mij vraagt. Maar 'y' kan natuurlijk ook de variabele zijn waar je het resultaat in stopt.
andere stijlzaken
Ondertussen zijn we er allemaal achter dat je in Scheme liever niet dingen als 'x = x + 1' doet. Dit is een eigenaardigheidje van Scheme in het bijzonder en declaratief programmeren in het algemeen.
Maar hoe doe je dan een loopje als:
x=1;
while (x < 10) x = x + 1;
in Scheme?
(define t1 (lambda (x) x))
(define test (lambda (i max)
(cond
((or (not (number? max)) (equal? max 0)) "No max")
((not (number? i)) "No iterator")
((> i max) "Done") ;**
(else (begin
(display "i=")
(display i)
(display " result=")
(display (t1 i)) ;***
(newline)
(test (+ i 1) max) ;*
)
)
)
))
Ik heb hier dus niet 1 keer een toekenning/assignment gepleegd op 'i' ... Ik hoog 'i' (impliciet) precies 1 keer op (per aanroep) bij *.
Voor mijn triviale 't1' functie kun je natuurlijk iedere willekeurige functie gebruiken, incl. return-values. Let ook op de laatste regel output (hier "Done") dat de return-value is van 'test' zelf. Typisch komt de return-value van een functie ter beschikking van de aanroepende functie (zie ***). Als je die functie zelf met de hand hebt aangeroepen, komt-ie op het scherm (zie **).
'Test' is hier een zogenaamde wrapper-functie, die voor de programmeur 't1' aanroept met de juiste argumenten, zo vaak als nodig. Deze 'truuk', een functie om je basis functie heen schrijven die het lastige werk voor je doet, is bij tijd en wijle erg handig :)
Opdracht
Analyseer je eindopdracht (indien al bekend, anders je tussenopdracht) in termen van probleemstelling, opdelen van probleem in deelproblemen (die uiteraard corresponderen met aanwezige functies :), etc, zodat je
- overzicht hebt in je programma, en
- begrijpt en kunt uitleggen welke functies er zijn, en waarom.