Alter Code. Hässlicher Code. Komplizierter Code Spaghetti-Code. Gibberish Blödsinn. In zwei Worten, Legacy Code. Diese Serie hilft Ihnen bei der Arbeit und beim Umgang damit.
In diesem siebten Kapitel unserer Refactoring-Tutorials werden wir eine andere Art von Refactoring durchführen. In den vergangenen Lektionen haben wir festgestellt, dass der Code für die Präsentation in unserem alten Code verstreut ist. Wir werden versuchen, den gesamten für die Präsentation relevanten Code zu identifizieren, und wir werden dann die notwendigen Schritte unternehmen, um ihn von der Geschäftslogik zu trennen.
Wann immer wir eine Änderung unseres Codes überarbeiten, richten wir uns dabei an einige Prinzipien. Diese Prinzipien und Regeln helfen uns, die Probleme zu erkennen, und in viele Fälle weisen sie uns in die richtige Richtung, um den Code zu verbessern.
SRP ist eines der SOLID-Prinzipien, über die wir in einem früheren Tutorial ausführlich gesprochen haben: SOLID: Teil 1 - Das Prinzip der einzigen Verantwortung. Wenn Sie in die Details eintauchen möchten, empfehle ich, dass Sie den Artikel lesen. Ansonsten lesen Sie einfach weiter und sehen eine Zusammenfassung des Prinzips der Einzelverantwortung unten.
SRP sagt grundsätzlich, dass jedes Modul, jede Klasse oder Methode eine einzige Verantwortung haben sollte. Eine solche Verantwortung wird als Achsen der Veränderung definiert. Eine Achse der Veränderung ist eine Richtung, ein Grund zum Ändern. SRP bedeutet also, dass unsere Klasse einen einzigen Grund haben sollte, sich zu ändern.
Das klingt zwar ziemlich einfach, aber wie definieren Sie einen "Grund für Veränderung"? Wir müssen es aus der Sicht der Benutzer unseres Codes, sowohl der normalen Endbenutzer als auch verschiedener Softwareabteilungen, betrachten. Diese Benutzer können als Akteure dargestellt werden. Wenn ein Schauspieler möchte, dass wir unseren Code ändern, ist dies ein Änderungsgrund, der die Änderungsachse bestimmt. Eine solche Anfrage sollte nur eines unserer Module, Klassen oder sogar Methoden betreffen, sofern dies möglich ist.
Ein sehr naheliegendes Beispiel wäre, wenn unser UI-Designteam von uns verlangt, alle Informationen bereitzustellen, die so dargestellt werden müssen, dass unsere Anwendung über eine HTML-Webseite anstatt über unsere aktuelle Befehlszeilenschnittstelle bereitgestellt werden kann.
So wie unser Code heute steht, könnten wir den gesamten Text an ein externes Smart-Objekt senden, das ihn in HTML umwandeln würde. Das kann aber nur funktionieren, weil HTML hauptsächlich textbasiert ist. Was ist, wenn unser UI-Team unser Trivia-Spiel als Desktop-Benutzeroberfläche mit Fenstern, Schaltflächen und verschiedenen Tischen präsentieren möchte??
Was ist, wenn unsere Benutzer das Spiel auf einem virtuellen Spielbrett sehen möchten, das als Stadt mit Straßen dargestellt wird, und die Spieler als Menschen, die um den Block gehen?
Wir könnten diese Leute als UI Actor identifizieren. Und wir müssen uns darüber im Klaren sein, dass nach heutigem Stand unseres Kodex unsere Trivia-Klasse und fast alle ihre Methoden geändert werden müssten. Klingt es logisch, das zu ändern? wasCorrectlyAnswered ()
Methode aus der Spiel
Klasse, wenn ich einen Tippfehler auf dem Bildschirm in einem Text korrigieren möchte oder wenn ich unsere Trivia-Software als virtuelles Spielbrett präsentieren möchte? Die Antwort ist absolut nicht.
Clean Architecture ist ein Konzept, das hauptsächlich von Robert C. Martin gefördert wird. Grundsätzlich heißt es, dass unsere Geschäftslogik klar definiert und durch Grenzen von anderen Modulen getrennt sein sollte, die nicht mit der Kernfunktionalität unseres Systems zusammenhängen. Dies führt zu entkoppeltem und in hohem Maße überprüfbarem Code.
Sie haben diese Zeichnung möglicherweise in meinen Tutorials und Kursen gesehen. Ich halte es für so wichtig, dass ich niemals Code schreibe oder über Code spreche, ohne darüber nachzudenken. Die Art und Weise, wie wir Code bei Syneto schreiben, und das Aussehen unseres Projekts haben sich grundlegend geändert. Vorher hatten wir unseren gesamten Code in einem MVC-Framework, mit Geschäftslogik in den Modellen. Dies war sowohl schwer zu verstehen als auch schwer zu testen. Außerdem war die Geschäftslogik vollständig an dieses spezifische MVC-Framework gekoppelt. Dies kann zwar bei kleinen Haustierprojekten funktionieren. Wenn es sich um ein großes Projekt handelt, von dem die Zukunft eines Unternehmens abhängt, einschließlich aller Mitarbeiter, müssen Sie aufhören, mit MVC-Frameworks herumzuspielen, und Sie müssen darüber nachdenken wie Sie Ihren Code organisieren Wenn Sie dies getan haben und es richtig gemacht haben, möchten Sie nie wieder zu der Art und Weise zurückkehren, in der Sie Ihre Projekte zuvor erstellt haben.
Wir haben bereits angefangen, unsere Geschäftslogik von der Präsentation in den vorherigen Tutorials zu trennen. Wir haben manchmal einige Druckfunktionen beobachtet und diese in private Methoden extrahiert Spiel
Klasse. Dies war unser Unterbewusstsein, das uns aufforderte, die Präsentation auf Methodenebene aus der Geschäftslogik herauszuschieben.
Nun ist es Zeit zu analysieren und zu beobachten.
Dies ist die Liste aller Variablen, Methoden und Funktionen von unserem Game.php
Datei. Die Dinge, die mit einem orangefarbenen "f" gekennzeichnet sind, sind Variablen. Das rote "m" bedeutet Methode. Wenn ein grünes Schloss folgt, ist es öffentlich. Wenn es von einem roten Schloss gefolgt wird, ist es privat. Und aus dieser Liste ist alles, was uns interessiert, der folgende Teil.
Alle ausgewählten Methoden haben etwas gemeinsam. Alle ihre Namen beginnen mit "Anzeige" ... etwas. Dies sind alles Methoden, die mit dem Drucken von Dingen auf dem Bildschirm zusammenhängen. Sie wurden alle in vorherigen Tutorials von uns identifiziert und nacheinander nahtlos extrahiert. Nun müssen wir feststellen, dass es sich um eine Gruppe von Methoden handelt, die zusammengehören. Eine Gruppe, die eine bestimmte Aufgabe ausführt, eine einzige Verantwortung erfüllt, zeigt Informationen auf dem Bildschirm an.
Am besten veranschaulicht und erklärt in Refactoring - Verbessern des Designs von vorhandenem Code von Martin Fowler. Die Grundidee des Refactoring der Extraktklasse ist, dass, nachdem Sie erkannt haben, dass Ihre Klasse funktioniert und dass dies von zwei Klassen durchgeführt werden sollte, Sie Maßnahmen ergreifen zwei Klassen. Es gibt spezifische Mechanismen dafür, wie im untenstehenden Zitat aus dem oben genannten Buch erläutert.
Leider gibt es zum Zeitpunkt der Erstellung dieses Artikels keine IDE in PHP, die eine Extraktionsklasse ausführen kann, indem Sie einfach eine Gruppe von Methoden auswählen und eine Option aus dem Menü anwenden.
Da es nie weh tut, die Mechanik der Prozesse zu kennen, die das Arbeiten mit Code implizieren, werden wir die oben genannten Schritte nacheinander durchführen und auf unseren Code anwenden.
Das wissen wir schon. Wir möchten die Darstellung von der Geschäftslogik abbrechen. Wir möchten ausgeben, Funktionen und anderen Code anzeigen und an einen anderen Ort verschieben.
Unsere erste Aktion ist das Erstellen einer neuen, leeren Klasse.
Klasse Anzeige
Ja. Das ist alles für jetzt. Und einen richtigen Namen dafür zu finden, war auch ziemlich einfach. Anzeige
ist das Wort, an dem alle unsere Methoden, an denen wir interessiert sind, beginnen. Es ist der gemeinsame Nenner ihrer Namen. Es ist ein sehr wirkungsvoller Vorschlag über ihr gemeinsames Verhalten, das Verhalten, nach dem wir unsere neue Klasse benannt haben.
Wenn Sie es vorziehen und Ihre Programmiersprache dies unterstützt, können Sie die neue Klasse in derselben Datei wie die alte Klasse erstellen. Oder Sie können eine neue Datei von Anfang an dafür erstellen. Ich persönlich habe keinen definitiven Grund gefunden, einen der beiden Wege zu gehen oder zu verbieten. Es liegt also an dir. Entscheiden Sie sich einfach und machen Sie weiter.
Dieser Schritt hört sich vielleicht nicht sehr vertraut an. Es bedeutet, eine Klassenvariable in der alten Klasse zu deklarieren und sie zu einer Instanz der neuen zu machen.
required_once __DIR__. '/Display.php'; Funktion echoln ($ string) echo $ string. "\ n"; class Game static $ minimumNumberOfPlayers = 2; statisch $ numberOfCoinsToWin = 6; private $ Anzeige; //… // Funktion __construct () //… // $ this-> display = new Display (); //… alle anderen Methoden… //
Einfach. Ist es nicht Im Spiel
's Konstruktor wir haben gerade eine private Klassenvariable initialisiert, die wir genauso benannt haben wie die neue Klasse, Anzeige
. Wir mussten auch die Display.php
Datei in unserem Game.php
Datei. Wir haben noch keinen Autoloader. Vielleicht stellen wir Ihnen in einem zukünftigen Tutorial eines vor, falls erforderlich.
Vergessen Sie nicht, Ihre Tests durchzuführen. In dieser Phase genügen Einzeltests, um sicherzustellen, dass der neu hinzugefügte Code keinen Tippfehler enthält.
Lass uns diese beiden Schritte auf einmal machen. Welche Felder können wir identifizieren, von denen ausgehen sollte Spiel
zu Anzeige
?
Durch bloßes Ansehen der Liste…
statisch $ minimumNumberOfPlayers = 2; statisch $ numberOfCoinsToWin = 6; private $ Anzeige; var $ spieler; var $ places; var $ geldbörsen; var $ inPenaltyBox; var $ popQuestions; var $ scienceQuestions; var $ sportsQuestions; var $ rockQuestions; var $ currentPlayer = 0; var $ isGettingOutOfPenaltyBox;
… Wir können keine Variablen / Felder finden, die dazu gehören müssen Anzeige
. Vielleicht werden einige rechtzeitig auftauchen. Also nichts für diesen Schritt zu tun. Und was die Tests angeht, haben wir sie schon vor einem Moment durchgeführt. Zeit, um weiterzuziehen.
Dies ist an sich ein weiterer Refactoring. Sie können es auf verschiedene Arten tun, und Sie finden eine schöne Definition in demselben Buch, über das wir zuvor gesprochen haben.
Wie oben erwähnt, sollten wir mit der untersten Methodenebene beginnen. Diejenigen, die keine anderen Methoden aufrufen. Stattdessen werden sie gerufen.
private Funktion displayPlayersNewLocation () echoln ($ this-> players [$ this-> currentPlayer]. "s neuer Speicherort ist". $ this-> places [$ this-> currentPlayer]);
displayPlayersNewLocation ()
scheint ein guter Kandidat zu sein. Lassen Sie uns analysieren, was es tut.
Wir können sehen, dass es keine anderen Methoden aufruft Spiel
. Stattdessen werden drei Felder verwendet: Spieler
, currentPlayer
, und setzt
. Diese können in zwei oder drei Parameter umgewandelt werden. Bisher ziemlich nett. Aber was ist mit Echoln ()
, der einzige Funktionsaufruf in unserer Methode? Wo ist das Echoln ()
kommen von?
Es ist an der Spitze unseres Game.php
Datei außerhalb der Spiel
Klasse selbst.
Funktion echoln ($ string) echo $ string. "\ n";
Es tut definitiv, was es sagt. Echos ein String mit einer neuen Zeile am Ende. Und das ist reine Präsentation. Es sollte in die gehen Anzeige
Klasse. Also lass es uns dort kopieren.
class Anzeige Funktion echoln ($ string) echo $ string. "\ n";
Führen Sie unsere Tests erneut durch. Wir können den Goldenen Meister deaktiviert lassen, bis wir alle Präsentationen für die neue Ausgabe extrahiert haben Anzeige
Klasse. Wenn Sie der Meinung sind, dass die Ausgabe möglicherweise geändert wurde, führen Sie auch die Golden Master-Tests erneut aus. An diesem Punkt wird durch die Tests bestätigt, dass wir keine Tippfehler oder doppelten Funktionsdeklarationen oder andere Fehler in dieser Hinsicht eingeführt haben, indem wir die Funktion an ihre neue Stelle kopieren.
Jetzt geh und lösch Echoln ()
von dem Game.php
Datei, führen Sie unsere Tests durch und erwarten Sie einen Fehler.
PHP Schwerwiegender Fehler: Rufen Sie die undefinierte Funktion echoln () in /… /Game.php in Zeile 55 auf
Nett! Unser Unit-Test ist hier eine große Hilfe. Es läuft sehr schnell und sagt uns die genaue Position des Problems. Wir gehen zur Zeile 55.
Aussehen! Da ist ein Echoln ()
dort anrufen Tests lügen nie. Beheben Sie es durch Aufrufen $ this-> dipslay-> echoln ()
stattdessen.
Funktion add ($ playerName) array_push ($ this-> players, $ playerName); $ this-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "wurde hinzugefügt"); echoln ("Sie sind Spielernummer". Anzahl ($ this-> Spieler)); wahr zurückgeben;
Das führt dazu, dass der Test die Zeile 55 durchläuft und bei 56 fehlschlägt.
PHP Fataler Fehler: Aufruf der undefined-Funktion echoln () in /… /Game.php in Zeile 56
Und die Lösung liegt auf der Hand. Dies ist ein langwieriger Prozess, aber es ist zumindest einfach.
Funktion add ($ playerName) array_push ($ this-> players, $ playerName); $ this-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "wurde hinzugefügt"); $ this-> display-> echoln ("Sie sind Spielernummer". count ($ this-> players)); wahr zurückgeben;
Damit sind die ersten drei Tests tatsächlich bestanden und auch der nächste Ort, an dem sich ein Anruf befindet, den wir ändern sollten.
PHP Schwerwiegender Fehler: Rufen Sie die undefinierte Funktion echoln () in /… /Game.php in Zeile 169 auf
Das ist in falsche Antwort()
.
function wrongAnswer () echoln ("Frage wurde falsch beantwortet"); echoln ($ this-> players [$ this-> currentPlayer]. "wurde zur Strafbox geschickt"); $ this-> inPenaltyBox [$ this-> currentPlayer] = true; $ this-> currentPlayer ++; if ($ this-> sollteResetCurrentPlayer ()) $ this-> currentPlayer = 0; return true;
Durch Fixieren dieser beiden Aufrufe wird der Fehler auf Zeile 228 heruntergereicht.
private Funktion displayCurrentPlayer () echoln ($ this-> players [$ this-> currentPlayer]. "ist der aktuelle Spieler");
EIN Anzeige
Methode! Vielleicht sollte dies unsere erste Methode sein, um uns zu bewegen. Wir versuchen hier eine kleine testgetriebene Entwicklung (TDD) durchzuführen. Und wenn die Tests fehlschlagen, dürfen wir keinen weiteren Produktionscode schreiben, der für den Test nicht unbedingt erforderlich ist. Und alles, was dazu gehört, ist nur die Veränderung der Echoln ()
ruft an, bis alle Unit-Tests bestanden sind.
Sie können diesen Vorgang beschleunigen, indem Sie die Such- und Ersetzungsfunktion Ihrer IDE oder Ihres Editors verwenden. Führen Sie einfach alle Tests aus, einschließlich des Goldenen Meisters, nachdem Sie mit diesem Ersatz fertig sind. Unsere Unit-Tests decken nicht den gesamten Code ab Echoln ()
Anrufe.
Wir können mit dem ersten Kandidaten beginnen, displayCurrentPlayer ()
. Kopieren Sie es in Anzeige
und führen Sie Ihre Tests durch.
Dann machen Sie es öffentlich auf Anzeige
und in displayCurrentPlayer ()
im Spiel
Anruf $ this-> display-> displayCurrentPlayer ()
anstatt direkt zu tun Echoln ()
. Zum Schluss führen Sie Ihre Tests durch.
Sie werden scheitern. Indem wir die Änderung auf diese Weise durchführen, haben wir sichergestellt, dass wir nur eine Sache geändert haben, die fehlschlagen könnte. Alle anderen Methoden rufen noch auf Spiel
's displayCurrentPlayer ()
. Und dies ist die Delegation an Anzeige
.
Undefinierte Eigenschaft: Display :: $ display
Unsere Methode verwendet Klassenfelder. Diese müssen Parameter für die Funktion sein. Wenn Sie Ihren Testfehlern folgen, sollten Sie in etwa so etwas wie enden Spiel
.
private Funktion displayCurrentPlayer () $ this-> display-> displayCurrentPlayer ($ this-> players [$ this-> currentPlayer]);
Und das in Anzeige
.
Funktion displayCurrentPlayer ($ currentPlayer) $ this-> echoln ($ currentPlayer. "ist der aktuelle Spieler");
Anrufe ersetzen Spiel
auf die lokale Methode mit der in Anzeige
. Vergessen Sie auch nicht, die Parameter um eine Stufe nach oben zu verschieben.
private Funktion displayStatusAfterRoll ($ rollsNumber) $ this-> display-> displayCurrentPlayer ($ this-> players [$ this-> currentPlayer]); $ this-> displayRolledNumber ($rollNumber);
Entfernen Sie schließlich die nicht verwendete Methode aus Spiel
. Führen Sie Ihre Tests durch, um sicherzustellen, dass alles in Ordnung ist.
Dies ist ein langwieriger Prozess. Sie können es etwas beschleunigen, indem Sie mehrere Methoden gleichzeitig verwenden und alles tun, was Ihre IDE zum Verschieben und Ersetzen von Code zwischen Klassen tun kann. Der Rest der Methoden bleibt für Sie eine Übung oder Sie können in diesem Kapitel mehr über die Highlights des Prozesses erfahren. Der fertige Code, der diesem Artikel beigefügt ist, enthält das vollständige Anzeige
Klasse.
Ah, und vergessen Sie nicht den Code, der in den "display" -Methoden noch nicht extrahiert wurde Spiel
. Sie können diese verschieben Echoln ()
Anrufe zur direkten Anzeige. Unser Ziel ist es, nicht anzurufen Echoln ()
überhaupt von Spiel
, und machen es privat auf Anzeige
.
Nach nur einer halben Stunde Arbeit, Anzeige
beginnt gut auszusehen.
Alle Anzeigemethoden aus Spiel
sind in Anzeige
. Jetzt können wir alle suchen Echoln
Anrufe, die in geblieben sind Spiel
und bewege sie auch. Tests sind natürlich bestanden.
Aber sobald wir vor dem stehen Frage stellen()
Als Methode erkennen wir, dass es auch nur Präsentationscode ist. Und das bedeutet, dass auch die verschiedenen Frage-Arrays gehen sollten Anzeige
.
class Display private $ popQuestions = []; private $ scienceQuestions = []; private $ sportsQuestions = []; private $ rockQuestions = []; function __construct () $ this-> initializeQuestions (); //… // private Funktion initializeQuestions () $ categorySize = 50; für ($ i = 0; $ i < $categorySize; $i++) array_push($this->popQuestions, "Pop-Frage". $ i); array_push ($ this-> scienceQuestions, ("Science Question". $ i)); array_push ($ this-> sportsQuestions, ("Sports Question". $ i)); array_push ($ this-> rockQuestions, "Rock Question". $ i);
Das sieht passend aus. Fragen sind nur Zeichenketten, wir stellen sie vor und passen hier besser. Wenn wir diese Art von Refactoring durchführen, ist dies auch eine gute Gelegenheit, den neu verschobenen Code zu refactorieren. Wir haben Anfangswerte in der Deklaration von Feldern definiert, wir haben sie auch als privat deklariert und eine Methode mit dem Code erstellt, die ausgeführt werden muss, damit sie nicht nur im Konstruktor verbleibt. Stattdessen ist es am unteren Rand der Klasse versteckt.
Nachdem wir die nächsten beiden Methoden extrahiert haben, stellen wir fest, dass es schöner ist, sie im Inneren des Systems zu benennen Anzeige
Klasse ohne das Präfix "Anzeige".
function correctAnswer () $ this-> echoln ("Antwort war richtig !!!!"); function playerCoins ($ currentPlayer, $ playerCoins) $ this-> echoln ($ currentPlayer. "hat jetzt". $ playerCoins. "Gold Coins.");
Wenn unsere Tests grün sind und gut abschneiden, können wir unsere Methoden jetzt umgestalten und umbenennen. PHPStorm kann mit den Umbenennungs-Refactorings recht gut umgehen. Es werden Funktionsaufrufe in umbenannt Spiel
entsprechend. Dann gibt es diesen Code.
Schauen Sie sich die ausgewählte Zeile, 119, genau an. Das sieht genauso aus wie unsere kürzlich extrahierte Methode in Anzeige
.
function correctAnswer () $ this-> echoln ("Antwort war richtig !!!!");
Wenn wir es jedoch anstelle des Codes aufrufen, schlägt der Test fehl. Ja! Es gibt einen Tippfehler. Und nein! Sie sollten es nicht reparieren. Wir refaktorieren. Wir müssen die Funktionalität unverändert lassen, auch wenn ein Fehler vorliegt.
Der Rest der Methode stellt keine besondere Herausforderung dar.
Nun, da alle Präsentationsfunktionen vorhanden sind Anzeige
, Wir müssen die Methoden überprüfen und nur die Methoden öffentlich machen Spiel
. Dieser Schritt ist auch durch das Interface-Segregationsprinzip motiviert, über das wir in einem früheren Tutorial gesprochen haben.
In unserem Fall ist der einfachste Weg, um herauszufinden, welche Methoden öffentlich oder privat sein müssen, die einzelnen Methoden jeweils einzeln privat zu machen, die Tests auszuführen und wenn sie fehlschlagen, auf public zurückzugreifen.
Da goldene Master-Tests langsam ablaufen, können wir uns auch auf unsere IDE verlassen, um den Prozess zu beschleunigen. PHPStorm ist intelligent genug, um herauszufinden, ob eine Methode nicht verwendet wird. Wenn wir eine Methode privat machen und sie plötzlich nicht mehr verwendet wird, ist klar, dass sie außerhalb von verwendet wurde Anzeige
und muss öffentlich bleiben.
Schließlich können wir uns neu organisieren Anzeige
Damit stehen private Methoden am Ende der Klasse.
Nun ist der letzte Schritt des Refraktorierungsprinzips der Extraktklasse in unserem Fall irrelevant. Damit ist das Tutorial abgeschlossen, aber die Serie ist noch nicht abgeschlossen. Bleiben Sie dran für unseren nächsten Artikel, in dem wir weiter auf eine saubere Architektur hinarbeiten und Abhängigkeiten umkehren.