In onze vorige les hebben we een nieuwe manier geleerd om code beter te begrijpen en te verbeteren door uit te pakken tot we erbij neervallen. Hoewel die tutorial een goede manier was om de technieken te leren, was het nauwelijks het ideale voorbeeld om de voordelen ervan te begrijpen. In deze les zullen we extraheren tot we op al onze Trivia-game-gerelateerde code vallen en we zullen het uiteindelijke resultaat analyseren.
Deze les zal ook onze reeks over refactoring afronden. Als u denkt dat we iets hebben gemist, kunt u reageren met een voorgesteld onderwerp. Als goede ideeën samenkomen, ga ik door met extra zelfstudies op basis van uw verzoeken.
Wat is een betere manier om ons artikel te starten dan door onze langste methode te nemen en deze in kleine stukjes te extraheren. Eerst testen, zoals gebruikelijk, maakt deze procedure niet alleen efficiënt, maar ook leuk.
Zoals gewoonlijk, hebt u de code zoals deze was toen ik deze zelfstudie begon in de php_start
map, terwijl het eindresultaat in de php
map.
function wasCorrectlyAnswered () if ($ this-> inPenaltyBox [$ this-> currentPlayer]) if ($ this-> isGettingOutOfPenaltyBox) $ this-> display-> correctAnswer (); $ This-> portemonnees [$ this-> currentPlayer] ++; $ this-> display-> playerCoins ($ this-> spelers [$ this-> currentPlayer], $ this-> portemonnees [$ this-> currentPlayer]); $ winnaar = $ dit-> didPlayerNotWin (); $ This-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; win $ winnaar; else $ this-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; return true; else $ this-> display-> correctAnswerWithTypo (); $ This-> portemonnees [$ this-> currentPlayer] ++; $ this-> display-> playerCoins ($ this-> spelers [$ this-> currentPlayer], $ this-> portemonnees [$ this-> currentPlayer]); $ winnaar = $ dit-> didPlayerNotWin (); $ This-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; win $ winnaar;
Deze methode, wasCorrectlyAnswered ()
, is ons eerste slachtoffer.
Zoals we hebben geleerd uit eerdere lessen, is de eerste stap bij het wijzigen van oude code het testen ervan. Dit kan een moeilijk proces zijn. Gelukkig voor ons, de wasCorrectlyAnswered ()
methode is vrij eenvoudig. Het bestaat uit meerdere if-else
statements. Elke tak van de code retourneert een waarde. Wanneer we een geretourneerde waarde hebben, kunnen we altijd speculeren dat testen uitvoerbaar is. Niet noodzakelijk eenvoudig, maar op zijn minst mogelijk.
function testWasCorrectlyAnsweredAndGettingOutOfPenaltyBoxWhileBeingAWinner () $ this-> setAPlayerThatIsInThePenaltyBox (); $ this-> game-> isGettingOutOfPenaltyBox = true; $ this-> game-> portemonnees [$ this-> game-> currentPlayer] = Game :: $ numberOfCoinsToWin; $ This-> assertTrue ($ this-> Spel-> wasCorrectlyAnswered ());
Er is geen definitieve regel over welke test het eerst moet worden geschreven. We hebben zojuist het eerste pad van uitvoering gekozen. We hadden eigenlijk een leuke verrassing en we hergebruikden een van de privémethoden die we eerder met veel tutorials hebben geëxtraheerd. Maar we zijn nog niet klaar. Allemaal groen, dus het is tijd voor refactoring.
function testWasCorrectlyAnsweredAndGettingOutOfPenaltyBoxWhileBeingAWinner () $ this-> setAPlayerThatIsInThePenaltyBox (); $ This-> currentPlayerWillLeavePenaltyBox (); $ This-> setCurrentPlayerAWinner (); $ This-> assertTrue ($ this-> Spel-> wasCorrectlyAnswered ());
Dit is gemakkelijker te lezen en aanzienlijk meer beschrijvend. Je kunt de geëxtraheerde methoden vinden in de bijgevoegde code.
function testWasCorrectlyAnsweredAndGettingOutOfPenaltyBoxWhileNOTBeingAWinner () $ this-> setAPlayerThatIsInThePenaltyBox (); $ This-> currentPlayerWillLeavePenaltyBox (); $ This-> setCurrentPlayerNotAWinner (); $ This-> assertFalse ($ this-> Spel-> wasCorrectlyAnswered ()); persoonlijke functie setCurrentPlayerNotAWinner () $ this-> game-> portemonnees [$ this-> game-> currentPlayer] = 0;
We verwachtten dat dit zou slagen, maar het mislukt. De redenen zijn helemaal niet duidelijk. Een nadere blik op didPlayerNotWin ()
kan nuttig zijn.
function didPlayerNotWin () return! ($ this-> portemonnees [$ this-> currentPlayer] == self :: $ numberOfCoinsToWin);
De methode geeft feitelijk als resultaat wanneer een speler niet heeft gewonnen. Misschien kunnen we onze variabele hernoemen, maar eerst moeten tests slagen.
persoonlijke functieset CurrentPlayerAWinner () $ this-> game-> portemonnees [$ this-> game-> currentPlayer] = Game :: $ numberOfCoinsToWin; persoonlijke functie setCurrentPlayerNotAWinner () $ this-> game-> portemonnees [$ this-> game-> currentPlayer] = 0;
Bij nader inzien kunnen we zien dat we de waarden hier door elkaar hebben gehaald. Onze verwarring tussen de naam van de methode en de naam van de variabele deed ons de voorwaarden omkeren.
persoonlijke functieset CurrentPlayerAWinner () $ this-> game-> portemonnees [$ this-> game-> currentPlayer] = 0; persoonlijke functie setCurrentPlayerNotAWinner () $ this-> game-> portemonnees [$ this-> game-> currentPlayer] = Game :: $ numberOfCoinsToWin - 1;
Dit werkt. Tijdens het analyseren didPlayerNotWin ()
we merkten ook op dat het gelijkheid gebruikt om de winnaar te bepalen. We moeten onze waarde op een waarde verlagen, omdat de waarde wordt verhoogd in de productiecode die we testen.
De overige drie tests zijn eenvoudig te schrijven. Het zijn slechts variaties van de eerste twee. Je kunt ze vinden in de bijgevoegde code.
Het meest verwarrende probleem is het misleidende $ winnaar
variabele naam. Dat zou moeten zijn $ notAWinner
.
$ notAWinner = $ this-> didPlayerNotWin (); $ This-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; return $ notAWinner;
We kunnen waarnemen dat de $ notAWinner
variabele wordt alleen gebruikt om een waarde terug te geven. Kunnen we de didPlayerNotWin ()
methode direct in de return-verklaring?
$ This-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; return $ this-> didPlayerNotWin ();
Dit is nog steeds geslaagd voor onze unit-test, maar als we onze Golden Master-tests uitvoeren, zullen ze falen met de foutmelding "niet genoeg geheugen". In feite maakt de verandering het spel nooit af.
Wat er gebeurt, is dat de huidige speler wordt bijgewerkt naar de volgende speler. Omdat we een enkele speler hadden, hebben we altijd dezelfde speler opnieuw gebruikt. Dat is hoe testen is. Je weet nooit wanneer je een verborgen logica ontdekt in moeilijke code.
function testWasCorrectlyAnsweredAndGettingOutOfPenaltyBoxWhileBeingAWinner () $ this-> setAPlayerThatIsInThePenaltyBox (); $ this-> game-> add ('Another Player'); $ This-> currentPlayerWillLeavePenaltyBox (); $ This-> setCurrentPlayerAWinner (); $ This-> assertTrue ($ this-> Spel-> wasCorrectlyAnswered ());
Door gewoon een andere speler aan elke test toe te voegen die gerelateerd is aan deze methode, kunnen we ervoor zorgen dat de logica wordt afgedekt. Met deze test wordt de gewijzigde retourinstructie hierboven mislukt.
persoonlijke functie selectNextPlayer () $ this-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0;
We kunnen meteen zien dat de selectie van de volgende speler identiek is op beide paden van de conditie. We kunnen het verplaatsen naar een eigen methode. De naam die we voor deze methode hebben gekozen is selectNextPlayer ()
. Deze naam helpt bij het benadrukken van het feit dat de huidige speler de waarde zal veranderen. Het suggereert ook dat didPlayerNotWin ()
kan worden hernoemd in iets dat de relatie met de huidige speler weergeeft.
function wasCorrectlyAnswered () if ($ this-> inPenaltyBox [$ this-> currentPlayer]) if ($ this-> isGettingOutOfPenaltyBox) $ this-> display-> correctAnswer (); $ This-> portemonnees [$ this-> currentPlayer] ++; $ this-> display-> playerCoins ($ this-> spelers [$ this-> currentPlayer], $ this-> portemonnees [$ this-> currentPlayer]); $ notAWinner = $ this-> didCurrentPlayerNotWin (); $ This-> selectNextPlayer (); return $ notAWinner; else $ this-> selectNextPlayer (); geef waar terug; else $ this-> display-> correctAnswerWithTypo (); $ This-> portemonnees [$ this-> currentPlayer] ++; $ this-> display-> playerCoins ($ this-> spelers [$ this-> currentPlayer], $ this-> portemonnees [$ this-> currentPlayer]); $ notAWinner = $ this-> didCurrentPlayerNotWin (); $ This-> selectNextPlayer (); return $ notAWinner;
Onze code wordt steeds korter en expressiever. Wat kunnen we hierna doen? We zouden de vreemde naam van de "niet-winnaar" -logica kunnen veranderen en de methode kunnen veranderen in een positieve logica in plaats van in een negatieve. Of we kunnen doorgaan met extraheren en de negatieve logica-verwarring later verwerken. Ik denk niet dat er één definitieve manier is om te gaan. Ik zal het probleem van de negatieve logica dus als een oefening voor je achterlaten en we zullen doorgaan met de extractie.
function wasCorrectlyAnswered () if ($ this-> inPenaltyBox [$ this-> currentPlayer]) return $ this-> getCorrectlyAnsweredForPlayersInPenaltyBox (); else return $ this-> getCorrectlyAnsweredForPlayersNotInPenaltyBox ();
Probeer als vuistregel om een enkele coderegel op elk pad van een beslissingslogica te hebben.
We hebben het hele codeblok in elk deel van ons uitgepakt als
uitspraak. Dit is een belangrijke stap, en iets waar je altijd aan moet denken. Wanneer u een beslissingspad of een lus in uw code hebt, moet de binnenkant ervan slechts één instructie zijn. De persoon die deze methode leest, zal waarschijnlijk niet om de implementatiedetails geven. Hij of zij zal geven om de beslissingslogica, de als
uitspraak.
function wasCorrectlyAnswered () if ($ this-> inPenaltyBox [$ this-> currentPlayer]) return $ this-> getCorrectlyAnsweredForPlayersInPenaltyBox (); return $ this-> getCorrectlyAnsweredForPlayersNotInPenaltyBox ();
En als we van eventuele extra code af kunnen komen, zouden we dat moeten doen. De. Verwijderen anders
en toch houden we de logica hetzelfde, we hebben een beetje economie gemaakt. Ik vind deze oplossing leuker omdat het laat zien wat het "standaard" gedrag van de functie is. De code die zich direct in het binnenste van de functie bevindt (de laatste regel code hier). De als
verklaring is de uitzonderlijke functionaliteit toegevoegd aan de standaard.
Ik heb redeneringen gehoord dat het schrijven van de voorwaarden op deze manier het feit kan verbergen dat de standaard niet wordt uitgevoerd als de als
verklaring wordt geactiveerd. Ik kan het hier alleen maar mee eens zijn, en als je liever de anders
deel daar voor duidelijkheid, doe dat alsjeblieft.
private function getCorrectlyAnsweredForPlayersInPenaltyBox () if ($ this-> isGettingOutOfPenaltyBox) return $ this-> getCorrectlyAnsweredForPlayerGettingOutOfPenaltyBox (); else return $ this-> getCorrectlyAnsweredForPlayerStayingInPenaltyBox ();
We kunnen doorgaan met extraheren binnen onze nieuw gecreëerde privémethoden. Het toepassen van hetzelfde principe op onze volgende voorwaardelijke verklaring leidt tot de bovenstaande code.
private function giveCurrentUserACoin () $ this-> portemonnees [$ this-> currentPlayer] ++;
Door naar onze privémethoden te kijken getCorrectlyAnsweredForPlayersNotInPenaltyBox ()
en getCorrectlyAnsweredForPlayerGettingOutOfPenaltyBox ()
we kunnen meteen zien dat een eenvoudige opdracht wordt gedupliceerd. Die opdracht kan voor iemand als wij vanzelfsprekend zijn die al weet wat er met de portemonnees en munten zit, maar niet voor een nieuwkomer. Het extraheren van die enkele regel in een methode giveCurrentUserACoin ()
is een goed idee.
Het helpt ook bij duplicatie. Als we in de toekomst de manier veranderen waarop we spelers munten geven, moeten we de code alleen binnen deze privémethode wijzigen.
private function getCorrectlyAnsweredForPlayersNotInPenaltyBox () $ this-> display-> correctAnswerWithTypo (); return $ this-> getCorrectlyAnsweredForAPlayer (); private function getCorrectlyAnsweredForPlayerGettingOutOfPenaltyBox () $ this-> display-> correctAnswer (); return $ this-> getCorrectlyAnsweredForAPlayer (); private function getCorrectlyAnsweredForAPlayer () $ this-> giveCurrentUserACoin (); $ this-> display-> playerCoins ($ this-> spelers [$ this-> currentPlayer], $ this-> portemonnees [$ this-> currentPlayer]); $ notAWinner = $ this-> didCurrentPlayerNotWin (); $ This-> selectNextPlayer (); return $ notAWinner;
Dan zijn de twee correct beantwoorde methoden identiek, behalve dat een van hen iets uitvoert met een typefout. We hebben de duplicaatcode geëxtraheerd en de verschillen bij elke methode bewaard. U denkt misschien dat we de geëxtraheerde methode met een parameter in de bellercode hadden kunnen gebruiken en eenmaal normaal en eenmaal met een typefout konden uitvoeren. De hierboven voorgestelde oplossing heeft echter een voordeel: het houdt de twee concepten gescheiden van niet in strafschopgebied en het wegvallen van een strafschopgebied.
Hiermee is het werk voltooid wasCorrectlyAnswered ()
.
function wrongAnswer () $ this-> display-> incorrectAnswer (); $ currentPlayer = $ this-> spelers [$ this-> currentPlayer]; $ This-> display-> playerSentToPenaltyBox ($ currentPlayer); $ this-> inPenaltyBox [$ this-> currentPlayer] = true; $ This-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; return true;
Op 11 regels is deze methode niet enorm, maar zeker groot. Herinner je je het magische getal zeven plus minus twee onderzoeken? Het stelt dat ons brein tegelijkertijd kan denken aan 7 + -2 dingen. Dat wil zeggen, we hebben een beperkte capaciteit. Dus om een methode gemakkelijk en volledig te begrijpen, willen we dat de logica erin past binnen die reeks. Met een totaal van 11 regels en een inhoud van 9 regels is deze methode een soort van limiet. Je zou kunnen zeggen dat er eigenlijk een lege regel is en een andere met alleen een brace. Dat zou er 7 regels logica van maken.
Hoewel accolades en spaties een tekort aan ruimte hebben, hebben ze betekenis voor ons. Ze scheiden delen van de logica, ze hebben een betekenis, dus onze hersenen moeten ze verwerken. Ja, het is gemakkelijker vergeleken met een volledige vergelijkingslogica, maar toch.
Dat is de reden waarom ons doel voor lijnen van logica binnen een methode 4 regels is. Dat is onder het minimum van de bovenstaande theorie, dus zowel een genie als een middelmatige programmeur moet de methode kunnen begrijpen.
$ This-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0;
We hebben al een methode voor dit stuk code, dus we moeten het gebruiken.
function wrongAnswer () $ this-> display-> incorrectAnswer (); $ currentPlayer = $ this-> spelers [$ this-> currentPlayer]; $ This-> display-> playerSentToPenaltyBox ($ currentPlayer); $ this-> inPenaltyBox [$ this-> currentPlayer] = true; $ This-> selectNextPlayer (); geef waar terug;
Beter, maar moeten we vallen of doorgaan?
$ currentPlayer = $ this-> spelers [$ this-> currentPlayer]; $ This-> display-> playerSentToPenaltyBox ($ currentPlayer);
We kunnen de variabele uit deze twee regels inline zetten. $ This-> currentPlayer
is uiteraard de huidige speler terug, dus het is niet nodig om de logica te herhalen. We leren niets nieuws of abstracten niets nieuws door de lokale variabele te gebruiken.
function wrongAnswer () $ this-> display-> incorrectAnswer (); $ This-> display-> playerSentToPenaltyBox ($ this-> spelers [$ this-> currentPlayer]); $ this-> inPenaltyBox [$ this-> currentPlayer] = true; $ This-> selectNextPlayer (); geef waar terug;
We zijn tot 5 regels. Al het andere daarbinnen?
$ this-> inPenaltyBox [$ this-> currentPlayer] = true;
We kunnen de bovenstaande regel uitpakken in zijn eigen methode. Het zal helpen verklaren wat er gaande is en de logica isoleren over het feit dat de huidige speler op zijn eigen plek in de strafbank wordt gestuurd.
function wrongAnswer () $ this-> display-> incorrectAnswer (); $ This-> display-> playerSentToPenaltyBox ($ this-> spelers [$ this-> currentPlayer]); $ This-> sendCurrentPlayerToPenaltyBox (); $ This-> selectNextPlayer (); geef waar terug;
Nog steeds 5 regels, maar alle methodeaanroepen. De eerste twee tonen dingen. De volgende twee hebben betrekking op onze logica. De laatste regel geeft alleen waar terug. Ik zie geen manier om deze methode gemakkelijker te begrijpen te maken zonder ingewikkeldheid te introduceren door de extracties die we zouden kunnen maken, bijvoorbeeld door de twee weergavemethoden naar een privé-methode te extraheren. Als we dat zouden doen, waar zou die methode dan moeten gaan? In dit Spel
klasse, of in tonen
? Ik denk dat dit al een te complexe vraag is om het verdienen te worden beschouwd met betrekking tot de pure eenvoud van onze methode.
Laten we wat statistieken maken door deze geweldige tool te bekijken van de schrijver van PHPUnit https://github.com/sebastianbergmann/phploc.git
./ phploc ... / \ Legacy \ Code \ - \ Part \ Ref: \ De \ Golden \ Master / Source / trivia / php / phploc 2.1-gca70e70 van Sebastian Bergmann. Maat Lijnen van code (LOC) 232 Commentaar Lijnen van code (CLOC) 0 (0,00%) Niet-commentaar Lijnen van code (NCLOC) 232 (100,00%) Logische codelijnen (LLOC) 99 (42,67%) Klassen 88 (88,89 %) Gemiddelde Klasse Lengte 88 Minimale Klasse Lengte 88 Maximale Klasse Lengte 88 Gemiddelde Methode Lengte 7 Minimale Methode Lengte 1 Maximale Methode Lengte 17 Functies 1 (1,01%) Gemiddelde Functielengte 1 Niet in klassen of functies 10 (10,10%) Cyclomatic Complexity Gemiddelde Complexiteit per LLOC 0,26 Gemiddelde complexiteit per klasse 25,00 minimale klasse-complexiteit 25,00 maximale klasse-complexiteit 25,00 Gemiddelde complexiteit per methode 3,18 Minimale methode Complexiteit 1,00 Maximale methode Complexiteit 10,00 Afhankelijkheden Wereldwijde toegangen 0 Wereldwijde constanten 0 (0,00%) Wereldwijde variabelen 0 (0,00%) Supergloed Variabelen 0 (0,00%) Kenmerk Bereikbaarheid 115 Niet-statisch 115 (100,00%) Statisch 0 (0,00%) Methode Oproepen 21 Niet-statisch 21 (100,00%) Statisch 0 (0,00%) Structuur Naamruimten 0 Interfaces 0 Traits 0 Klassen 1 Abstract Klassen 0 (0,00%) Betonklassen 1 ( 100,00%) Methoden 11 Scope Niet-statische methoden 11 (100,00%) Statische methoden 0 (0,00%) Zichtbaarheid Openbare methoden 11 (100,00%) Niet-openbare methoden 0 (0,00%) Functies 1 Benoemde functies 1 (100,00%) Anonieme functies 0 (0,00%) Constanten 0 Globale constanten 0 (0,00%) Klasseconstanten 0 (0,00%)
./ phploc ... / \ Legacy \ Code \ - \ Part \ 11 \: \ The \ End \? / Source / trivia / php phploc 2.1-gca70e70 door Sebastian Bergmann herformuleren. Maat Lijnen van code (LOC) 371 Commentaar Lijnen van code (CLOC) 0 (0,00%) Niet-commentaar Lijnen van code (NCLOC) 371 (100,00%) Logische regels code (LLOC) 151 (40,70%) Klassen 145 (96.03 %) Gemiddelde Klasse Lengte 36 Minimale Klasse Lengte 8 Maximale Klasse Lengte 89 Gemiddelde Methode Lengte 2 Minimale Methode Lengte 1 Maximale Methode Lengte 14 Functies 0 (0,00%) Gemiddelde Functielengte 0 Niet in klassen of functies 6 (3,97%) Cyclomatic Complexity Gemiddelde Complexiteit per LLOC 0,15 Gemiddelde complexiteit per klasse 6,50 Minimumklasse-complexiteit 1,00 Maximale klasse-complexiteit 17,00 Gemiddelde complexiteit per methode 1,46 Minimale methode Complexiteit 1,00 Maximale methode Complexiteit 10,00 Afhankelijkheden Wereldwijde toegangen 0 Wereldwijde constanten 0 (0,00%) Wereldwijde variabelen 0 (0,00%) Supergloed Variabelen 0 (0,00%) Kenmerk Biedt toegang 96 Niet-statisch 94 (97,92%) Statisch 2 (2,08%) Methode Oproepen 74 Niet-statisch 74 (100,00%) Statisch 0 (0,00%) Structuur Naamruimten 0 Interfaces 1 Traits 0 Klassen 3 Abstract Klassen 0 (0,00%) Betonklassen 3 (100,00 %) Methoden 59 Scope Niet-statische methoden 59 (100,00%) Statische methoden 0 (0,00%) Zichtbaarheid Openbare methoden 35 (59,32%) Niet-openbare methoden 24 (40,68%) Functies 0 Benoemde functies 0 (0,00%) Anonieme functies 0 (0,00%) Constanten 3 Globale constanten 0 (0,00%) Klasse Constanten 3 (100,00%)
Brute gegevens zijn net zo goed als we kunnen begrijpen en analyseren.
Het aantal logische coderegels steeg aanzienlijk van 99 naar 151. Maar dit aantal zou je niet moeten laten denken dat onze code complexer werd. Dit is een natuurlijke tendens van een code die goed wordt bijgesteld, vanwege de toename van het aantal methoden en oproepen daarvoor.
Zodra we naar de gemiddelde lengte van de klas kijken, zien we een dramatische daling van de coderegels, van 88 tot 36.
En het is gewoon verbazingwekkend hoe de lengte van de methode daalde van gemiddeld zeven regels naar slechts twee regels code.
Hoewel het aantal lijnen een goede indicator is van het codevolume per meeteenheid, is de werkelijke winst gelegen in de analyse van de cyclomatische complexiteit. Telkens wanneer we een beslissing nemen in onze code, verhogen we de cyclomatische complexiteit. Als we ketenen als
uitspraken binnen de ander, neemt de cyclomatische complexiteit van die methode exponentieel toe. Onze voortdurende extracties leidden tot methoden met slechts één enkele beslissing erin, waardoor hun gemiddelde complexiteit per methode verminderde van 3,18 tot 1,00. Je kunt dit lezen als "onze refactored-methoden zijn 3,18 keer eenvoudiger dan de originele code". Op het niveau van de klas is de daling in complexiteit nog verbazingwekkender. Het ging van 25.00 tot 6.50.
Goed. Dat is het. Einde van de serie. Aarzel niet om uw mening te geven en als u denkt dat we een onderwerp voor refactoring hebben gemist, vraag dan om hen in onderstaande opmerkingen. Als ze interessant zijn, zal ik ze in extra delen van deze serie transformeren.
Bedankt voor je onverdeelde aandacht.