Time
Je kunt alle code voor dit hoofdstuk hier vinden
De producteigenaar wil dat we de functionaliteit van onze opdrachtregelapplicatie uitbreiden door een groep mensen te helpen Texas Holdem Poker te spelen.
Net genoeg informatie over poker
Je hoeft niet veel over poker te weten, alleen dat alle spelers op bepaalde tijdstippen geĂŻnformeerd moeten worden over een gestaag oplopende "blinde" waarde.
Onze applicatie helpt bij het bijhouden wanneer de blinde inzet verhoogd moet worden en hoeveel deze moet bedragen.
Bij aanvang wordt gevraagd hoeveel spelers er spelen. Dit bepaalt de tijd voordat de "blinde" inzet verhoogd wordt.
Er is een basistijd van 5 minuten.
Voor elke speler wordt 1 minuut toegevoegd.
Bijvoorbeeld: 6 spelers is gelijk aan 11 minuten voor de blinde.
Nadat de blinde inzet is verstreken, moet het spel de spelers waarschuwen voor het nieuwe bedrag van de blinde inzet. - De blinde inzet begint bij 100 chips, dan 200, 400, 600, 1000, 2000 en blijft verdubbelen tot het spel eindigt (onze eerdere functionaliteit van "Ruth wint" zou het spel nog steeds moeten voltooien).
Herinnering aan de code
In het vorige hoofdstuk zijn we begonnen met de opdrachtregelapplicatie die al de opdracht {name} wint accepteert. Zo ziet de huidige CLI-code eruit, maar zorg ervoor dat je ook de andere code kent voordat je begint.
type CLI struct {
playerStore PlayerStore
in *bufio.Scanner
}
func NewCLI(store PlayerStore, in io.Reader) *CLI {
return &CLI{
playerStore: store,
in: bufio.NewScanner(in),
}
}
func (cli *CLI) PlayPoker() {
userInput := cli.readLine()
cli.playerStore.RecordWin(extractWinner(userInput))
}
func extractWinner(userInput string) string {
return strings.Replace(userInput, " wins", "", 1)
}
func (cli *CLI) readLine() string {
cli.in.Scan()
return cli.in.Text()
}time.AfterFunc
time.AfterFuncWe willen ons programma zo kunnen inplannen dat de waarden van de blinde inzet met bepaalde tijdsduren worden afgedrukt, afhankelijk van het aantal spelers.
Om de reikwijdte van wat we moeten doen te beperken, laten we het aantal spelers even buiten beschouwing en gaan we ervan uit dat er 5 spelers zijn. We testen dan of elke 10 minuten de nieuwe waarde van de blinde inzet wordt afgedrukt.
Zoals gebruikelijk is de standaardbibliotheek voorzien van func AfterFunc(d Duration, f func()) *Timer
AfterFuncwacht tot de tijdsduur is verstreken en roept vervolgens f aan in zijn eigen goroutine. Het retourneert eenTimerdie kan worden gebruikt om de aanroep te annuleren met behulp van de Stop-methode.
A Duration represents the elapsed time between two instants as an int64 nanosecond count.
The time library has a number of constants to let you multiply those nanoseconds so they're a bit more readable for the kind of scenarios we'll be doing
Wanneer we PlayPoker aanroepen, plannen we al onze blind alerts.
Het testen hiervan kan echter lastig zijn. We willen controleren of elke tijdsperiode is gepland met het juiste blindbedrag. Maar als je naar de handtekening van time.AfterFunc kijkt, zie je dat het tweede argument de functie is die wordt uitgevoerd. Je kunt geen functies vergelijken in Go, dus we kunnen niet testen welke functie is verzonden. We moeten dus een soort wrapper rond time.AfterFunc schrijven die de tijd om uit te voeren en de hoeveelheid om af te drukken opneemt, zodat we dat kunnen zien.
Schrijf eerst de test
Voeg een nieuwe test toe aan onze suite
Je zult merken dat we een SpyBlindAlerter hebben gemaakt die we in onze CLI proberen te injecteren en vervolgens controleren of er een waarschuwing is gepland nadat we PlayPoker hebben aangeroepen.
(Onthoud dat we eerst het eenvoudigste scenario uitproberen en dat we dit vervolgens herhalen.)
Hier is de definitie van SpyBlindAlerter
Probeer de test uit te voeren
Schrijf de minimale hoeveelheid code om de test uit te voeren en controleer de mislukte testuitvoer.
We hebben een nieuw argument toegevoegd en de compiler klaagt. Strikt genomen is de minimale hoeveelheid code bedoeld om NewCLI een *SpyBlindAlerter te laten accepteren, maar laten we een beetje vals spelen en de afhankelijkheid gewoon als interface definiëren.
En voeg het dan toe aan de constructor
Je andere tests zullen nu mislukken omdat ze geen BlindAlerter hebben doorgegeven aan NewCLI.
Spioneren op BlindAlerter is niet relevant voor de andere tests, dus voeg in het testbestand toe
Gebruik dat vervolgens in de andere tests om de compilatieproblemen op te lossen. Door het als "dummy" te labelen, is het voor de lezer van de test duidelijk dat het niet belangrijk is.
De tests zouden nu moeten compileren en onze nieuwe test mislukt.
Schrijf voldoende code om de test te laten slagen
We moeten BlindAlerter als veld toevoegen aan onze CLI, zodat we ernaar kunnen verwijzen in onze PlayPoker-methode.
Om de test te laten slagen, kunnen we onze BlindAlerter aanroepen met wat we maar willen
Vervolgens willen we controleren of het alle gewenste meldingen inplant voor 5 spelers.
Schrijf eerst de test
De tabelgebaseerde test werkt hier prima en illustreert duidelijk onze vereisten. We doorlopen de tabel en controleren de SpyBlindAlerter om te zien of de waarschuwing met de juiste waarden is gepland.
Probeer de test uit te voeren
Je zou veel fouten moeten zien die er zo uitzien.
Schrijf voldoende code om de test te laten slagen
Het is niet veel ingewikkelder dan wat we al hadden. We itereren nu gewoon over een array van blinds en roepen de scheduler aan met een oplopende blindTime.
Refactor
We kunnen onze geplande waarschuwingen in een methode inkapselen, zodat PlayPoker wat duidelijker leesbaar is.
Uiteindelijk lijken onze tests wat onhandig. We hebben twee anonieme structuren die hetzelfde vertegenwoordigen: een ScheduledAlert. Laten we dat refactoren naar een nieuw type en vervolgens wat hulpmiddelen maken om ze te vergelijken.
We hebben een String()-methode aan ons type toegevoegd, zodat deze netjes wordt afgedrukt als de test mislukt.
We hebben onze test bijgewerkt om ons nieuwe type te gebruiken.
Implementeer assertScheduledAlert zelf.
We hebben hier behoorlijk wat tijd besteed aan het schrijven van tests en zijn een beetje stout geweest door niet te integreren met onze applicatie. Laten we dat aanpakken voordat we meer eisen stellen.
Probeer de app te draaien, maar hij compileert niet en klaagt over onvoldoende argumenten voor NewCLI.
Laten we een implementatie van BlindAlerter maken die we in onze applicatie kunnen gebruiken.
Maak blind_alerter.go aan, verplaats onze BlindAlerter-interface en voeg de nieuwe dingen hieronder toe.
Onthoud dat elk type een interface kan implementeren, niet alleen structs. Als je een bibliotheek maakt die een interface met één gedefinieerde functie beschikbaar stelt, is het een gebruikelijke idioom om ook een MyInterfaceFunc-type beschikbaar te stellen.
Dit type zal een func zijn die ook je interface implementeert. Zo hebben gebruikers van je interface de mogelijkheid om je interface te implementeren met slechts één functie, in plaats van een leeg struct-type te hoeven aanmaken.
Vervolgens maken we de functie StdOutAlerter aan, die dezelfde handtekening heeft als de functie, en gebruiken we time.AfterFunc om te plannen dat deze naar os.Stdout wordt afgedrukt.
We werken main bij waar we NewCLI aanmaken om dit in actie te zien.
Voordat je de test uitvoert, kun je de blindTime-increment in de CLI wijzigen naar 10 seconden in plaats van 10 minuten, zodat je het in actie kunt zien.
Je zou de blinde waarden elke 10 seconden moeten zien afdrukken zoals we verwachten. Merk op dat je nog steeds Shaun wint in de CLI kunt typen en het programma stopt zoals we verwachten.
Het spel wordt niet altijd met 5 spelers gespeeld, dus we moeten de gebruiker vragen om een ​​aantal spelers in te voeren voordat het spel begint.
Schrijf eerst de test
Om te controleren of we het aantal spelers vragen, willen we vastleggen wat er naar StdOut wordt geschreven. We hebben dit nu een paar keer gedaan en we weten dat os.Stdout een io.Writer is, dus we kunnen controleren wat er wordt geschreven als we dependency injection gebruiken om een ​​bytes.Buffer in onze test door te geven en zien wat onze code zal schrijven.
We maken ons nog geen zorgen over onze andere medewerkers in deze test, dus hebben we een paar dummy's in ons testbestand gemaakt.
We moeten er wel rekening mee houden dat we nu vier afhankelijkheden hebben voor CLI, wat misschien te veel verantwoordelijkheden met zich meebrengt. Laten we er voorlopig mee leren leven en kijken of er een refactoring ontstaat wanneer we deze nieuwe functionaliteit toevoegen.
Hier is onze nieuwe test:
We geven os.Stdout door in main en kijken wat er geschreven wordt.
Probeer de test uit te voeren
Schrijf de minimale hoeveelheid code voor de test om uit te voeren en controleer de mislukte testuitvoer.
We hebben een nieuwe afhankelijkheid, dus we moeten NewCLI bijwerken.
De other tests zullen nu niet compileren omdat ze geen io.Writer hebben die wordt doorgegeven aan NewCLI.
Voeg dummyStdout toe voor de andere tests.
De nieuwe test zou als volgt moeten mislukken
Schrijf voldoende code om het te laten slagen
We moeten onze nieuwe afhankelijkheid toevoegen aan onze CLI, zodat we ernaar kunnen verwijzen in PlayPoker
Dan kunnen we eindelijk onze prompt aan het begin van het spel schrijven
Refactor
We hebben een dubbele string voor de prompt die we moeten extraheren naar een constante
Gebruik dit in zowel de testcode als de CLI.
Nu moeten we een nummer invoeren en eruit halen. De enige manier om te weten of het het gewenste effect heeft gehad, is door te kijken welke blinde waarschuwingen er zijn gepland.
Schrijf eerst de test
Au! Veel veranderingen.
We verwijderen onze dummy voor StdIn en sturen in plaats daarvan een nagebootste versie die onze gebruiker 7 laat invoeren.
We verwijderen ook onze dummy voor de blinde alerter, zodat we kunnen zien dat het aantal spelers effect heeft gehad op de planning.
We testen welke alerts er gepland zijn.
Probeer de test uit te voeren.
De test zou nog steeds moeten compileren en mislukken door te melden dat de geplande tijden onjuist zijn, omdat we de game hard hebben geprogrammeerd om te worden gebaseerd op 5 spelers.
Schrijf genoeg code om het te laten slagen
Onthoud dat we vrij zijn om elke zonde te begaan die we nodig hebben om dit te laten werken. Zodra we werkende software hebben, kunnen we beginnen met het refactoren van de puinhoop die we gaan maken!
We lezen
numberOfPlayersInputin als een string.We gebruiken
cli.readLine()om de invoer van de gebruiker op te halen en roepen vervolgensAtoiaan om deze om te zetten naar een geheel getal, waarbij we eventuele foutscenario's negeren. We moeten later een test voor dat scenario schrijven.Vanaf hier wijzigen we
scheduleBlindAlertsom een aantal spelers te accepteren. Vervolgens berekenen we eenblindIncrement-tijd die we toevoegen aanblindTimeterwijl we itereren over de blinde aantallen.
Hoewel onze nieuwe test is gerepareerd, zijn veel andere mislukt, omdat ons systeem nu alleen werkt als het spel begint met een gebruiker die een getal invoert. Je moet de tests repareren door de gebruikersinvoer te wijzigen, zodat er een getal gevolgd door een nieuwe regel wordt toegevoegd (dit brengt nu nog meer tekortkomingen in onze aanpak aan het licht).
Refactor
Dit voelt allemaal een beetje afschuwelijk, toch? Laten we luisteren naar onze tests.
Om te testen of we bepaalde meldingen inplannen, hebben we vier verschillende afhankelijkheden ingesteld. Wanneer je veel afhankelijkheden hebt voor iets in je systeem, betekent dit dat het te veel doet. Visueel kunnen we dit zien aan hoe rommelig onze test is.
Het voelt alsof we een duidelijkere abstractie moeten maken tussen het lezen van gebruikersinvoer en de bedrijfslogica die we willen uitvoeren.
Een betere test zou zijn: roepen we, gegeven deze gebruikersinvoer, een nieuw type 'Game' aan met het juiste aantal spelers?
Vervolgens zouden we de tests van de planning extraheren naar de tests voor onze nieuwe 'Game'.
We kunnen eerst refactoren naar onze 'Game' en onze test zou moeten slagen. Zodra we de gewenste structurele wijzigingen hebben aangebracht, kunnen we nadenken over hoe we de tests kunnen refactoren om onze nieuwe scheiding van aandachtspunten te weerspiegelen.
Onthoud dat je bij het aanbrengen van wijzigingen in de refactoring moet proberen deze zo klein mogelijk te houden en de tests steeds opnieuw uit te voeren.
Probeer het eerst zelf. Denk na over de grenzen van wat een 'Game' zou bieden en wat onze 'CLI' zou moeten doen.
Verander voorlopig de externe interface van 'NewCLI' niet, omdat we de testcode en de clientcode niet tegelijkertijd willen wijzigen. Dat is te veel werk en we zouden dingen kapot kunnen maken.
Dit is wat ik bedacht heb:
Vanuit een domeinperspectief:
We willen een
Gamestarten en aangeven hoeveel mensen er spelen.We willen een
Gamebeëindigen en de winnaar bekendmaken.
Het nieuwe Game-type omvat dit voor ons.
Met deze wijziging hebben we BlindAlerter en PlayerStore doorgegeven aan Game, omdat dit nu verantwoordelijk is voor het melden en opslaan van resultaten.
Onze CLI houdt zich nu alleen bezig met:
Het construeren van
Gamemet de bestaande afhankelijkheden (die we vervolgens zullen refactoren).Het interpreteren van gebruikersinvoer als methodeaanroepen voor
Game.
We willen grote refactoren vermijden, waardoor we gedurende langere tijd in een staat van falende tests terechtkomen, omdat dit de kans op fouten vergroot. (Als je in een groot/gedistribueerd team werkt, is dit extra belangrijk.)
Het eerste wat we doen, is Game refactoren, zodat we het in de CLI injecteren. We zullen de kleinste wijzigingen in onze tests doorvoeren om dit te vergemakkelijken en vervolgens zullen we bekijken hoe we de tests kunnen opsplitsen in de thema's van het parsen van gebruikersinvoer en gamebeheer.
Het enige wat we nu hoeven te doen, is NewCLI wijzigen.
Dit voelt al als een verbetering. We hebben minder afhankelijkheden en onze afhankelijkheidslijst weerspiegelt ons algemene ontwerpdoel: de CLI houdt zich bezig met invoer/uitvoer en delegeert gamespecifieke acties aan een Game.
Als je probeert te compileren, treden er problemen op. Je zou deze problemen zelf moeten kunnen oplossen. Maak je nu nog geen zorgen over het maken van mocks voor Game, initialiseer gewoon de echte Game om alles te compileren en de tests groen te maken.
Om dit te doen, moet je een constructor maken
Hier is een voorbeeld van een van de opstellingen voor de tests die worden gerepareerd
Het zou niet veel moeite moeten kosten om de tests te repareren en weer groen te zijn (dat is juist de bedoeling!), maar zorg er wel voor dat je ook main.go repareert voordat je doorgaat naar de volgende fase.
Nu we Game hebben geëxtraheerd, moeten we onze gamespecifieke beweringen verplaatsen naar tests die losstaan van de CLI.
Dit is slechts een oefening in het kopiëren van onze CLI-tests, maar dan met minder afhankelijkheden.
De bedoeling achter wat er gebeurt wanneer een pokerspel begint, is nu veel duidelijker.
Zorg ervoor dat je ook de test verplaatst wanneer het spel eindigt.
Zodra we tevreden zijn met de verplaatsing van de tests voor spellogica, kunnen we onze CLI-tests vereenvoudigen, zodat ze onze beoogde verantwoordelijkheden duidelijker weergeven.
Gebruikersinvoer verwerken en de methoden van
Gameaanroepen wanneer dat nodig is.Uitvoer verzenden.
Cruciaal is dat de CLI niet op de hoogte is van de daadwerkelijke werking van spellen.
Om dit te doen, moeten we ervoor zorgen dat CLI niet langer afhankelijk is van een concreet Game-type, maar in plaats daarvan een interface accepteert met Start(aantalSpelers) en Finish(winnaar). We kunnen dan een spion van dat type aanmaken en controleren of de juiste calls worden gemaakt.
Hier realiseren we ons dat het benoemen soms lastig is. Hernoem Game naar TexasHoldem (aangezien dat het soort spel is dat we spelen) en de nieuwe interface zal Game heten. Hiermee wordt trouw gebleven aan het idee dat onze CLI geen weet heeft van het spel dat we spelen en wat er gebeurt als u op Start en Finish klikt.
Vervang alle verwijzingen naar *Game in CLI door Game (onze nieuwe interface). Blijf zoals altijd de tests opnieuw uitvoeren om te controleren of alles groen is tijdens het refactoren.
Nu we CLI hebben losgekoppeld van TexasHoldem, kunnen we spies gebruiken om te controleren of Start en Finish worden aangeroepen wanneer we dat verwachten, met de juiste argumenten.
Maak een spy die Game implementeert
Vervang elke CLI-test die gamespecifieke logica test door controles op de aanroep van onze GameSpy. Dit zal dan duidelijk de verantwoordelijkheden van de CLI in onze tests weergeven.
Hier is een voorbeeld van een van de tests die wordt gerepareerd; probeer de rest zelf uit te voeren en controleer de broncode als je vastloopt.
Nu we een duidelijke scheiding van aandachtspunten hebben, zou het controleren van grensgevallen rond IO in onze CLI eenvoudiger moeten zijn.
We moeten het scenario aanpakken waarbij een gebruiker een niet-numerieke waarde invoert wanneer er om het aantal spelers wordt gevraagd:
Onze code zou het spel niet moeten starten en een handige foutmelding aan de gebruiker moeten tonen en vervolgens moeten afsluiten.
Schrijf eerst de test
We beginnen met ervoor te zorgen dat het spel niet start
Je moet aan onze GameSpy een veld StartCalled toevoegen, dat alleen wordt aangemaakt als Start wordt aangeroepen.
Probeer de test uit te voeren
Schrijf voldoende code om het te laten slagen
Rond de plek waar we Atoi aanroepen, hoeven we alleen nog maar te controleren op de fout
Vervolgens moeten we de gebruiker informeren over wat hij/zij fout heeft gedaan, zodat we een bewering kunnen doen op basis van wat er naar stdout is gestuurd.
Schrijf eerst de test
We hebben eerder een bewering gedaan op basis van wat er naar stdout is gestuurd, dus we kunnen die code nu kopiëren.
We slaan alles op dat naar stdout wordt geschreven, dus we verwachten nog steeds de poker.PlayerPrompt. Vervolgens controleren we of er nog iets wordt afgedrukt. We maken ons nu niet al te druk om de exacte bewoording, we pakken het aan wanneer we de refactoring uitvoeren.
Probeer de test uit te voeren
Schrijf voldoende code om het te laten slagen
Wijzig de code voor foutverwerking
Refactor
Refactor het bericht nu naar een constante zoals PlayerPrompt
en een meer passende boodschap plaatsen
Ten slotte is ons testen van wat er naar stdout is verzonden vrij uitgebreid. Laten we een assert-functie schrijven om het op te schonen.
Het gebruik van de vararg-syntaxis (...string) is hier handig, omdat we moeten asserten op verschillende aantallen berichten.
Gebruik deze helper in beide tests waarin we asserten op berichten die naar de gebruiker worden verzonden.
Er zijn een aantal tests die geholpen kunnen worden met sommige assertX-functies, dus oefen je refactoring door onze tests op te schonen zodat ze goed leesbaar zijn.
Neem even de tijd om na te denken over de waarde van een aantal van de tests die we hebben uitgevoerd. Onthoud dat we niet meer tests willen dan nodig is. Kun je een aantal ervan refactoren/verwijderen en er toch zeker van zijn dat alles werkt?
Dit is wat ik heb bedacht.
De tests weerspiegelen nu de belangrijkste mogelijkheden van CLI. Het kan gebruikersinvoer lezen in termen van hoeveel spelers er spelen en wie er gewonnen heeft, en handelt af wanneer er een onjuiste waarde voor het aantal spelers wordt ingevoerd. Hierdoor is het voor de lezer duidelijk wat CLI doet, maar ook wat het niet doet.
Wat gebeurt er als de gebruiker in plaats van Ruth wint Lloyd is een moordenaar invoert?
Sluit dit hoofdstuk af door een test voor dit scenario te schrijven en deze te laten slagen.
Samenvattend
Een korte samenvatting van het project
De afgelopen 5 hoofdstukken hebben we langzaam een behoorlijke hoeveelheid code TDD'd.
We hebben twee applicaties: een opdrachtregelapplicatie en een webserver.
Beide applicaties maken gebruik van een
PlayerStoreom winnaars bij te houden.De webserver kan ook een ranglijst weergeven van wie de meeste games wint.
De opdrachtregelapplicatie helpt spelers een potje poker te spelen door bij te houden wat de huidige blindwaarde is.
time.Afterfunc
Een zeer handige manier om een functieaanroep na een bepaalde tijdsduur te plannen. Het is zeker de moeite waard om er tijd in te steken bekijk de documentatie voor time omdat het veel tijdbesparende functies en methoden bevat waarmee je kunt werken.
Een paar van mijn favorieten zijn:
time.After(duration)retourneert eenchan Timewanneer de tijdsduur is verstreken. Dus als je iets na een bepaalde tijd wilt doen, kan dit helpen.time.NewTicker(duration)retourneert eenTickerdie vergelijkbaar is met de bovenstaande, in die zin dat het een kanaal retourneert, maar deze "tikt" elke tijdsduur, in plaats van slechts één keer. Dit is erg handig als je elkeN tijdsduurcode wilt uitvoeren.
Meer voorbeelden van een goede scheiding van aandachtspunten
Over het algemeen is het een goede gewoonte om de verantwoordelijkheden voor het omgaan met gebruikersinvoer en -reacties te scheiden van de domeincode. Je ziet dat hier in onze commandline-applicatie en ook op onze webserver.
Onze tests raakten rommelig. We hadden te veel assertions (controleer deze invoer, plan deze waarschuwingen, enz.) en te veel afhankelijkheden. We konden visueel zien dat het rommelig was; het is zo belangrijk om naar je tests te luisteren.
Als je tests er rommelig uitzien, probeer ze dan te refactoren.
Als je dit hebt gedaan en ze nog steeds rommelig zijn, wijst dat zeer waarschijnlijk op een fout in je ontwerp.
Dit is een van de echte sterke punten van tests.
Hoewel de tests en de productiecode wat rommelig waren, konden we vrijelijk refactoren, ondersteund door onze tests.
Onthoud in dergelijke situaties dat je altijd kleine stapjes moet zetten en de tests na elke wijziging opnieuw moet uitvoeren.
Het zou gevaarlijk zijn geweest om zowel de testcode als de productiecode tegelijkertijd te refactoren, dus hebben we eerst de productiecode gerefactored (in de huidige staat konden we de tests niet veel verbeteren) zonder de interface aan te passen, zodat we zoveel mogelijk op onze tests konden vertrouwen terwijl we dingen aanpasten. Vervolgens hebben we de tests gerefactored nadat het ontwerp was verbeterd.
Na de refactoring weerspiegelde de afhankelijkheidslijst ons ontwerpdoel. Dit is een ander voordeel van DI, omdat het vaak de intentie documenteert. Wanneer je afhankelijk bent van globale variabelen, worden de verantwoordelijkheden erg onduidelijk.
Een voorbeeld van een functie die een interface implementeert
Wanneer je een interface definieert met één methode erin, kun je overwegen om een MyInterfaceFunc-type te definiëren als aanvulling hierop, zodat gebruikers je interface met slechts één functie kunnen implementeren.
By doing this, people using your library can implement your interface with just a function. They can use Type Conversion to convert their function into a BlindAlerterFunc and then use it as a BlindAlerter (as BlindAlerterFunc implements BlindAlerter).
Het algemene punt is dat je in Go methoden aan typen kunt toevoegen, niet alleen aan structs. Dit is een zeer krachtige functie, die je kunt gebruiken om interfaces op een handigere manier te implementeren.
Bedenk dat je niet alleen typen van functies kunt definiëren, maar ook typen rond andere typen, zodat je er methoden aan kunt toevoegen.
We hebben hier een HTTP-handler gemaakt die een heel eenvoudige "blog" implementeert, waarbij URL-paden als sleutels naar berichten in een kaart worden gebruikt.
Laatst bijgewerkt