Código heredado de refactorización Parte 7 - Identificación de la capa de presentación

Código antiguo. Código feo Código complicado. Código de espagueti. Tonterías gibernéticas. En dos palabras, Código Legado. Esta es una serie que te ayudará a trabajar y lidiar con ella..

En este séptimo capítulo de nuestros tutoriales de refactorización, haremos un tipo diferente de refactorización. En las lecciones pasadas, observamos que hay códigos relacionados con la presentación dispersos por todo nuestro código heredado. Intentaremos identificar todo el código relacionado con la presentación que podamos y luego tomaremos las medidas necesarias para separarlo de la lógica empresarial..

La fuerza impulsora

Cada vez que hacemos un cambio de refactorización en nuestro código, lo hacemos guiados por algunos principios. Estos principios y reglas nos ayudan a identificar los problemas y, en muchos casos, nos orientan en la dirección correcta para mejorar el código..

El Principio de Responsabilidad Única (SRP)

SRP es uno de los principios de SOLID de los que hablamos en gran detalle en un tutorial anterior: SOLID: Parte 1 - El principio de responsabilidad única. Si desea profundizar en los detalles, le recomiendo que lea el artículo; de lo contrario, continúe leyendo y vea un resumen del Principio de responsabilidad única a continuación..

SRP básicamente dice que cualquier módulo, clase o método debe tener una sola responsabilidad. Tal responsabilidad se define como un eje de cambio. Un eje de cambio es una dirección, una razón para cambiar. Entonces, SRP significa que nuestra clase debe tener una sola razón para cambiar.

Si bien eso suena bastante simple, ¿cómo define una "razón para el cambio"? Debemos pensarlo desde el punto de vista de los usuarios de nuestro código, tanto los usuarios finales comunes como los distintos departamentos de software. Estos usuarios pueden ser representados como actores. Cuando un actor quiere que cambiemos nuestro código, esa es una razón de cambio que determina un eje de cambio. Dicha solicitud debe afectar solo a uno de nuestros módulos, clases o incluso métodos, si es posible.

Un ejemplo muy obvio sería si nuestro equipo de diseño de interfaz de usuario nos obliga a proporcionar toda la información que se debe presentar de manera que nuestra aplicación pueda enviarse a través de una página web HTML, en lugar de nuestra interfaz de línea de comandos actual.

Tal como está hoy nuestro código, podríamos simplemente enviar todo el texto a algún objeto inteligente externo que lo transformaría en HTML. Pero eso puede funcionar solo porque HTML está basado principalmente en texto. ¿Qué pasa si nuestro equipo de UI quiere presentar nuestro juego de preguntas como una UI de escritorio, con ventanas, botones y varias tablas??

¿Qué pasa si nuestros usuarios quieren ver el juego en un tablero de juego virtual representado como una ciudad con calles y los jugadores como personas que caminan alrededor de la cuadra??

Podríamos identificar a estas personas como el actor UI. Y debemos darnos cuenta de que tal como está hoy nuestro código, deberíamos modificar nuestra clase de trivia y casi todos sus métodos. ¿Suena lógico modificar el fue respondido correctamente () método de la Juego ¿Clase si quiero corregir un error tipográfico en la pantalla en un texto, o si quiero presentar nuestro software de trivia como un tablero de juego virtual? No. La respuesta es absolutamente no..

Arquitectura limpia

Arquitectura limpia es un concepto promovido principalmente por Robert C. Martin. Básicamente, dice que nuestra lógica de negocios debe estar bien definida y claramente separada por límites de otros módulos que no estén relacionados con la funcionalidad principal de nuestro sistema. Esto conduce a un código desacoplado y altamente comprobable..

Es posible que haya visto este dibujo a lo largo de mis tutoriales y cursos. Lo considero tan importante, que nunca escribo código ni hablo de código sin pensarlo. Cambió totalmente la forma en que escribimos el código en Syneto y cómo se ve nuestro proyecto. Antes teníamos todo nuestro código en un marco MVC, con lógica de negocios en los Modelos. Esto fue difícil de entender y difícil de probar. Además, la lógica empresarial estaba totalmente acoplada a ese marco MVC específico. Si bien hacer esto puede funcionar con pequeños proyectos de mascotas, cuando se trata de un proyecto grande del cual depende el futuro de una empresa, incluyendo a todos sus empleados, debe dejar de jugar con los marcos MVC y debe comenzar a pensar Cómo organizar tu código. Una vez que haga esto, y haga las cosas bien, nunca querrá volver a la forma en que ariteccionó sus proyectos antes.

