Oude code. Lelijke code. Ingewikkelde code. Spaghetti-code. Gibberistische onzin. In twee woorden, Oude code. Dit is een serie die u zal helpen om ermee te werken en ermee om te gaan.
In dit zevende hoofdstuk van onze refactoring-tutorials zullen we een ander soort refactoring doen. We hebben in de afgelopen lessen vastgesteld dat er presentatiegerelateerde code is verspreid over onze oude code. We zullen proberen alle aan de presentatie gerelateerde code te identificeren die we kunnen en we zullen dan de nodige stappen ondernemen om het te scheiden van bedrijfslogica.
Wanneer we een refactoring-wijziging aanbrengen in onze code, doen we dat op basis van een aantal principes. Deze principes en regels helpen ons de problemen te identificeren en in veel gevallen wijzen ze ons in de goede richting om de code te verbeteren.
SRP is een van de SOLID-principes waar we uitgebreid over gesproken hebben in een eerdere tutorial: SOLID: Deel 1 - Het principe van één verantwoordelijkheid. Als je in de details wilt duiken, raad ik je aan het artikel te lezen, ga anders gewoon verder met lezen en bekijk een samenvatting van het principe voor één verantwoordelijkheid hieronder.
SRP zegt in feite dat elke module, klasse of methode één enkele verantwoordelijkheid zou moeten hebben. Een dergelijke verantwoordelijkheid wordt gedefinieerd als een as van verandering. Een as van verandering is een richting, een reden om te veranderen. SRP betekent dus dat onze klasse één reden moet hebben om te veranderen.
Hoewel dat vrij eenvoudig klinkt, hoe definieer je een "reden voor verandering"? We moeten het bedenken vanuit het oogpunt van de gebruikers van onze code, zowel gewone eindgebruikers als verschillende softwareafdelingen. Deze gebruikers kunnen worden weergegeven als acteurs. Wanneer een acteur wil dat wij onze code wijzigen, is dat een reden voor verandering die een veranderingsas bepaalt. Een dergelijk verzoek zou slechts één van onze modules, klassen of zelfs methoden moeten beïnvloeden, indien mogelijk.
Een heel voor de hand liggend voorbeeld zou zijn als ons UI-ontwerpteam ons zou verplichten alle informatie te verstrekken die moet worden gepresenteerd op een manier dat onze applicatie kan worden afgeleverd via een HTML-webpagina, in plaats van onze huidige opdrachtregelinterface.
Zoals onze code vandaag de dag staat, kunnen we gewoon alle tekst naar een extern slim object verzenden dat het in HTML zou transformeren. Maar dat werkt misschien alleen omdat HTML voornamelijk op tekst is gebaseerd. Wat als onze UI-team onze trivia-game wil presenteren als een desktop UI, met vensters, knoppen en verschillende tabellen?
Wat als onze gebruikers de game op een virtueel bord willen zien, weergegeven als een stad met straten en de spelers als mensen die rondlopen in het blok?
We zouden deze mensen kunnen identificeren als de UI-acteur. En we moeten ons realiseren dat, zoals onze code vandaag de dag is, we onze trivia-klasse en bijna alle methoden moeten aanpassen. Klinkt het logisch om de wasCorrectlyAnswered ()
methode van de Spel
klas als ik een typefout op het scherm in een tekst wil repareren, of als ik onze trivia-software wil presenteren als een virtueel bord? Nee. Het antwoord is absoluut niet.
Schone architectuur is een concept dat vooral wordt gepromoot door Robert C. Martin. In principe staat er dat onze bedrijfslogica goed gedefinieerd en duidelijk gescheiden moet zijn door grenzen van andere modules die geen verband houden met de kernfunctionaliteit van ons systeem. Dit leidt tot ontkoppelde en zeer testbare code.
Je hebt deze tekening mogelijk gezien tijdens mijn tutorials en cursussen. Ik vind het zo belangrijk dat ik nooit code schrijf of over code praat zonder erover na te denken. Het heeft de manier waarop we code schrijven op Syneto en hoe ons project eruitziet totaal veranderd. Voordat we al onze code in een MVC-raamwerk hadden, met bedrijfslogica in de modellen. Dit was zowel moeilijk te begrijpen als moeilijk te testen. Bovendien was de bedrijfslogica volledig gekoppeld aan dat specifieke MVC-raamwerk. Hoewel dit mogelijk werkt met kleine pet-projecten, als het gaat om een groot project waarvan de toekomst van een bedrijf afhangt, inclusief al zijn werknemers, moet je stoppen met spelen met MVC-frameworks, en moet je gaan nadenken over hoe je je code kunt organiseren. Als je dit eenmaal hebt gedaan en het goed hebt gedaan, wil je nooit meer terugkeren naar de manieren waarop je je projecten eerder hebt ontworpen.
We zijn al begonnen onze bedrijfslogica te scheiden van de presentatie in de vorige paar zelfstudies. Soms observeerden we sommige afdrukfuncties en haalden ze eruit in privé-methoden Spel
klasse. Dit was onze onbewuste geest die ons vertelde om de presentatie uit bedrijfslogica op het niveau van de methode te duwen.
Nu is het tijd om te analyseren en te observeren.
Dit is de lijst met alle variabelen, methoden en functies van onze Game.php
het dossier. De dingen gemarkeerd met een oranje "f" zijn variabelen. De rode "m" betekent methode. Als het wordt gevolgd door een groen slot, is het openbaar. Het wordt gevolgd door rood slot, het is privé. En van die lijst is het enige waar we in geïnteresseerd zijn.
Alle geselecteerde methoden hebben iets gemeen. Al hun namen beginnen met "weergave" ... iets. Het zijn allemaal methoden die gerelateerd zijn aan het afdrukken van dingen op het scherm. Ze werden allemaal door ons geïdentificeerd in eerdere tutorials en naadloos één voor één uitgewerkt. Nu moeten we constateren dat ze een groep methoden zijn die bij elkaar horen. Een groep die één specifiek ding doet, aan één enkele verantwoordelijkheid voldoet, geeft informatie weer op het scherm.
Het beste geïllustreerd en uitgelegd in Refactoring - Het ontwerp van de bestaande code verbeteren door Martin Fowler, het basisidee van de Refactoring van de Extract Class is dat nadat je je realiseert dat je klas werkt en dat het door twee klassen gedaan moet worden, je acties moet ondernemen om twee klassen. Er zijn specifieke mechanismen, zoals uitgelegd in het onderstaande citaat uit het bovengenoemde boek.
Helaas, op het moment dat dit artikel wordt geschreven, is er geen IDE in PHP die een extractklasse kan doen door een groep methoden te selecteren en een optie uit het menu toe te passen.
Omdat het nooit pijn doet om de werking van de processen te kennen die impliceren dat u met code werkt, zullen we bovenstaande stappen een voor een uitvoeren en toepassen op onze code.
We weten dit al. We willen de presentatie van bedrijfslogica doorbreken. We willen uitvoer, weergave van functies en andere code uitvoeren en deze ergens anders verplaatsen.
Onze eerste actie is om een nieuwe, lege klas te maken.
klasse Display
Yep. Dat is het voor nu. En het vinden van een juiste naam was ook vrij eenvoudig. tonen
is het woord waar al onze methoden in beginnen. Het is de gemeenschappelijke noemer van hun namen. Het is een zeer krachtige suggestie over hun gemeenschappelijk gedrag, het gedrag waarna we onze nieuwe klasse hebben genoemd.
Als je dat wilt en je programmeertaal het ondersteunt, PHP doet, kun je de nieuwe klasse binnen hetzelfde bestand maken als het oude. Of u kunt vanaf het begin een nieuw bestand maken. Ik vond persoonlijk geen definitieve reden om in beide richtingen te gaan of om een van de twee manieren te verbieden. Dus het is aan jou. Beslis gewoon en ga door.
Deze stap klinkt misschien niet erg vertrouwd. Wat het betekent is om een klassenvariabele in de oude klasse te declareren en er een instantie van de nieuwe te maken.
require_once __DIR__. '/Display.php'; functie echoln ($ string) echo $ string. "\ N"; class Game static $ minimumNumberOfPlayers = 2; static $ numberOfCoinsToWin = 6; privé $ weergave; // ... // function __construct () // ... // $ this-> display = new Display (); // ... alle andere methoden ... //
Eenvoudig. Is het niet? In Spel
We hebben net een private class-variabele geïnitialiseerd die we hetzelfde hebben genoemd als de nieuwe klasse, tonen
. We moesten ook de Display.php
bestand in onze Game.php
het dossier. We hebben nog geen autoloader. Misschien zullen we in een toekomstige zelfstudie zo nodig een introduceren.
En zoals gewoonlijk, vergeet niet om uw tests uit te voeren. Eenheidstests zijn in dit stadium voldoende, alleen om zeker te zijn dat er geen typfouten in de nieuw toegevoegde code zitten.
Laten we deze twee stappen tegelijk nemen. Welke velden kunnen we identificeren waar we naartoe moeten gaan? Spel
naar tonen
?
Door alleen maar naar de lijst te kijken ...
static $ minimumNumberOfPlayers = 2; static $ numberOfCoinsToWin = 6; privé $ weergave; var $ spelers; var $ places; var $ portemonnees; var $ inPenaltyBox; var $ popQuestions; var $ scienceQuestions; var $ sportsQuestions; var $ rockQuestions; var $ currentPlayer = 0; var $ isGettingOutOfPenaltyBox;
... we kunnen geen variabele / veld vinden waartoe we moeten behoren tonen
. Misschien zal er iets in de tijd verschijnen. Dus niets te doen voor deze stap. En over de tests hebben we ze al eerder gereden. Tijd om verder te gaan.
Dit is op zichzelf een andere refactoring. Je kunt het op verschillende manieren doen en je zult een mooie definitie ervan vinden in hetzelfde boek waar we eerder over gesproken hebben.
Zoals hierboven vermeld, zouden we moeten beginnen met het laagste niveau van methoden. Degenen die andere methoden niet noemen. In plaats daarvan worden ze genoemd.
private function displayPlayersNewLocation () echoln ($ this-> players [$ this-> currentPlayer]. "'s nieuwe locatie is". $ this-> plaatst [$ this-> currentPlayer]);
displayPlayersNewLocation ()
lijkt een goede kandidaat te zijn. Laten we analyseren wat het doet.
We kunnen zien dat andere methoden niet worden aangeroepen Spel
. In plaats daarvan gebruikt het drie velden: spelers
, currentPlayer
, en plaatsen
. Die kunnen in twee of drie parameters veranderen. Tot nu toe best leuk. Maar hoe zit het met echoln ()
, de enige functie in onze methode? Waar is dit echoln ()
afkomstig uit?
Het staat aan de top van ons Game.php
bestand, buiten de Spel
klasse zelf.
functie echoln ($ string) echo $ string. "\ N";
Het doet absoluut wat het zegt. Echoes een string met een nieuwe regel aan het einde. En dit is pure presentatie. Het zou in de moeten gaan tonen
klasse. Dus laten we het daarheen kopiëren.
klasse Display functie echoln ($ string) echo $ string. "\ N";
Voer onze tests opnieuw uit. We kunnen de gouden meester uitgeschakeld houden totdat we klaar zijn met het uitpakken van alle presentatie naar de nieuwe tonen
klasse. Als u van mening bent dat de uitvoer mogelijk is gewijzigd, kunt u de gouden hoofdtests ook op elk gewenst moment opnieuw uitvoeren. Op dit punt bevestigen de tests dat we geen typefouten of duplicaatfunctie-verklaringen hebben ingevoerd, of andere fouten, door de functie naar de nieuwe locatie te kopiëren.
Ga nu en verwijder echoln ()
van de Game.php
bestand, voer onze tests uit en verwacht dat ze falen.
PHP Fatale fout: oproep naar ongedefinieerde functie echoln () in / ... /Game.php op regel 55
Leuk! Onze unit-test is hier van grote hulp. Het loopt erg snel en het vertelt ons de exacte positie van het probleem. We gaan naar lijn 55.
Kijken! Er is een echoln ()
Bel daar. Tests liegen nooit. Laten we het oplossen door te bellen $ This-> dipslay-> echoln ()
in plaats daarvan.
functie add ($ playerName) array_push ($ this-> spelers, $ playerName); $ This-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "is toegevoegd"); echoln ("Ze zijn speler nummer". count ($ dit-> spelers)); geef waar terug;
Dat maakt de test door lijn 55 en mislukt op 56.
PHP Fatale fout: oproep naar ongedefinieerde functie echoln () in / ... / Game.php op regel 56
En de oplossing ligt voor de hand. Dit is een moeizaam proces, maar het is op zijn minst eenvoudig.
functie add ($ playerName) array_push ($ this-> spelers, $ playerName); $ This-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "is toegevoegd"); $ this-> display-> echoln ("Ze zijn speler nummer". count ($ dit-> spelers)); geef waar terug;
Dat maakt eigenlijk de eerste drie tests over en vertelt ons ook de volgende plaats waar een oproep is die we moeten veranderen.
PHP Fatale fout: oproep naar ongedefinieerde functie echoln () in / ... /Game.php op regel 169
Dat is in wrongAnswer ()
.
function wrongAnswer () echoln ("Vraag is niet goed beantwoord"); echoln ($ this-> spelers [$ this-> currentPlayer]. "is naar het strafveld gestuurd"); $ this-> inPenaltyBox [$ this-> currentPlayer] = true; $ This-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; return true;
Door deze twee oproepen te verhelpen, wordt onze fout verlaagd naar regel 228.
persoonlijke functie displayCurrentPlayer () echoln ($ this-> spelers [$ this-> currentPlayer]. "is de huidige speler");
EEN tonen
methode! Misschien moet dit onze eerste methode zijn om te bewegen. We proberen hier een kleine testgestuurde ontwikkeling (TDD) uit te voeren. En als de tests mislukken, mogen we geen productiecode meer schrijven die niet absoluut noodzakelijk is om de test te laten slagen. En het enige dat dat inhoudt, is gewoon het veranderen van de echoln ()
roept totdat al onze unit-tests passeren.
U kunt dit proces versnellen door gebruik te maken van de zoek- en vervangfunctionaliteit van uw IDE's of editor. Voer alle tests uit, inclusief de gouden meester, nadat u klaar bent met deze vervanging. Onze unit tests dekken niet alle code, en alle echoln ()
calls.
We kunnen beginnen met de eerste kandidaat, displayCurrentPlayer ()
. Kopieer het naar tonen
en voer je testen uit.
Maak het vervolgens openbaar tonen
en in displayCurrentPlayer ()
in Spel
telefoontje $ This-> display-> displayCurrentPlayer ()
in plaats van rechtstreeks een echoln ()
. Voer tot slot uw tests uit.
Ze zullen falen. Maar door de wijziging op deze manier te doen, hebben we ervoor gezorgd dat we slechts één ding hebben gewijzigd dat zou kunnen mislukken. Alle andere methoden bellen nog steeds Spel
's displayCurrentPlayer ()
. En dit is de delegatie naar tonen
.
Ongedefinieerde eigenschap: Display :: $ display
Onze methode maakt gebruik van klassenvelden. Deze moeten parameters voor de functie worden gemaakt. Als je je testfouten volgt, zou je eindigen met zoiets als in Spel
.
persoonlijke functie displayCurrentPlayer () $ this-> display-> displayCurrentPlayer ($ this-> players [$ this-> currentPlayer]);
En dit in tonen
.
function displayCurrentPlayer ($ currentPlayer) $ this-> echoln ($ currentPlayer. "is de huidige speler");
Vervang oproepen door Spel
naar de lokale methode met die erin tonen
. Vergeet ook niet om de parameters één niveau omhoog te verplaatsen.
persoonlijke functie displayStatusAfterRoll ($ rolledNumber) $ this-> display-> displayCurrentPlayer ($ this-> players [$ this-> currentPlayer]); $ This-> displayRolledNumber ($ rolledNumber);
Verwijder ten slotte de ongebruikte methode uit Spel
. En voer uw tests uit om te controleren of alles in orde is.
Dit is een moeizaam proces. Je kunt het een klein beetje versnellen door verschillende methoden tegelijkertijd te gebruiken en alles te gebruiken wat je IDE kan doen om code tussen klassen te verplaatsen en te vervangen. De rest van de methoden blijft een oefening voor u of u kunt meer lezen in dit hoofdstuk met de hoogtepunten van het proces. De voltooide code die aan dit artikel is gekoppeld, bevat de volledige code tonen
klasse.
Ah, en vergeet de code die nog niet is geëxtraheerd in de "weergave" -methoden erin Spel
. Je mag die verplaatsen echoln ()
oproepen om direct weer te geven. Ons doel is niet te bellen echoln ()
helemaal van Spel
, en maak het privé op tonen
.
Na slechts een half uur of zo van werk, tonen
begint er goed uit te zien.
Alle weergavemethoden van Spel
zijn in tonen
. Nu kunnen we alles zoeken echoln
oproepen die binnen bleven Spel
en verplaats ze ook. Testen zijn voorbij, natuurlijk.
Maar zodra we geconfronteerd worden met de askQuestion ()
methode, we realiseren ons dat het ook gewoon een presentatiecode is. En dat betekent dat ook de verschillende vraag-arrays zouden moeten gaan tonen
.
class Geef private $ popQuestions = [] weer; privé $ scienceQuestions = []; privé $ sportsQuestions = []; private $ rockQuestions = []; function __construct () $ this-> initializeQuestions (); // ... // persoonlijke functie initializeQuestions () $ categorySize = 50; voor ($ i = 0; $ i < $categorySize; $i++) array_push($this->popQuestions, "Pop Question". $ I); array_push ($ this-> scienceQuestions, ("Science Question". $ i)); array_push ($ this-> sportsQuestions, ("Sports Question". $ i)); array_push ($ this-> rockQuestions, "Rock Question". $ i);
Dat lijkt passend. Vragen zijn slechts snaren, we presenteren ze en ze passen hier beter. Wanneer we dit soort refactoring doen, is dit ook een goede gelegenheid om de nieuw verplaatste code te refactoren. We hebben de beginwaarden gedefinieerd in de declaratie van velden, we hebben ze ook privé gemaakt en een methode gemaakt met de code die moet worden uitgevoerd, zodat deze niet alleen in de constructor blijft hangen. In plaats daarvan is het verborgen aan de onderkant van de klas, uit de weg.
Na het uitpakken van de volgende twee methoden, realiseren we ons dat het leuker is om ze te noemen, binnen de tonen
klasse, zonder het voorvoegsel "weergave".
function correctAnswer () $ this-> echoln ("Answer was correct !!!!"); function playerCoins ($ currentPlayer, $ playerCoins) $ this-> echoln ($ currentPlayer. "heeft nu". $ playerCoins. "Gouden munten.");
Met onze tests groen en goed bezig, kunnen we onze methoden nu refactoren en hernoemen. PHPStorm kan de hernoeming van refactorings vrij goed aan. Het zal functieaanroepen hernoemen Spel
overeenkomstig. Dan is er dit stuk code.
Kijk zorgvuldig naar de geselecteerde regel, 119. Dat lijkt precies op onze recent uitgehaalde methode in tonen
.
function correctAnswer () $ this-> echoln ("Answer was correct !!!!");
Maar als we het in plaats van de code noemen, mislukt de test. Ja! Er is een typfout. En nee! Je zou het niet moeten repareren. We zijn aan het refactoren. We moeten de functionaliteit ongewijzigd houden, zelfs als er een bug is.
De rest van de methode vertegenwoordigt geen speciale uitdaging.
Nu is alle presentatiefunctionaliteit aanwezig tonen
, we moeten de methoden bekijken en alleen die openbaar houden die in gebruik zijn Spel
. Deze stap wordt ook gemotiveerd door het Interface Segregation Principle waar we het in een eerdere tutorial over hadden.
In ons geval is de eenvoudigste manier om erachter te komen welke methoden openbaar of privé moeten zijn, om ze allemaal privé te maken, de tests uit te voeren en als ze niet terugkeren naar het openbare domein.
Omdat golden master-tests traag verlopen, kunnen we ook op onze IDE vertrouwen om ons te helpen het proces te versnellen. PHPStorm is slim genoeg om erachter te komen of een methode ongebruikt is. Als we een methode privé maken en deze wordt plotseling ongebruikt, is het duidelijk dat deze buiten werd gebruikt tonen
en moet openbaar blijven.
Eindelijk kunnen we reorganiseren tonen
zodat privémethoden aan het einde van de les zijn.
Nu is de laatste stap van het Extract Class Refactoring-principe in ons geval niet relevant. Dus hiermee is de tutorial afgerond, maar hiermee wordt de serie nog niet afgesloten. Houd ons in de gaten voor ons volgende artikel, waar we verder zullen werken aan een schone architectuur en de afhankelijkheden omkeren.