Cod vechi. Cod urât. Cod complicat. Codul spaghetti. Gândurile proaste. Cu două cuvinte, Codul vechi. Aceasta este o serie care vă va ajuta să lucrați și să vă ocupați de aceasta.
În acest capitol al șaptelea al tutorialelor noastre de refactorizare, vom face un alt tip de refactorizare. Am observat în lecțiile anterioare că există un cod de prezentare împrăștiat peste codul nostru vechi. Vom încerca să identificăm tot codul de prezentare pe care îl putem folosi și vom lua apoi pașii necesari pentru al separa de logica de afaceri.
Ori de câte ori facem o schimbare de refactorizare a codului nostru, noi o facem prin ghidat de niște principii. Aceste principii și reguli ne ajută să identificăm problemele și, în multe cazuri, ne indică în direcția corectă pentru a face codul mai bun.
SRP este unul dintre principiile SOLID despre care am vorbit în detaliu într-un tutorial anterior: SOLID: Partea 1 - Principiul responsabilității unice. Dacă doriți să vă aruncați cu capul în detaliu, vă recomand să citiți articolul, în caz contrar continuați să citiți și să vedeți un rezumat al Principiului responsabilității unice de mai jos.
SRP spune în esență că orice modul, clasă sau metodă ar trebui să aibă o singură responsabilitate. O astfel de responsabilitate este definită ca o axă a schimbării. O axă a schimbării este o direcție, un motiv de schimbare. Deci, SRP înseamnă că clasa noastră ar trebui să aibă un singur motiv de schimbare.
În timp ce sună destul de simplu, cum definești un "motiv pentru schimbare"? Trebuie să ne gândim la acest lucru din punctul de vedere al utilizatorilor codului nostru, atât utilizatorii finali obișnuiți, cât și diverse departamente software. Acești utilizatori pot fi reprezentați ca actori. Când un actor dorește să ne schimbăm codul, acesta este un motiv de schimbare care determină o axă de schimbare. O astfel de solicitare ar trebui să afecteze numai unul dintre modulele noastre, clase sau chiar metode, dacă este posibil.
Un exemplu foarte evident ar fi dacă echipa noastră de design UI ar cere să furnizăm toate informațiile care trebuie prezentate într-un mod în care aplicația noastră poate fi difuzată pe o pagină web HTML, în locul interfeței noastre curente de linie de comandă.
Așa cum codul nostru este astăzi, am putea să trimitem întregul text către un obiect inteligent extern care să-l transforme în HTML. Dar acest lucru poate funcționa numai pentru că HTML este în mare parte bazat pe text. Ce se întâmplă dacă echipa noastră de interfață cu utilizatorul dorește să vă prezinte jocul trivia ca interfață desktop, cu ferestre, butoane și diverse mese?
Ce se întâmplă dacă utilizatorii noștri doresc să vadă jocul pe un tablou de joc virtual reprezentat ca un oraș cu străzi și jucătorii ca oameni care se plimbă în jurul blocului?
Am putea identifica aceste persoane ca Actor al UI. Și trebuie să ne dăm seama că, așa cum este astăzi codul nostru, ar trebui să modificăm clasa trivia și aproape toate metodele ei. Se pare logic să se modifice wasCorrectlyAnswered ()
metoda de la Joc
clasa dacă vreau să reparăm o tastă pe ecran într-un text sau dacă vreau să prezint software-ul nostru de trivia ca o tablă de joc virtuală? Nu. Răspunsul nu este absolut.
Clean Architecture este un concept promovat în cea mai mare parte de Robert C. Martin. Practic se spune că logica noastră de afaceri ar trebui să fie bine definită și clar separată de limite de la alte module care nu sunt legate de funcționalitatea de bază a sistemului nostru. Acest lucru conduce la un cod decuplat și foarte testabil.
Este posibil să fi văzut acest desen în toate tutorialele și cursurile mele. Consider că este atât de important, încât nu scriu niciodată cod sau să vorbesc despre cod, fără să mă gândesc la asta. A schimbat complet modul în care scriem codul la Syneto și cum arată proiectul nostru. Înainte de a avea codul nostru într-un cadru MVC, cu logica de afaceri în Modele. Acest lucru a fost atât de greu de înțeles și de greu de testat. Plus logica de afaceri a fost cuplată în totalitate cu cadrul specific MVC. În timp ce face acest lucru poate funcționa cu mici proiecte de animale de companie, atunci când vine vorba de un proiect amplu pe care depinde viitorul unei companii, inclusiv toți angajații săi, trebuie să nu mai jucați cu cadrele MVC și trebuie să începeți să vă gândiți cum să vă organizați codul. Odată ce ați făcut acest lucru și veți reuși, nu veți mai dori niciodată să vă întoarceți la modul în care ați proiectat înainte proiectele.
Am început deja să separăm logica noastră de afaceri de prezentarea din ultimele câteva tutoriale. Am observat uneori unele funcții de imprimare și le-am extras în metode private în același timp Joc
clasă. Aceasta a fost mintea noastră inconștientă, care ne-a spus să împingem prezentarea din logica de afaceri la nivel de metodă.
Acum este timpul să analizăm și să observăm.
Aceasta este lista tuturor variabilelor, metodelor și funcțiilor de la noi Game.php
fişier. Lucrurile marcate cu un portocaliu "f" sunt variabile. Modul roșu "m" înseamnă metoda. Dacă este urmată de o închidere verde, este publică. Acesta este urmat de blocarea roșie este privată. Și din lista aceea, tot ceea ce ne interesează este următoarea parte.
Toate metodele selectate au ceva în comun. Toate numele lor încep cu "afișare" ... ceva. Sunt toate metodele legate de tipărirea lucrurilor pe ecran. Toate acestea au fost identificate de noi în tutorialele anterioare și extrase fără probleme, unul câte unul. Acum trebuie să observăm că sunt un grup de metode care aparțin împreună. Un grup care face un singur lucru, îndeplinește o singură responsabilitate, afișează informații pe ecran.
Cel mai bine exemplificat și explicat în Refactoring - Îmbunătățirea designului codului existent de Martin Fowler, ideea de bază a Refactoringului de clasă Extract este că, după ce vă dați seama că clasa dvs. funcționează și că trebuie făcută de două clase, luați măsuri pentru a face două clase. Există mecanisme specifice pentru aceasta, după cum se explică în citatul de mai jos din cartea menționată mai sus.
Din păcate, în momentul în care scrie acest articol, nu există nici un IDE în PHP care să poată face o clasă de extras doar prin selectarea unui grup de metode și aplicarea unei opțiuni din meniu.
Deoarece niciodată nu doare să cunoască mecanica proceselor care implică lucrul cu codul, vom face pașii de mai sus, unul câte unul și îi vom aplica codului nostru.
Știm deja asta. Vrem să dezvăluiți prezentarea din logica de afaceri. Vrem să luăm rezultate, să afișăm funcții și alte coduri și să le mutăm în altă parte.
Prima noastră acțiune este crearea unei clase noi, goale.
afișare clasă
Da. Asta este tot pentru acum. Și găsirea unui nume adecvat a fost, de asemenea, destul de ușoară. Afişa
este cuvântul toate metodele noastre suntem interesați să începem cu. Este numitorul comun al numelor lor. Este o sugestie foarte puternică despre comportamentul lor comun, comportamentul după care am numit noua noastră clasă.
Dacă preferați și limba dvs. de programare o acceptă, PHP face, puteți crea noua clasă în interiorul aceluiași fișier ca cel vechi. Sau, puteți crea un fișier nou pentru el de la început. Nu am găsit personal nici un motiv definitiv pentru a merge în vreun fel sau pentru a interzice oricare dintre cele două căi. Așa că este până la tine. Doar decideți și mergeți mai departe.
Este posibil ca acest pas să nu fie foarte familiarizat. Ce înseamnă să declarați o variabilă de clasă în clasa veche și să o transformați într-o instanță a celei noi.
requ_once __DIR__. '/Display.php'; funcția echoln ($ string) echo $ string. "\ N"; joc de clasă static $ minimumNumberOfPlayers = 2; static $ numberOfCoinsToWin = 6; display privat $; // ... // funcția __construct () // ... // $ this-> display = new Afișare (); // ... toate celelalte metode ... //
Simplu. Nu-i așa? În Joc
constructorul am inițializat o variabilă de clasă privată pe care am numit-o la fel ca noua clasă, afişa
. De asemenea, trebuia să includem Display.php
fișier în nostru Game.php
fişier. Nu avem încă un autoloader. Poate într-un viitor tutorial vom introduce unul dacă este necesar.
Și, ca de obicei, nu uitați să efectuați testele. Testele de unitate sunt suficiente în acest stadiu, doar pentru a vă asigura că nu există coduri în codul nou adăugat.
Să facem acești doi pași odată. În ce domenii putem identifica care ar trebui să treacă Joc
la Afişa
?
Privind doar lista ...
static $ minimumNumberOfPlayers = 2; static $ numberOfCoinsToWin = 6; display privat $; var $ jucători; var $ places; var $ purses; var $ inPenaltyBox; var $ popQuestions; var $ scienceQuestions; var $ sportsObiecte; var $ rockQuestions; var $ curentPlayer = 0; var $ isGettingOutOfPenaltyBox;
... nu putem găsi nici o variabilă / câmp care trebuie să aparțină Afişa
. Poate unii vor apărea la timp. Deci, nimic de făcut pentru acest pas. Și despre teste, le-am fugit deja cu o clipă. Este timpul să mergem mai departe.
Acest lucru este în sine un alt refactor. Puteți face acest lucru în mai multe moduri și veți găsi o definiție frumoasă a acestuia în aceeași carte despre care am vorbit mai devreme.
După cum sa menționat mai sus, ar trebui să începem cu cel mai scăzut nivel de metodă. Cei care nu apelează la alte metode. În schimb, ei sunt chemați.
funcția privată displayPlayersNewLocation () echoln ($ this-> players [$ this-> currentPlayer]. "noua locație este" $ this-> places [$ this-> currentPlayer]);
displayPlayersNewLocation ()
pare a fi un bun candidat. Să analizăm ce face.
Putem vedea că nu numesc alte metode Joc
. În schimb, utilizează trei câmpuri: jucători
, currentPlayer
, și locuri
. Acestea se pot transforma în doi sau trei parametri. Până destul de drăguț. Dar ce zici echoln ()
, Singura funcție de apel în metoda noastră? Unde este asta echoln ()
provin de la?
Este în partea de sus a noastră Game.php
fișier, în afara Joc
clasa însăși.
funcția echoln ($ string) echo $ string. "\ N";
Cu siguranță, face ceea ce spune. Echoeste un șir cu o nouă linie la sfârșit. Și aceasta este o prezentare pură. Ar trebui să intre în Afişa
clasă. Deci, să o copiem acolo.
clasa Display funcția echoln ($ string) echo $ string. "\ N";
Executați din nou testele. Putem păstra maestrul de aur dezactivat până când terminăm extragerea întregii prezentări către noul Afişa
clasă. În orice moment, dacă simțiți că producția poate fi modificată, reluați și testele de bază ale aurului. În acest moment, testele vor atesta faptul că nu am introdus declarații de tipărire sau duplicate de funcții sau alte erori pentru aceasta, prin copierea funcției în noul său loc.
Acum, du-te și șterge echoln ()
de la Game.php
fișierul, executați testele noastre și așteptați ca acestea să eșueze.
PHP Eroare fatală: Apelați la funcția nedefinită echoln () din /.../Game.php pe linia 55
Frumos! Testul nostru de unitate este de mare ajutor aici. Acesta ruleaza foarte repede si ne spune exacta pozitie a problemei. Mergem la linia 55.
Uite! Este o echoln ()
sunați acolo. Testele nu mint niciodată. Să rezolvăm apelul $ This-> dipslay-> echoln ()
in schimb.
adăugați funcția ($ playerName) array_push ($ this-> players, $ playerName); $ This-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "a fost adăugat"); echoln ("Aceștia sunt numărul de jucători".) ($ this-> players)); return true;
Acest lucru face ca testul să treacă prin linia 55 și să nu reușească la 56.
PHP Eroare fatală: Apel la funcția nedefinită echoln () în / ... /Game.php pe linia 56
Și soluția este evidentă. Acesta este un proces obositor, dar este cel puțin ușor.
adăugați funcția ($ playerName) array_push ($ this-> players, $ playerName); $ This-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "a fost adăugat"); $ this-> display-> echoln ("Acestea sunt numărul de jucători". count ($ this-> players)); return true;
Asta face ca primele trei teste să treacă și, de asemenea, ne spune locul următor în care există un apel pe care ar trebui să îl schimbăm.
PHP Eroare fatală: Apel la funcția nedefinită echoln () în / ... /Game.php pe linia 169
Asta e răspuns greșit()
.
function errorAnswer () echoln ("Întrebarea a fost respinsă incorect"); echoln ($ this-> players [$ this-> currentPlayer] "a fost trimis la caseta de penalizare"); $ this-> înPenaltyBox [$ this-> currentPlayer] = adevărat; $ This-> currentPlayer ++; dacă ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; return true;
Remedierea acestor două apeluri ne împinge la linia 228.
funcția privată displayCurrentPlayer () echoln ($ this-> players [$ this-> currentPlayer] "este playerul curent");
A afişa
metodă! Poate ar trebui să fie prima noastră metodă de mișcare. Încercăm să facem un mic test dezvoltat (TDD) aici. Iar când testele nu reușesc, nu avem voie să scriem niciun cod de producție care să nu fie absolut necesar pentru a trece testul. Și tot ce implică este doar schimbarea echoln ()
solicită să treacă toate încercările noastre.
Puteți accelera acest proces utilizând funcția de căutare și înlocuire a IDE-ului sau a editorului. Rulați toate testele, inclusiv maestrul de aur după ce ați terminat cu acest înlocuitor. Testele unitare nu acoperă întregul cod și toate echoln ()
apeluri.
Putem începe cu primul candidat, displayCurrentPlayer ()
. Copiați-l Afişa
și executați testele.
Apoi, faceți publice Afişa
si in displayCurrentPlayer ()
în Joc
apel $ This-> Display-> displayCurrentPlayer ()
în loc să o faci direct echoln ()
. În cele din urmă, executați testele.
Ei vor eșua. Dar făcând schimbarea în acest fel, am asigurat că am schimbat un singur lucru care ar putea eșua. Toate celelalte metode sunt încă de apel Joc
„s displayCurrentPlayer ()
. Și acesta este cel care deleagă Afişa
.
Proprietate nedefinită: Display :: $ display
Metoda noastră folosește câmpuri de clasă. Acestea trebuie să fie parametrii funcției. Dacă urmați erorile de testare, ar trebui să vă încheiați cu ceva de genul asta în Joc
.
funcția privată displayCurrentPlayer () $ this-> display-> displayCurrentPlayer ($ this-> jucători [$ this-> currentPlayer]);
Și asta în Afişa
.
funcția displayCurrentPlayer ($ currentPlayer) $ this-> echoln ($ currentPlayer. este playerul curent);
Înlocuiți apelurile Joc
la metoda locală cu cea din Afişa
. Nu uitați să mutați parametrii până la un nivel, de asemenea.
funcția privată displayStatusAfterRoll ($ rolledNumber) $ this-> display-> displayCurrentPlayer ($ this-> jucători [$ this-> currentPlayer]); $ This-> displayRolledNumber ($ rolledNumber);
În cele din urmă, eliminați metoda neutilizată Joc
. Și executați testele pentru a vă asigura că totul este în regulă.
Acesta este un proces obositor. Puteți accelera puțin mai mult, luând mai multe metode simultan și utilizând ceea ce poate face IDE-ul dvs. pentru a vă ajuta să mutați și să înlocuiți codul între clase. Restul metodelor vor rămâne un exercițiu pentru dvs. sau puteți citi mai mult acest capitol cu cele mai importante momente ale procesului. Codul final atașat la acest articol va conține completă Afişa
clasă.
Ah, și nu uitați de codul care nu este încă extras în metode de "afișare" din interior Joc
. Poți să-i muți pe aceștia echoln ()
solicită afișarea directă. Scopul nostru este să nu sunăm echoln ()
la toate de la Joc
, și să o facă privată Afişa
.
După doar o jumătate de oră de muncă, Afişa
începe să arate bine.
Toate metodele de afișare de la Joc
sunt în Afişa
. Acum putem căuta pe toate echoln
apeluri care au rămas în Joc
și le mutați și pe ele. Testele trec, desigur.
Dar, de îndată ce ne confruntăm cu cere întrebare()
, ne dăm seama că este doar un cod de prezentare. Și asta înseamnă că diferitele rețele de întrebări ar trebui să meargă, de asemenea Afişa
.
clasa Afișați private $ popQuestions = []; private $ scienceQuestions = []; private $ sportsQuestions = []; private $ rockQuestions = []; funcția __construct () $ this-> initializeQuestions (); // // // funcția privată initializeQuestions () $ categorySize = 50; pentru ($ i = 0; $ i < $categorySize; $i++) array_push($this->popQuestions, "Pop Question". $ I); array_push ($ this-> scienceQuestions, ("Science Question". $ i)); array_push ($ this-> sportsQuestions, ("Întrebare sportivă". $ i)); array_push ($ this-> rockQuestions, "Întrebare Rock" $ i);
Asta pare potrivit. Întrebările sunt doar șiruri de caractere, le prezentăm și se potrivesc mai bine aici. Când facem acest tip de refactorizare, este, de asemenea, o bună ocazie de a refactoriza codul nou mutat. Am definit valori inițiale în declararea câmpurilor, le-am făcut și private și am creat o metodă cu codul care trebuie executat astfel încât să nu rămână doar în constructor. În schimb, este ascuns în partea de jos a clasei, din drum.
După extragerea următoarelor două metode, ne dăm seama că este mai bine să le numim, în interiorul Afişa
fără prefixul "afișare".
funcția correctAnswer () $ this-> echoln ("Răspunsul a fost corect !!!!"); funcția playerCoins ($ currentPlayer, $ playerCoins) $ this-> echoln ($ currentPlayer. "acum are" $ playerCoins. "Coins de aur");
Cu testele noastre verzi și de a face bine, putem acum refactor și redenumiți metodele noastre. PHPStorm se poate descurca destul de bine cu refacerea numelui. Acesta va redenumi apeluri de funcții în Joc
în consecinţă. Apoi este o bucată de cod.
Uită-te cu atenție la linia selectată, 119. Aceasta arată la fel ca metoda noastră recent extrasă în Afişa
.
funcția correctAnswer () $ this-> echoln ("Răspunsul a fost corect !!!!");
Dar dacă îl numim în loc de cod, testul va eșua. Da! Există o greșeală. Si nu! Nu ar trebui să rezolvi asta. Refacționăm. Trebuie să păstrăm funcționalitatea neschimbată, chiar dacă există o eroare.
Restul metodei nu reprezintă o cădere specială.
Acum, că toate funcțiile de prezentare se află în Afişa
, trebuie să revizuim metodele și să păstrăm publice numai cele utilizate în Joc
. Acest pas este, de asemenea, motivat de principiul de separare a interfeței pe care am vorbit-o într-un tutorial din trecut.
În cazul nostru, cel mai simplu mod de a înțelege ce metode trebuie să fie publice sau private este de a face pe fiecare să fie privat la un moment dat, să execute testele și, dacă nu reușesc, să revină la public.
Deoarece testele de bază ale aurului rulează lent, ne putem baza și pe IDE-ul nostru pentru a ne ajuta să accelerăm procesul. PHPStorm este suficient de inteligent pentru a afla dacă o metodă este neutilizată. Dacă facem o metodă privată și devine brusc neutilizată, este clar că a fost folosită în afara acesteia Afişa
și trebuie să rămână publică.
În cele din urmă, putem să ne reorganizăm Afişa
astfel încât metodele private să fie la sfârșitul clasei.
Acum, ultima etapă a principiului de refacționare a clasei Extract este irelevantă în cazul nostru. Prin urmare, acest lucru încheie tutorialul, dar acest lucru nu încheie încă seria. Rămâneți atenți la următorul articol în care vom continua să lucrăm spre o arhitectură curată și la dependențe inverse.