Observando pertenencia

Ya comenzamos a separar nuestra lógica de negocios de la presentación en los tutoriales anteriores. Algunas veces observamos algunas funciones de impresión y las extrajimos en métodos privados en el mismo Juego clase. Esta fue nuestra mente inconsciente que nos dice que eliminemos la presentación de la lógica empresarial a nivel de método..

Ahora es el momento de analizar y observar..

Esta es la lista de todas las variables, métodos y funciones de nuestro Game.php expediente. Las cosas marcadas con una "f" naranja son variables. La "m" roja significa método. Si es seguido por un candado verde, es público. Le sigue un candado rojo, es privado. Y de esa lista, todo lo que nos interesa, es la siguiente parte.

Todos los métodos seleccionados tienen algo en común. Todos sus nombres comienzan con "mostrar" ... algo. Todos son métodos relacionados con la impresión de cosas en la pantalla. Todos los identificamos en tutoriales anteriores y se extrajeron a la perfección, uno a la vez. Ahora debemos observar que son un grupo de métodos que pertenecen juntos. Un grupo que hace una cosa específica, satisface una sola responsabilidad, muestra información en la pantalla..

La clase de extracción Refactorización

Lo mejor ejemplificado y explicado en Refactoring: mejorar el diseño del código existente por Martin Fowler, la idea básica de la clase de refactoring de Extract es que después de darse cuenta de que su clase funciona y de que deben hacerlo dos clases, debe tomar medidas para realizarlas. dos clases. Hay mecanismos específicos para esto, como se explica en la cita a continuación del libro mencionado anteriormente.

  • Decide cómo dividir las responsabilidades de la clase..
  • Crear una nueva clase para expresar las responsabilidades de separación.
    • Si las responsabilidades de la clase anterior ya no coinciden con su nombre, cambie el nombre de la clase anterior.
  • Haz un enlace de la clase antigua a la nueva..
    • Es posible que necesite un enlace de dos vías. Pero no hagas el enlace de vuelta hasta que encuentres que lo necesitas.
  • Utilice el campo Mover en cada campo que desee mover.
  • Compilar y probar después de cada movimiento..
  • Use el método de movimiento para mover los métodos de los antiguos a los nuevos. Comience con los métodos de nivel inferior (llamados en lugar de llamar) y construya al nivel superior.
  • Compilar y probar después de cada movimiento..
  • Revisar y reducir las interfaces de cada clase..
    • Si tenía un enlace bidireccional, examine para ver si se puede convertir de una manera.
  • Decida si va a exponer la nueva clase. Si expone la clase, decida si la expone como un objeto de referencia o como un objeto de valor inmutable.

Aplicando la clase de extracto

Desafortunadamente, al momento de escribir este artículo, no hay un IDE en PHP que pueda realizar una clase de extracción simplemente seleccionando un grupo de métodos y aplicando una opción del menú..

Como nunca está de más conocer la mecánica de los procesos que implican trabajar con el código, realizaremos los pasos anteriores, uno por uno, y los aplicaremos a nuestro código..

Decidir cómo dividir las responsabilidades

Ya lo sabemos. Queremos romper la presentación desde la lógica empresarial. Queremos sacar resultados, mostrar funciones y otros códigos, y moverlos a otro lugar..

Crear una nueva clase

Nuestra primera acción es crear una nueva clase vacía..

clase de visualización  

Sí. Eso es todo por ahora. Y encontrar un nombre propio también fue bastante fácil.. Monitor es la palabra en la que estamos interesados ​​en todos nuestros métodos. Es el denominador común de sus nombres. Es una sugerencia muy poderosa sobre su comportamiento común, el comportamiento después del cual nombramos a nuestra nueva clase..

Si lo prefiere y su lenguaje de programación lo admite, PHP lo hace, puede crear la nueva clase dentro del mismo archivo que el anterior. O bien, puede crear un nuevo archivo para él desde el principio. Personalmente no encontré ninguna razón definitiva para ir de ninguna manera o prohibir cualquiera de las dos formas. Eso depende de ti. Solo decide y sigue adelante.

