--- name: racket-programmeerstijl description: Gebruik deze skill wanneer je Racket-code voor Hans schrijft, wijzigt, refactort of beoordeelt. Pas de bestaande, eenvoudige en procedurele programmeerstijl toe; voorkom over-engineering en onnodige abstracties. Gebruik deze skill niet voor algemene uitleg over Racket waarbij geen code voor zijn projecten wordt gemaakt of aangepast. --- # Racket-programmeerstijl Gebruik deze stijl wanneer je Racket-code voor Hans schrijft of aanpast. ## Uitgangspunt Het *allerbelangrijkste* uitgangspunt is dat je de programmerstijl van Hans volgt. Als je een zip met een package aangeleverd krijgt via de prompt dan volg je de programmeerstijl die je in de aangeleverde code vindt, als die klopt met deze skill. Wanneer bestaande broncode die geprogrammeerd is door Hans beschikbaar is, heeft de stijl van die broncode voorrang. Sluit daar zo nauw mogelijk op aan. Schrijf eenvoudige, directe en goed leesbare Racket-code. Kies de kleinste oplossing die het huidige probleem netjes oplost. Bouw geen abstraheringslaag voor mogelijk toekomstig gebruik. En maak geen helpers die alleen maar in de weg staan. Dus geen code-bloat. Geen overengineering. Gewoon to the point en helder leesbare code. ## Structuur - Houd modules klein en doelgericht. - Splits functionaliteit alleen af naar een private module wanneer die een duidelijk eigen doel heeft. - Gebruik voor duidelijke secties bij voorkeur commentaar in deze vorm: ```racket ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Supporting functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ``` of, wanneer dat beter bij de module past: ```racket ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Internal state / functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ``` Voor publieke functies: ```racket ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Provided functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ``` - Houd `provide` en `require` eenvoudig en overzichtelijk. - Voeg geen extra framework, wrapperlaag of generieke infrastructuur toe zonder concrete noodzaak. ## pre/postcondities Geëxporteerde functies/procedures/classes of functies/procedures/classes die daarvoor duidelijk in aanmerking komen, d.w.z. die die provided zijn of naar verwachting zullen worden, moeten gedocumenteerd worden. Zowel in een module scribble als in de code zelf. In het engels. In de code zelf: minimaal: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; goal : ; pre : ; post : ; [result:] Over het algemeen wil ik de internals van een functie weten. Hoe werkt het en waarom werkt. Beschrijf die in de sectie 'internals'. ; [internals:] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Als een functie/procedure/class overduidelijk in aanmerking komt voor 'provide' en hij staat er nog niet in. Verzamel dan de lijst en vraag of je ze moet toevoegen. ## Procedures en control flow - Geef de voorkeur aan gewone procedures met een direct leesbare control flow. - Gebruik van `let`, `let*`, `letrec`, `if`, `when`, `unless`, `begin` en `cond` heeft de voorkeur. - `map`, `filter`, en dat soort constructies gaan boven meer abstracte constructies als `for/or`, etc. - Gebruik bij voorkeur **geen** `define` binnen een procedure, tenzij het echt om een locale functie definitie gaat die een dermate omvang krijgt dat het binnen de closure gerechtvaardigd is. - `let-values` is prima om te gebruiken. - Maak niet voor iedere kleine stap een aparte helperprocedure. - Introduceer geen hogere-orde of functionele constructies alleen omdat dat compacter kan. - Gebruik recursie of een named `let` wanneer dat de meest directe oplossing is. - Gebruik bij `cond` bij voorkeur deze vorm: ```racket (cond ([condition] korte body) ([other-condition] langere body) (else ... alleen als het nodig is)) ``` ## Mate van abstractie vs leesbaarheid Liever concreet dan abstract. Geef expliciete, goed leesbare constructies de voorkeur boven compacte abstracte idiomen. Bijvoorbeeld combinaties als (filter values (list (and condition 'symbol) ...)). Schrijf dan liever expliciet (filter (lambda (x) x) (list (if condition 'symbol #f) ...)). Vermijd vooral het stapelen van meerdere impliciete idiomen wanneer dat de leesbaarheid vermindert. ## Gebruik lambda. Geef de voorkeur aan λ boven lambda. ## Waarden en state - Gebruik `#f` als normale waarde voor "niet gevonden", "niet beschikbaar" of "nog niet geïnitialiseerd" wanneer dat natuurlijk past. - Expliciete vergelijkingen zoals `(eq? value #f)` zijn prima wanneer dat de bedoeling duidelijk maakt. - Houd state eenvoudig. Een gewone modulevariabele zoals `cached-git-exe` is prima wanneer daarvoor geen zwaarder mechanisme nodig is. - Gebruik geen parameters, structs, classes of objectlagen wanneer een gewone variabele of procedure voldoende is. ## Condities - `and` en `or` mogen een willekeurig aantal subexpressies bevatten, zolang iedere subexpressie eenvoudig en direct leesbaar is. - Als een subexpressie te ingewikkeld wordt, bereken die dan eerst en geef het resultaat een duidelijke naam met `let` of `let*`. ## Publieke API - Gebruik `define/contract` voor publieke procedures wanneer een contract nuttige documentatie en controle geeft. - Houd publieke procedures klein en voorspelbaar. - Verander een bestaande publieke API niet zonder noodzaak. - Voeg geen extra publieke functies toe voor hypothetische toekomstige behoeften. ## Fouten en interactie - Geef duidelijke en concrete foutmeldingen. - Los eenvoudige interactieve invoer lokaal en procedureel op. - Maak foutafhandeling niet generieker dan nodig. - Als een externe executable of voorziening ontbreekt, meld precies wat ontbreekt en wat de gebruiker kan doen. ## Configuratie - Bewaar lokale configuratie in een kleine, afzonderlijke private module wanneer dat de hoofdmodule eenvoudiger maakt. - Gebruik bestaande projectvoorzieningen, zoals `simple-ini`, rechtstreeks in plaats van er een extra abstractielaag omheen te bouwen. - Dupliceer geen configuratie die al door een extern programma zelf wordt beheerd. ## Naamgeving en leesbaarheid - Kies concrete, korte namen die passen bij de bestaande code. - Gebruik Engels voor identifiers en technische namen wanneer de bestaande code dat doet. - Schrijf comments alleen wanneer ze iets toevoegen dat niet al vanzelf uit de code blijkt. - Geef de voorkeur aan een paar duidelijke regels boven een compacte maar moeilijker leesbare expressie. ## Vermijd Vermijd zonder concrete noodzaak: - over-engineering; - generieke wrappers; - extra abstraheringslagen; - dynamische `require`-constructies; - classes wanneer procedures volstaan; - structs wanneer een eenvoudige waarde volstaat; - configuratie-objecten of dependency-injectionpatronen; - veel kleine helperprocedures die de control flow versnipperen; - refactors die alleen bedoeld zijn om code "slimmer" of abstracter te maken. ## Werkwijze bij aanpassen van bestaande code 1. Lees eerst de omliggende module(s). 2. Neem naamgeving, inspringing, control-flow-stijl en module-indeling over. 3. Wijzig alleen wat voor de gevraagde stap nodig is. 4. Houd bestaande werkende code intact als er geen reden is die te veranderen. 5. Voeg geen volgende architectuurstappen alvast toe. 6. Controleer of de oplossing eenvoudiger is dan het probleem; zo niet, vereenvoudig. ## Referentiestijl Deze vorm is representatief: ```racket (define cached-value #f) (define/contract (get-value) (-> (or/c path? #f)) (if (eq? cached-value #f) (let ((value (find-value))) (set! cached-value value) value) cached-value)) ``` Een wat langere maar direct leesbare implementatie heeft de voorkeur boven een kortere oplossing met meerdere nieuwe abstracties. # schrijven van documentatie Schrijf altijd documentatie van een package in de scrbl subdirectory in .scrbl files. Als je de auteur aangeeft hierin gebruik je ```scrbl @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] ``` # Schrijven van testgevallen voor modules/packages Een test die alleen werkt vanuit de development directory, op het development-OS of met de lokale shell/environment is geen geldige package-test. ## Racket Package Index / build-service tests Als je tests toevoegt aan een racket module, zet daar dan altijd ```racket ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Tests for module voor. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ``` Behandel de Racket Package Index/build service als een aparte, strikte en onbekende testomgeving. Bij packagecode en tests gelden daarom altijd de volgende regels: - Maak nooit aannames over `current-directory` of de directory van waaruit code of tests worden uitgevoerd. Bepaal testdata en paden expliciet en relocatable, bijvoorbeeld met runtime paths en tijdelijke directories. - Maak nooit impliciete aannames over het besturingssysteem. Vermijd OS-specifieke paden, shells, executables en gedrag, of handel verschillen expliciet per platform af. - Maak tests onafhankelijk van lokale environment state. Benodigde environment variables moeten expliciet en bij voorkeur geïsoleerd worden ingesteld. - Een succesvolle test moet stil en ondubbelzinnig succesvol zijn. Laat geen verwachte foutmeldingen naar de echte stdout/stderr lekken, omdat `raco test --drdr` en de Package Index dergelijke output als een mogelijke test failure kunnen classificeren. - Verwachte foutoutput moet worden gecaptureerd en geassert. - Tests moeten hun eigen tijdelijke state en testbestanden aanmaken en mogen geen bestanden, processen, environment changes of andere state achterlaten. - Test packagewijzigingen waar mogelijk ook in een omgeving die lijkt op: `raco setup --check-pkg-deps` en `raco test --drdr --package `.