Ancien code. Code laid. Code compliqué. Code spaghetti. Gibberish absurdité. En deux mots, Code hérité. C’est une série qui vous aidera à travailler et à vous en occuper.
Dans ce septième chapitre de nos tutoriels de refactoring, nous allons faire un type de refactoring différent. Nous avons observé dans les leçons précédentes que du code lié à la présentation était dispersé dans notre code hérité. Nous allons essayer d'identifier tout le code lié à la présentation que nous pouvons et nous prendrons ensuite les mesures nécessaires pour le séparer de la logique métier..
Chaque fois que nous apportons une modification de refactoring à notre code, nous le faisons sur la base de certains principes. Ces principes et règles nous aident à identifier les problèmes et dans de nombreux cas, ils nous orientent dans la bonne direction afin d'améliorer le code..
SRP est l'un des principes SOLID dont nous avons parlé en détail dans un précédent tutoriel: SOLID: Partie 1 - Le principe de responsabilité unique. Si vous souhaitez vous plonger dans les détails, je vous recommande de lire l'article, sinon continuez simplement à lire et consultez un résumé du principe de responsabilité unique ci-dessous..
SRP dit fondamentalement que n'importe quel module, classe ou méthode devrait avoir une responsabilité unique. Une telle responsabilité est définie comme un axe de changement. Un axe de changement est une direction, une raison de changer. Donc, SRP signifie que notre classe devrait avoir une seule raison de changer.
Bien que cela semble assez simple, comment définissez-vous une "raison du changement"? Nous devons y penser du point de vue des utilisateurs de notre code, qu’il s’agisse d’utilisateurs finaux ordinaires ou de divers départements de logiciels. Ces utilisateurs peuvent être représentés en tant qu'acteurs. Lorsqu'un acteur veut que nous changions notre code, c'est une raison du changement qui détermine un axe de changement. Une telle requête ne devrait affecter que si possible l'un de nos modules, classes ou même méthodes.
Un exemple très évident serait si notre équipe de conception d'interface utilisateur nous demandait de fournir toutes les informations devant être présentées de manière à ce que notre application puisse être diffusée via une page Web HTML, au lieu de notre interface de ligne de commande actuelle..
Dans l'état actuel de notre code, nous pourrions simplement envoyer tout le texte à un objet intelligent externe qui le transformerait en HTML. Mais cela ne peut fonctionner que parce que HTML est principalement basé sur du texte. Et si notre équipe d'interface utilisateur voulait présenter notre jeu-questionnaire sous la forme d'une interface utilisateur de bureau, avec des fenêtres, des boutons et diverses tables?
Et si nos utilisateurs voulaient voir le jeu sur un plateau de jeu virtuel représenté comme une ville avec des rues, et les joueurs comme des passants?
Nous pourrions identifier ces personnes comme l'acteur de l'assurance-chômage. Et nous devons comprendre que, dans l'état actuel de notre code, nous aurions besoin de modifier notre classe de questionnaires et presque toutes ses méthodes. Cela vous semble-t-il logique de modifier le wasCorrectlyAnswered ()
méthode de la Jeu
classe si je veux corriger une faute de frappe à l’écran dans un texte, ou si je veux présenter notre logiciel trivia comme un plateau de jeu virtuel? Non, la réponse est absolument pas.
L'architecture propre est un concept promu principalement par Robert C. Martin. En gros, il est dit que notre logique métier doit être bien définie et clairement séparée par des limites d'autres modules non liés aux fonctionnalités de base de notre système. Cela conduit à un code découplé et hautement testable.
Vous avez peut-être vu ce dessin tout au long de mes tutoriels et de mes cours. Je considère qu'il est si important de ne jamais écrire de code ni parler de code sans y penser. Cela a totalement changé la façon dont nous écrivons du code chez Syneto et l’apparence de notre projet. Auparavant, nous avions tous notre code dans un framework MVC, avec une logique métier dans les modèles. C'était à la fois difficile à comprendre et difficile à tester. De plus, la logique métier était totalement couplée à ce framework MVC spécifique. Bien que cela puisse fonctionner avec de petits projets pour animaux de compagnie, quand il s’agit d’un grand projet dont dépend l’avenir d’une entreprise, y compris tous ses employés, vous devez cesser de jouer avec les frameworks MVC et commencer à penser à comment organiser votre code. Une fois que vous avez fait cela et que vous avez bien fait les choses, vous ne voudrez plus jamais revenir à la façon dont vous avez architecturé vos projets auparavant..
Nous avons déjà commencé à séparer notre logique métier de la présentation dans les quelques tutoriels précédents. Nous avons parfois observé certaines fonctions d’impression et les avons extraites dans des méthodes privées de la même manière. Jeu
classe. C'était notre esprit inconscient qui nous disait de pousser la présentation hors de la logique métier au niveau de la méthode..
Maintenant il est temps d'analyser et d'observer.
Ceci est la liste de toutes les variables, méthodes et fonctions de notre Game.php
fichier. Les choses marquées d'un "f" orange sont des variables. Le "m" rouge signifie méthode. S'il est suivi d'un cadenas vert, il est public. S'il est suivi d'un cadenas rouge, il est privé. Et de cette liste, tout ce qui nous intéresse, est la partie suivante.
Toutes les méthodes sélectionnées ont quelque chose en commun. Tous leurs noms commencent par "display"… quelque chose. Ce sont toutes des méthodes liées à l'impression d'éléments à l'écran. Ils ont tous été identifiés par nous dans des tutoriels précédents et extraits de manière transparente, un à la fois. Nous devons maintenant observer qu’il s’agit d’un groupe de méthodes qui vont de pair. Un groupe qui fait une chose spécifique, assume une seule responsabilité, affiche des informations à l'écran.
Le refactoring - Amélioration de la conception du code existant par Martin Fowler illustre parfaitement l'idée de refactoring de la classe d'extraction, c'est qu'après avoir réalisé que votre classe fonctionne et qu'elle doit être effectuée par deux classes, vous devez prendre des mesures deux classes. Il existe des mécanismes spécifiques à cela, comme expliqué dans la citation ci-dessous du livre mentionné ci-dessus.
Malheureusement, au moment de la rédaction de cet article, il n'y a pas d'EDI en PHP capable de faire une classe d'extraction en sélectionnant simplement un groupe de méthodes et en appliquant une option dans le menu..
Comme il n’est jamais inutile de connaître la mécanique des processus qui impliquent de travailler avec du code, nous allons suivre les étapes ci-dessus, une par une, et les appliquer à notre code..
Nous savons déjà cela. Nous voulons briser la présentation de la logique métier. Nous voulons prendre la sortie, afficher des fonctions et d'autres codes, et les déplacer ailleurs.
Notre première action est de créer une nouvelle classe vide.
affichage de la classe
Oui. C'est tout pour le moment. Et trouver un nom propre était aussi assez facile. Afficher
est le mot toutes nos méthodes qui nous intéressent commencent. C'est le dénominateur commun de leurs noms. C'est une suggestion très puissante sur leur comportement commun, le comportement après lequel nous avons appelé notre nouvelle classe.
Si vous préférez et que votre langage de programmation le supporte, c'est le cas de PHP, vous pouvez créer la nouvelle classe dans le même fichier que l'ancien. Ou, vous pouvez créer un nouveau fichier depuis le début. Personnellement, je n’ai trouvé aucune raison définitive d’aller dans un sens ou d’interdire l’un ou l’autre des moyens. C'est à vous de répondre. Juste décider et aller de l'avant.
Cette étape peut ne pas sembler très familière. Cela signifie que vous devez déclarer une variable de classe dans l'ancienne classe et en faire une instance de la nouvelle..
require_once __DIR__. '/Display.php'; fonction echoln ($ string) echo $ string. "\ n"; class Game static $ minimumNumberOfPlayers = 2; static $ numberOfCoinsToWin = 6; affichage privé $; //… // function __construct () //… // $ this-> display = new Display (); //… toutes les autres méthodes… //
Simple. N'est-ce pas? Dans Jeu
Le constructeur de nous vient d'initialiser une variable de classe privée que nous avons nommée la même que la nouvelle classe, afficher
. Nous devions également inclure le Display.php
déposer dans notre Game.php
fichier. Nous n'avons pas encore d'autoloader. Peut-être que dans un prochain tutoriel, nous en présenterons un si nécessaire.
Et comme d'habitude, n'oubliez pas de lancer vos tests. Les tests unitaires sont suffisants à ce stade, juste pour vous assurer qu'il n'y a pas de fautes de frappe dans le code nouvellement ajouté.
Faisons ces deux étapes à la fois. Quels champs pouvons-nous identifier qui devrait aller de Jeu
à Afficher
?
En regardant simplement la liste…
static $ minimumNumberOfPlayers = 2; static $ numberOfCoinsToWin = 6; affichage privé $; var $ players; var $ places; var $ sacs à main; var $ inPenaltyBox; var $ popQuestions; var $ scienceQuestions; var $ sportsQuestions; var $ rockQuestions; var $ currentPlayer = 0; var $ isGettingOutOfPenaltyBox;
… Nous ne trouvons aucune variable / champ qui doit appartenir à Afficher
. Peut-être que certains émergeront dans le temps. Donc, rien à faire pour cette étape. Et à propos des tests, nous les avons déjà exécutés il y a un instant. Il est temps de passer à autre chose.
C'est en soi une autre refactorisation. Vous pouvez le faire de plusieurs façons et vous en trouverez une belle définition dans le même livre que celui dont nous avons parlé plus tôt..
Comme mentionné ci-dessus, nous devrions commencer par le plus bas niveau de méthodes. Ceux qui n'appellent pas d'autres méthodes. Au lieu de cela, ils sont appelés.
fonction privée displayPlayersNewLocation () echoln ($ this-> players [$ this-> currentPlayer]. "nouvel emplacement est". $ this-> places [$ this-> currentPlayer]);
displayPlayersNewLocation ()
semble être un bon candidat. Analysons ce qu'il fait.
Nous pouvons voir qu'il n'appelle pas d'autres méthodes sur Jeu
. Au lieu de cela, il utilise trois champs: joueurs
, currentPlayer
, et des endroits
. Ceux-ci peuvent se transformer en deux ou trois paramètres. Jusqu'ici assez sympa. Mais qu'en est-il echoln ()
, le seul appel de fonction dans notre méthode? Où est-ce echoln ()
provenir de?
C'est au sommet de notre Game.php
fichier, en dehors du Jeu
classe elle-même.
fonction echoln ($ string) echo $ string. "\ n";
Il fait vraiment ce qu'il dit. Écho d'une chaîne avec une nouvelle ligne à la fin. Et ceci est une pure présentation. Il devrait aller dans le Afficher
classe. Alors copions-le là-bas.
classe Affichage fonction echoln ($ string) echo $ string. "\ n";
Exécutez à nouveau nos tests. Nous pouvons laisser le Golden Master désactivé jusqu'à ce que nous ayons fini d'extraire toute la présentation vers le nouveau Afficher
classe. À tout moment, si vous pensez que la sortie a peut-être été modifiée, réexécutez également les tests Golden Master. À ce stade, les tests attestent que nous n’avons pas introduit de fautes de frappe ni de déclarations de fonction en double, ni d’autres erreurs, en copiant la fonction à son nouvel emplacement..
Maintenant, allez et supprimez echoln ()
du Game.php
déposer, exécuter nos tests et s'attendre à ce qu'ils échouent.
Erreur irrécupérable PHP: Appel de la fonction indéfinie echoln () dans /… /Game.php à la ligne 55
Agréable! Notre test unitaire est d'une grande aide ici. Il court très vite et nous indique la position exacte du problème. Nous allons à la ligne 55.
Regardez! Il y a un echoln ()
appelez là. Les tests ne mentent jamais. Réglons-le en appelant $ this-> dipslay-> echoln ()
au lieu.
fonction add ($ playerName) array_push ($ this-> players, $ playerName); $ this-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "a été ajouté"); echoln ("Ils sont le numéro du joueur". count ($ this-> players)); retourne vrai;
Cela fait passer le test à la ligne 55 et échouer à la 56.
Erreur irrécupérable PHP: Appel de la fonction indéfinie echoln () dans /… /Game.php à la ligne 56
Et la solution est évidente. C'est un processus fastidieux, mais au moins facile.
fonction add ($ playerName) array_push ($ this-> players, $ playerName); $ this-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "a été ajouté"); $ this-> display-> echoln ("Ce sont des numéros de joueurs". count ($ this-> players)); retourne vrai;
Cela fait passer les trois premiers tests et nous indique également le prochain endroit où il y a un appel que nous devrions changer.
Erreur irrécupérable PHP: Appel de la fonction non définie echoln () dans /… /Game.php à la ligne 169
C'est dedans mauvaise réponse()
.
function wrongAnswer () echoln ("La question n'a pas été répondue correctement"); echoln ($ this-> joueurs [$ this-> currentPlayer]. "a été envoyé au banc des pénalités"); $ this-> inPenaltyBox [$ this-> currentPlayer] = true; $ this-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0; return true;
En corrigeant ces deux appels, notre erreur est renvoyée à la ligne 228.
fonction privée displayCurrentPlayer () echoln ($ this-> players [$ this-> currentPlayer]. "est le joueur actuel");
UNE afficher
méthode! Peut-être que cela devrait être notre première méthode de déplacement. Nous essayons de faire un peu de développement piloté par les tests (TDD) ici. Et lorsque les tests échouent, nous ne sommes plus autorisés à écrire du code de production qui n'est pas absolument nécessaire pour réussir le test. Et tout ce que cela implique est simplement de changer le echoln ()
appelle jusqu'à ce que tous nos tests unitaires réussissent.
Vous pouvez accélérer ce processus en utilisant les fonctionnalités de recherche et de remplacement de votre IDE ou de votre éditeur. Il suffit de lancer tous les tests, y compris le maître d’or, une fois que vous avez terminé ce remplacement. Nos tests unitaires ne couvrent pas tout le code, et tous les echoln ()
appels.
Nous pouvons commencer avec notre premier candidat, displayCurrentPlayer ()
. Copiez-le sur Afficher
et lancez vos tests.
Ensuite, rendez-le public sur Afficher
et en displayCurrentPlayer ()
dans Jeu
appel $ this-> display-> displayCurrentPlayer ()
au lieu de faire directement un echoln ()
. Enfin, lancez vos tests.
Ils vont échouer. Mais en procédant de la sorte, nous nous sommes assurés de ne changer qu’une chose qui pourrait échouer. Toutes les autres méthodes appellent encore Jeu
de displayCurrentPlayer ()
. Et c’est celui qui délègue à Afficher
.
Propriété non définie: Display :: $ display
Notre méthode utilise des champs de classe. Ceux-ci doivent être des paramètres de la fonction. Si vous suivez vos erreurs de test, vous devriez vous retrouver avec quelque chose comme ça dans Jeu
.
fonction privée displayCurrentPlayer () $ this-> display-> displayCurrentPlayer ($ this-> players [$ this-> currentPlayer]);
Et ceci dans Afficher
.
function displayCurrentPlayer ($ currentPlayer) $ this-> echoln ($ currentPlayer. "est le lecteur actuel");
Remplacer les appels dans Jeu
à la méthode locale avec celle de Afficher
. N'oubliez pas de déplacer les paramètres d'un niveau, également.
fonction privée displayStatusAfterRoll ($ rolledNumber) $ this-> display-> displayCurrentPlayer ($ this-> players [$ this-> currentPlayer]); $ this-> displayRolledNumber ($ rolledNumber);
Enfin, supprimez la méthode inutilisée de Jeu
. Et lancez vos tests pour vous assurer que tout va bien.
C'est un processus fastidieux. Vous pouvez accélérer un peu en prenant plusieurs méthodes à la fois et en utilisant ce que votre IDE peut faire pour vous aider à déplacer et à remplacer le code entre les classes. Le reste des méthodes restera un exercice pour vous ou vous pourrez en lire plus sur ce chapitre avec les points saillants du processus. Le code fini attaché à cet article contiendra le code complet Afficher
classe.
Ah, et n'oubliez pas le code qui n'est pas encore extrait dans les méthodes "display" à l'intérieur Jeu
. Vous pouvez déplacer ceux echoln ()
appels à afficher directement. Notre objectif est de ne pas appeler echoln ()
du tout de Jeu
, et le rendre privé sur Afficher
.
Après seulement une demi-heure de travail, Afficher
commence à bien paraître.
Toutes les méthodes d'affichage de Jeu
sont dans Afficher
. Maintenant on peut chercher tout echoln
appels restés dans Jeu
et déplacez-les aussi. Les tests passent, bien sûr.
Mais dès que nous sommes confrontés à la poser une question()
méthode, nous réalisons qu’il ne s’agit que d’un code de présentation. Et cela signifie que les divers tableaux de questions devraient également aller à Afficher
.
classe Display private $ popQuestions = []; private $ scienceQuestions = []; private $ sportsQuestions = []; private $ rockQuestions = []; function __construct () $ this-> initializeQuestions (); //… // fonction privée initializeQuestions () $ categorySize = 50; pour ($ i = 0; $ i < $categorySize; $i++) array_push($this->popQuestions, "Question Pop". $ i); array_push ($ this-> scienceQuestions, ("Question scientifique". $ i)); array_push ($ this-> sportsQuestions, ("Question sportive". $ i)); array_push ($ this-> rockQuestions, "Question Rock". $ i);
Cela semble approprié. Les questions ne sont que des chaînes, nous les présentons et elles s’adaptent mieux ici. Ce type de refactoring est également une bonne opportunité de refactoriser le code nouvellement déplacé. Nous avons défini les valeurs initiales dans la déclaration des champs, nous les avons également rendues privées et avons créé une méthode avec le code à exécuter, de sorte qu'elle ne soit pas simplement en attente dans le constructeur. Au lieu de cela, il est caché au bas de la classe, à l'écart.
Après avoir extrait les deux méthodes suivantes, nous réalisons qu’il est plus agréable de les nommer, à l’intérieur du Afficher
classe, sans le préfixe "display".
function correctAnswer () $ this-> echoln ("La réponse était correcte !!!!"); function playerCoins ($ currentPlayer, $ playerCoins) $ this-> echoln ($ currentPlayer. "a maintenant". $ playerCoins. "Coins en or.");
Avec nos tests écologiques et qui vont bien, nous pouvons maintenant refactoriser et renommer nos méthodes. PHPStorm peut très bien gérer les refactorisations de changement de nom. Il renommera les appels de fonction en Jeu
en conséquence. Ensuite, il y a ce morceau de code.
Regardez attentivement la ligne sélectionnée, 119. Cela ressemble à notre méthode récemment extraite dans Afficher
.
function correctAnswer () $ this-> echoln ("La réponse était correcte !!!!");
Mais si nous l'appelons à la place du code, le test échouera. Oui! Il y a une faute de frappe. Et non! Vous ne devriez pas le réparer. Nous sommes en train de refactorer. Nous devons garder la fonctionnalité inchangée, même s'il y a un bug.
Le reste de la méthode ne représente aucun défi particulier.
Maintenant que toutes les fonctionnalités de présentation sont en Afficher
, nous devons revoir les méthodes et ne garder publics que ceux utilisés dans Jeu
. Cette étape est également motivée par le principe de séparation des interfaces dont nous avons parlé dans un tutoriel précédent..
Dans notre cas, le moyen le plus simple de déterminer quelles méthodes doivent être publiques ou privées est de les rendre privées à la fois, d'exécuter les tests et, si elles échouent, de revenir au public..
Comme les tests Golden Master sont lents, nous pouvons également compter sur notre IDE pour nous aider à accélérer le processus. PHPStorm est suffisamment intelligent pour déterminer si une méthode est inutilisée. Si nous rendons une méthode privée et qu’elle devient soudainement inutilisée, il est clair qu’elle a été utilisée en dehors de Afficher
et doit rester public.
Enfin, nous pouvons réorganiser Afficher
de sorte que les méthodes privées sont à la fin de la classe.
À présent, la dernière étape du principe de refactoring de la classe d’extrait n’est pas pertinente dans notre cas. Donc, avec cela, cela termine le tutoriel, mais cela ne termine pas encore la série. Restez à l'écoute pour notre prochain article où nous travaillerons plus loin vers une architecture propre et des dépendances inverses.