Vinculación de la clase antigua a la clase nueva

Este paso puede no sonar muy familiar. Lo que significa es declarar una variable de clase en la clase antigua y convertirla en una instancia de la nueva..

require_once __DIR__. '/Display.php'; función echoln ($ string) echo $ string. "\norte";  juego de clase static $ minimumNumberOfPlayers = 2; static $ numberOfCoinsToWin = 6; pantalla privada $; //… // función __construct () //… // $ this-> display = new Display ();  // ... todos los otros métodos ... //

Sencillo. ¿No es así? En JuegoEl constructor acaba de inicializar una variable de clase privada que llamamos igual que la nueva clase., monitor. También necesitábamos incluir el Display.php archivar en nuestro Game.php expediente. Todavía no tenemos un cargador automático. Tal vez en un futuro tutorial introduciremos uno si es necesario..

Y como siempre, no olvides realizar tus pruebas. Las pruebas unitarias son suficientes en esta etapa, solo para asegurarse de que no haya errores tipográficos en el código recién agregado.

El campo de movimiento y compilar / probar

Vamos a dar estos dos pasos a la vez. ¿Qué campos podemos identificar que deberían ir desde Juego a Monitor?

Con solo mirar la lista ...

static $ minimumNumberOfPlayers = 2; static $ numberOfCoinsToWin = 6; pantalla privada $; var $ players; var $ lugares; monederos var $; var $ inPenaltyBox; var $ popQuestions; var $ scienceQuestions; var $ sportsQuestions; var $ rockQuestions; var $ currentPlayer = 0; var $ isGettingOutOfPenaltyBox; 

... no podemos encontrar ninguna variable / campo que deba pertenecer a Monitor. Tal vez algunos emergerán a tiempo. Así que no hay nada que hacer para este paso. Y sobre las pruebas, ya las ejecutamos hace un momento. Tiempo de seguir adelante.

Mover los métodos a la nueva clase

Esto es en sí mismo, otra refactorización. Puede hacerlo de varias maneras y encontrará una buena definición en el mismo libro del que hablamos anteriormente..

Como se mencionó anteriormente, deberíamos comenzar con el nivel más bajo de métodos. Los que no están llamando a otros métodos. En su lugar se les llama.

función privada displayPlayersNewLocation () echoln ($ this-> players [$ this-> currentPlayer]. "La nueva ubicación es". $ this-> places [$ this-> currentPlayer]); 

displayPlayersNewLocation () Parece ser un buen candidato. Analicemos lo que hace.

Podemos ver que no llama a otros métodos en Juego. En su lugar, utiliza tres campos: jugadores, jugador actual, y lugares. Aquellos pueden convertirse en dos o tres parámetros. Hasta ahora bastante bien. Pero que pasa Echoln (), ¿La única función llamada en nuestro método? Donde está esto Echoln () procedente de?

Está en la cima de nuestro Game.php archivo, fuera de la Juego clase en si.

función echoln ($ string) echo $ string. "\norte"; 

Definitivamente hace lo que dice. Repite una cadena con una nueva línea al final. Y esto es pura presentación. Debería entrar en el Monitor clase. Así que vamos a copiarlo allí.

clase Display function echoln ($ string) echo $ string. "\norte"; 

Ejecutar nuestras pruebas de nuevo. Podemos mantener el maestro dorado deshabilitado hasta que terminemos de extraer toda la presentación al nuevo Monitor clase. En cualquier momento, si cree que la salida puede haber sido modificada, vuelva a ejecutar las pruebas maestras doradas también. En este punto, las pruebas darán fe de que no introdujimos errores tipográficos ni declaraciones de funciones duplicadas, ni ningún otro error, copiando la función a su nuevo lugar..

Ahora ve y borra Echoln () desde el Game.php Archivo, ejecute nuestras pruebas, y espere que fallen.

Error fatal de PHP: llamada a la función no definida echoln () en /… /Game.php en la línea 55

¡Bonito! Nuestra prueba de unidad es de gran ayuda aquí. Se ejecuta muy rápido y nos dice la posición exacta del problema. Vamos a la linea 55.

