skill added
This commit is contained in:
@@ -214,6 +214,13 @@
|
|||||||
(regexp-match? #px"^/api/(?:auth|agent)(?:/|$)"
|
(regexp-match? #px"^/api/(?:auth|agent)(?:/|$)"
|
||||||
(request-path request)))
|
(request-path request)))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Add one HTTP header to an existing response.
|
||||||
|
; pre : Value is a response and extra-header is an HTTP header.
|
||||||
|
; post : The original response remains unchanged.
|
||||||
|
; result : A response with the same body and metadata and the additional
|
||||||
|
; header prepended to its header list.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (response-add-header value extra-header)
|
(define (response-add-header value extra-header)
|
||||||
(response (response-code value)
|
(response (response-code value)
|
||||||
(response-message value)
|
(response-message value)
|
||||||
@@ -222,6 +229,15 @@
|
|||||||
(cons extra-header (response-headers value))
|
(cons extra-header (response-headers value))
|
||||||
(response-output value)))
|
(response-output value)))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Dispatch an API request and renew an eligible browser cookie.
|
||||||
|
; pre : Current-player and current-auth are initialized and request targets
|
||||||
|
; an API route.
|
||||||
|
; post : The selected handler has run. A due browser-session renewal is
|
||||||
|
; recorded and returned as Set-Cookie; agent requests never renew it.
|
||||||
|
; result : The HTTP response produced by the API handler, optionally extended
|
||||||
|
; with the renewed session cookie.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (dispatch-api request)
|
(define (dispatch-api request)
|
||||||
(define value (api-dispatch request))
|
(define value (api-dispatch request))
|
||||||
(define renewed-cookie
|
(define renewed-cookie
|
||||||
|
|||||||
+83
-5
@@ -26,6 +26,13 @@
|
|||||||
(username [last-seen #:mutable] [last-cookie-renewal #:mutable])
|
(username [last-seen #:mutable] [last-cookie-renewal #:mutable])
|
||||||
#:transparent)
|
#:transparent)
|
||||||
(struct failures ([attempts #:mutable] [started #:mutable]) #:transparent)
|
(struct failures ([attempts #:mutable] [started #:mutable]) #:transparent)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Hold configured users, trusted proxies, and volatile auth state.
|
||||||
|
; pre : Constructor fields contain normalized and parsed internal values.
|
||||||
|
; post : Creating or recognizing a value does not change external state.
|
||||||
|
; result : auth-manager? recognizes values used by the authentication API.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(struct auth-manager
|
(struct auth-manager
|
||||||
(users trusted-proxies session-seconds sessions failed lock)
|
(users trusted-proxies session-seconds sessions failed lock)
|
||||||
#:transparent)
|
#:transparent)
|
||||||
@@ -45,6 +52,12 @@
|
|||||||
(define failure-window-seconds 300)
|
(define failure-window-seconds 300)
|
||||||
(define maximum-failures 5)
|
(define maximum-failures 5)
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Create an Argon2id password hash for configuration storage.
|
||||||
|
; pre : Password is a string containing at least twelve characters.
|
||||||
|
; post : No module state is changed.
|
||||||
|
; result : A salted Argon2id hash encoded as a string.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (make-password-hash password)
|
(define (make-password-hash password)
|
||||||
(unless (and (string? password)
|
(unless (and (string? password)
|
||||||
(>= (string-length password) 12))
|
(>= (string-length password) 12))
|
||||||
@@ -56,6 +69,12 @@
|
|||||||
(string->bytes/utf-8 password)
|
(string->bytes/utf-8 password)
|
||||||
password-parameters))
|
password-parameters))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Verify a password against an encoded Argon2id hash.
|
||||||
|
; pre : Password and encoded are arbitrary values.
|
||||||
|
; post : No module state is changed.
|
||||||
|
; result : #t only when both values are strings and the password matches.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (password-hash-valid? password encoded)
|
(define (password-hash-valid? password encoded)
|
||||||
(and (string? password)
|
(and (string? password)
|
||||||
(string? encoded)
|
(string? encoded)
|
||||||
@@ -135,6 +154,12 @@
|
|||||||
(string-trim (last (string-split forwarded ",")))
|
(string-trim (last (string-split forwarded ",")))
|
||||||
peer))
|
peer))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Report whether browser authentication is configured.
|
||||||
|
; pre : Manager is an auth-manager.
|
||||||
|
; post : Manager remains unchanged.
|
||||||
|
; result : #t when at least one configured user can log in, otherwise #f.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (auth-enabled? manager)
|
(define (auth-enabled? manager)
|
||||||
(positive? (hash-count (auth-manager-users manager))))
|
(positive? (hash-count (auth-manager-users manager))))
|
||||||
|
|
||||||
@@ -150,6 +175,14 @@
|
|||||||
(auth-manager-session-seconds manager))
|
(auth-manager-session-seconds manager))
|
||||||
(hash-remove! (auth-manager-sessions manager) token)))))
|
(hash-remove! (auth-manager-sessions manager) token)))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Resolve the browser user represented by a request cookie.
|
||||||
|
; pre : Manager is an auth-manager and request is an HTTP request.
|
||||||
|
; post : Expired sessions are removed and a valid session's last-seen time
|
||||||
|
; is updated.
|
||||||
|
; result : "anonymous" when authentication is disabled, the normalized
|
||||||
|
; username for a valid session, or #f when login is required.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (auth-request-user manager request)
|
(define (auth-request-user manager request)
|
||||||
(cond
|
(cond
|
||||||
((not (auth-enabled? manager)) "anonymous")
|
((not (auth-enabled? manager)) "anonymous")
|
||||||
@@ -187,7 +220,16 @@
|
|||||||
address
|
address
|
||||||
(failures 1 now))))
|
(failures 1 now))))
|
||||||
|
|
||||||
;; Returns a new token, #f for invalid credentials, or 'rate-limited.
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Authenticate credentials and start a browser session.
|
||||||
|
; pre : Manager is an auth-manager, request is an HTTP request, and
|
||||||
|
; username and password are strings.
|
||||||
|
; post : A valid login creates a new session; a failed login updates the
|
||||||
|
; rate-limit state for the effective client address.
|
||||||
|
; result : A new opaque token, #f for invalid credentials, or 'rate-limited.
|
||||||
|
; internals: Unknown users follow the same Argon2id verification path as known
|
||||||
|
; users to reduce username-dependent timing differences.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (auth-login! manager request username password)
|
(define (auth-login! manager request username password)
|
||||||
(define address (request-address manager request))
|
(define address (request-address manager request))
|
||||||
(define now (current-seconds))
|
(define now (current-seconds))
|
||||||
@@ -217,6 +259,12 @@
|
|||||||
(record-failure! manager address now)
|
(record-failure! manager address now)
|
||||||
#f)))))))
|
#f)))))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : End the browser session named by the request cookie.
|
||||||
|
; pre : Manager is an auth-manager and request is an HTTP request.
|
||||||
|
; post : The matching server-side session is removed when it exists.
|
||||||
|
; result : Void.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (auth-logout! manager request)
|
(define (auth-logout! manager request)
|
||||||
(let ((token (request-session-token request)))
|
(let ((token (request-session-token request)))
|
||||||
(when token
|
(when token
|
||||||
@@ -225,6 +273,13 @@
|
|||||||
(lambda ()
|
(lambda ()
|
||||||
(hash-remove! (auth-manager-sessions manager) token))))))
|
(hash-remove! (auth-manager-sessions manager) token))))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Encode an authenticated session token as a browser cookie.
|
||||||
|
; pre : Manager is an auth-manager and token is a session token string.
|
||||||
|
; post : Manager remains unchanged.
|
||||||
|
; result : A Secure, HttpOnly, SameSite=Strict Set-Cookie value whose Max-Age
|
||||||
|
; equals the configured session lifetime.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (auth-session-cookie manager token)
|
(define (auth-session-cookie manager token)
|
||||||
(string->bytes/utf-8
|
(string->bytes/utf-8
|
||||||
(format
|
(format
|
||||||
@@ -233,9 +288,17 @@
|
|||||||
token
|
token
|
||||||
(auth-manager-session-seconds manager))))
|
(auth-manager-session-seconds manager))))
|
||||||
|
|
||||||
;; Return a refreshed cookie at most once per half session lifetime. The
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; server-side inactivity timer is updated on every authenticated request, but
|
; goal : Renew an actively used browser cookie at a bounded frequency.
|
||||||
;; limiting Set-Cookie avoids rewriting it for every one-second player poll.
|
; pre : Manager is an auth-manager and request is an HTTP request.
|
||||||
|
; post : Expired sessions are removed. When renewal is due, the session's
|
||||||
|
; last-cookie-renewal time is advanced.
|
||||||
|
; result : A fresh Set-Cookie value after half the configured lifetime has
|
||||||
|
; elapsed, otherwise #f.
|
||||||
|
; internals: The server idle timer moves on every authenticated request, while
|
||||||
|
; this half-life threshold prevents the one-second player poll from
|
||||||
|
; returning Set-Cookie every second.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (auth-renewal-cookie manager request)
|
(define (auth-renewal-cookie manager request)
|
||||||
(and (auth-enabled? manager)
|
(and (auth-enabled? manager)
|
||||||
(let ((token (request-session-token request))
|
(let ((token (request-session-token request))
|
||||||
@@ -243,7 +306,7 @@
|
|||||||
(and token
|
(and token
|
||||||
(call-with-semaphore
|
(call-with-semaphore
|
||||||
(auth-manager-lock manager)
|
(auth-manager-lock manager)
|
||||||
(lambda ()
|
(λ ()
|
||||||
(prune-sessions! manager now)
|
(prune-sessions! manager now)
|
||||||
(define value
|
(define value
|
||||||
(hash-ref (auth-manager-sessions manager) token #f))
|
(hash-ref (auth-manager-sessions manager) token #f))
|
||||||
@@ -257,12 +320,27 @@
|
|||||||
(set-session-last-cookie-renewal! value now)
|
(set-session-last-cookie-renewal! value now)
|
||||||
(auth-session-cookie manager token)))))))))
|
(auth-session-cookie manager token)))))))))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Encode deletion of the browser session cookie.
|
||||||
|
; pre : None.
|
||||||
|
; post : No module state is changed.
|
||||||
|
; result : A Secure, HttpOnly, SameSite=Strict Set-Cookie value with Max-Age 0.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (auth-expired-cookie)
|
(define (auth-expired-cookie)
|
||||||
(string->bytes/utf-8
|
(string->bytes/utf-8
|
||||||
(format
|
(format
|
||||||
"~a=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Strict"
|
"~a=; Path=/; Max-Age=0; Secure; HttpOnly; SameSite=Strict"
|
||||||
session-cookie-name)))
|
session-cookie-name)))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Create the authentication and session manager used by the server.
|
||||||
|
; pre : User-pairs contains username and Argon2id-hash pairs, trusted proxy
|
||||||
|
; values are IP addresses or CIDR networks, and session-seconds is a
|
||||||
|
; positive exact integer.
|
||||||
|
; post : No external state is changed; session and rate-limit tables start
|
||||||
|
; empty.
|
||||||
|
; result : A new auth-manager with normalized usernames and parsed networks.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(define (make-auth-manager user-pairs
|
(define (make-auth-manager user-pairs
|
||||||
#:trusted-proxies
|
#:trusted-proxies
|
||||||
[trusted-proxy-values '("127.0.0.0/8" "::1/128")]
|
[trusted-proxy-values '("127.0.0.0/8" "::1/128")]
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
---
|
||||||
|
name: racket-programmeer-skill
|
||||||
|
description: Hiermee wordt mijn voorkeur racket programmeerstijl aangegeven.
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
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 aangeleverde code 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.
|
||||||
|
Wanneer bestaande broncode 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.
|
||||||
|
|
||||||
|
## 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 : <doel>
|
||||||
|
; pre : <preconditie(s)>
|
||||||
|
; post : <postconditie(s)>
|
||||||
|
; [result:] <resultaat en onder welke conditie>
|
||||||
|
|
||||||
|
Over het algemeen wil je de internals van een functie weten. Hoe werkt het en waarom werkt het zo.
|
||||||
|
|
||||||
|
; [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 `let`, `if`, `when`, `unless`, `begin` en `cond` expliciet wanneer dat de code duidelijker maakt.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## 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 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
|
||||||
|
|
||||||
|
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>`.
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user