Files
2026-08-31 17:14:27 +02:00

12 KiB

name, description
name description
racket-programmeerstijl 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:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Supporting functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

of, wanneer dat beter bij de module past:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal state / functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Voor publieke functies:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Provided functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  • Houd provide en require eenvoudig en overzichtelijk.
  • Voeg geen extra framework, wrapperlaag of generieke infrastructuur toe zonder concrete noodzaak.

Beschrijving van functies

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   : <doel>
; pre    : <preconditie(s)>
; post   : <postconditie(s)>
; [result:] <resultaat en onder welke conditie>
; [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.

Over het algemeen wil ik de internals van een functie weten. Hoe werkt het en waarom werkt het zo. Beschrijf die in de sectie 'internals'. Internals moet de functionele keten beschrijven met concrete namen van helpers en gegevensstructuren, niet alleen een technisch detail noemen. Duidelijk moet zijn hoe het werkt en waarom het zo werkt.

Beschrijving van functies in het algemeen

Iedere functie die wordt gedefinieerd, tenzij een lokale helper functie van maximaal 5 regels krijgt in ieder geval één commentregel met:

;;; <explain what this function does and it's meaning in the module.
;;; In about 1 to 3 lines.

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:
(cond 
  ([condition] korte body)
  ([other-condition]
   langere body)
  (else ... alleen als het nodig is))

Gebruik van lokale helper functies

  • Gewone lokale waarden: let/let*.
  • Een kleine lokale helper: (let ((helper (λ (...) ...))) ...) is prima.
  • Een omvangrijke lokale helper die echt bij één closure hoort: een interne (define (helper ...) ...) is juist duidelijker en toegestaan.

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.

Gebruik van modules en dependencies

  • Gebruik nooit de interne datastructuur van een module/dependency, tenzij die datastructuur publiek is, d.w.z. onderdeel van de Application Programming Interface.
  • Als een API ontoereikend is, d.w.z. er missende functionaliteit is in de provided functions of public/protected functions van een class, dan werk je daar niet omheen door de interne data- structuur rechtstreeks te gebruiken, maar signaleer je dat en stelt een API uitbreiding voor.
  • Respecteer de publieke API van dependencies. Gebruik geen interne datastructuren of ongedocumenteerde representatie.
  • Als de publieke API noodzakelijke functionaliteit, een predicate of een accessor mist, meld dit expliciet als een API-tekort. Verberg dit niet met een lokale wrapper, een zwakker contract zoals any/c, of directe inspectie van de interne datastructuur.
  • Stel voor de dependency-API gericht uit te breiden. Pas pas een lokale workaround toe wanneer de gebruiker daar bewust voor kiest.

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:

(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

@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

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Tests for module <modulenaam> 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 <package>.