¡Mira! Hay un Echoln () llama ahi Las pruebas nunca mienten. Vamos a arreglarlo llamando $ this-> dipslay-> echoln () en lugar.

función add ($ playerName) array_push ($ this-> players, $ playerName); $ this-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "se agregó"); echoln ("Son número de jugador". count ($ this-> players)); devuelve verdadero 

Eso hace que la prueba pase a través de la línea 55 y falle en 56..

Error fatal de PHP: llame a la función no definida echoln () en /… /Game.php en la línea 56

Y la solución es obvia. Este es un proceso tedioso, pero al menos es fácil.

función add ($ playerName) array_push ($ this-> players, $ playerName); $ this-> setDefaultPlayerParametersFor ($ this-> howManyPlayers ()); $ this-> display-> echoln ($ playerName. "se agregó"); $ this-> display-> echoln ("Son número de jugador". count ($ this-> players)); devuelve verdadero 

Eso hace que las tres primeras pruebas pasen y también nos dice el siguiente lugar donde hay una llamada que debemos cambiar..

Error fatal de PHP: llame a la función no definida echoln () en /… /Game.php en la línea 169

Eso esta en respuesta incorrecta().

function wrongAnswer () echoln ("La pregunta fue respondida incorrectamente"); echoln ($ this-> players [$ this-> currentPlayer]. "fue enviado a la casilla de penalización"); $ this-> inPenaltyBox [$ this-> currentPlayer] = true; $ this-> currentPlayer ++; if ($ this-> shouldResetCurrentPlayer ()) $ this-> currentPlayer = 0;  devuelve true; 

Arreglar estas dos llamadas, lleva nuestro error a la línea 228.

función privada displayCurrentPlayer () echoln ($ this-> players [$ this-> currentPlayer]. "es el jugador actual"); 

UNA monitor ¡método! Quizás este debería ser nuestro primer método para movernos. Intentamos hacer un pequeño desarrollo basado en pruebas (TDD) aquí. Y cuando las pruebas fallan, no se nos permite escribir más códigos de producción que no sean absolutamente necesarios para hacer que la prueba pase. Y todo lo que conlleva es simplemente cambiar la Echoln () Llamadas hasta que todas nuestras pruebas unitarias pasen..

Puede acelerar este proceso utilizando las funciones de búsqueda y reemplazo de su IDE o editor. Simplemente ejecute todas las pruebas, incluido el maestro dorado, una vez que haya terminado con este reemplazo. Nuestras pruebas unitarias no cubren todo el código, y todas las Echoln () llamadas.

Podemos empezar sin nuestro primer candidato., displayCurrentPlayer (). Copiarlo en Monitor y ejecuta tus pruebas.

Entonces, hazlo público en Monitor y en displayCurrentPlayer () en Juego llamada $ this-> display-> displayCurrentPlayer () en lugar de hacer directamente una Echoln (). Finalmente, ejecuta tus pruebas.

Ellos van a fallar. Pero al hacer el cambio de esta manera, nos hemos asegurado de que cambiemos solo una cosa que podría fallar. Todos los otros métodos siguen llamando Juegoes displayCurrentPlayer (). Y este es el delegado a Monitor.

 Propiedad indefinida: Display :: $ display

Nuestro método utiliza campos de clase. Estos deben ser parámetros a la función. Si sigues tus errores de prueba, deberías terminar con algo como esto en Juego.

función privada displayCurrentPlayer () $ this-> display-> displayCurrentPlayer ($ this-> players [$ this-> currentPlayer]); 

Y esto en Monitor.

function displayCurrentPlayer ($ currentPlayer) $ this-> echoln ($ currentPlayer. "es el jugador actual"); 

Reemplazar llamadas en Juego al método local con el de Monitor. No te olvides de subir los parámetros un nivel, también.

función privada displayStatusAfterRoll ($ rolledNumber) $ this-> display-> displayCurrentPlayer ($ this-> players [$ this-> currentPlayer]); $ this-> displayRolledNumber ($ rolledNumber); 

Finalmente, elimine el método no utilizado de Juego. Y ejecute sus pruebas para asegurarse de que todo está bien.

Este es un proceso tedioso. Puede acelerarlo un poco tomando varios métodos a la vez y usando lo que su IDE pueda hacer para ayudar a mover y reemplazar el código entre clases. El resto de los métodos seguirán siendo un ejercicio para usted o puede leer más en este capítulo con los aspectos más destacados del proceso. El código terminado adjunto a este artículo contendrá la información completa. Monitor clase.

Ah, y no olvide el código que aún no se ha extraído en los métodos de "visualización" que se encuentran dentro Juego. Puedes mover esos Echoln () llamadas para mostrar directamente. Nuestro objetivo es no llamar. Echoln () en absoluto de Juego, y hazlo privado en Monitor.

Después de solo media hora más o menos de trabajo., Monitor empieza a verse bien.

Todos los métodos de visualización desde Juego están en Monitor. Ahora podemos buscar todo. Echoln llamadas que quedaron en Juego Y moverlos, también. Las pruebas están pasando, por supuesto..

Pero en cuanto nos enfrentamos a la pregunta() método, nos damos cuenta de que es sólo el código de presentación también. Y eso significa que las diversas matrices de preguntas también deben ir a Monitor.

clase Display private $ popQuestions = []; preguntas $ science privadas = []; $ sportsQuestions privados = []; $ rockQuestions privadas = []; función __construct () $ this-> initializeQuestions ();  //… // private function initializeQuestions () $ categorySize = 50; para ($ 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); 

Eso se ve bien. Las preguntas son solo cadenas, las presentamos y encajan mejor aquí. Cuando hacemos este tipo de refactorización, también es una buena oportunidad para refactorizar el código recién movido. Definimos los valores iniciales en la declaración de campos, también los hicimos privados y creamos un método con el código que debe ejecutarse para que no se quede solo en el constructor. En su lugar, está oculto en la parte inferior de la clase, fuera del camino.

Después de extraer los siguientes dos métodos, nos damos cuenta de que es mejor nombrarlos, dentro de Monitor clase, sin el prefijo "display".

function correctAnswer () $ this-> echoln ("¡¡La respuesta fue correcta !!!!");  function playerCoins ($ currentPlayer, $ playerCoins) $ this-> echoln ($ currentPlayer. "ahora tiene". $ playerCoins. "Gold Coins."); 

Con nuestras pruebas verdes y funcionando bien, ahora podemos refactorizar y cambiar el nombre de nuestros métodos. PHPStorm puede manejar el cambio de nombre de refactorizaciones bastante bien. Se cambiará el nombre de las llamadas de función en Juego en consecuencia. Luego está esta pieza de código.

Mire detenidamente la línea seleccionada, 119. Se parece al método extraído recientemente en Monitor.

function correctAnswer () $ this-> echoln ("¡¡La respuesta fue correcta !!!!"); 

Pero si lo llamamos en lugar del código, la prueba fallará. ¡Sí! Hay un error tipográfico. ¡Y no! No deberías arreglarlo. Estamos refactorizando. Debemos mantener la funcionalidad sin cambios, incluso si hay un error.

El resto del método no representa ningún desafío especial..

Revisar y reducir las interfaces

Ahora que toda la funcionalidad de presentación está en Monitor, Debemos revisar los métodos y mantener públicos solo los utilizados en Juego. Este paso también está motivado por el Principio de Segregación de Interfaz del que hablamos en un tutorial anterior.

En nuestro caso, la forma más fácil de averiguar qué métodos deben ser públicos o privados, es hacer que cada uno sea privado a la vez, ejecutar las pruebas y, si fallan, volver al público..

Debido a que las pruebas maestras de oro se ejecutan con lentitud, también podemos confiar en nuestro IDE para ayudarnos a acelerar el proceso. PHPStorm es lo suficientemente inteligente como para averiguar si un método no se utiliza. Si hacemos que un método sea privado, y de repente no se usa, está claro que se usó fuera de Monitor y necesita seguir siendo público.

Finalmente, podemos reorganizar Monitor Para que los métodos privados estén al final de la clase..

Pensamientos finales

Ahora, el último paso del principio de Extracción de la clase de extracción es irrelevante en nuestro caso. Así que con esto, esto concluye el tutorial, pero esto aún no concluye la serie. Manténgase atento a nuestro próximo artículo en el que trabajaremos más hacia una arquitectura limpia e invertiremos las dependencias..