viernes, 13 de julio de 2018

Capítulo 34: Refactoring - Parte 1 - TDD By Example

La parte más importante del «refactoring» es saber el porqué podrías querer usarlo.

En TDD se utiliza de manera que no se cambia la semántica del programa bajo ninguna circunstancia. Así que las pruebas que ya han pasado definen la semántica que hay hasta ahora así que, por ejemplo, se pueden reemplazar las constantes por variables y llamar a esta operación «refactorización» porque esta no cambia el conjunto de pruebas que pasan.

Reconciliar las diferencias


Cuando hay dos piezas de código que son muy similares se puede adoptar la estrategia de, gradualmente, acercarlas hasta que son absolutamente idénticas y en ese caso unificarlas.

Habrá refactorizaciones que sean obvias y que apenas cambien el comportamiento del sistema mientras que otras obligarán a revisar el control de flujo y los datos.

TDD brinda las herramientas necesarias para hacer refactorizaciones en pasos pequeños y tener «feedback» en el momento por lo que se deberían evitar los saltos de fé con refactorizaciones de dimensiones que no podamos controlar.

Las refactorizaciones ocurren en todos los ámbitos:
  • Si dos estructuras de bucle son iguales se pueden hacer idénticas y se puede unir.
  • Si dos ramas de un condicional son similares se pueden hacer idénticos y eliminar el condicional.
  • Si dos métodos son similares se pueden hacer idénticos y eliminar uno.
  • Si dos clases son similares se pueden hacer idénticas y eliminar una.

A veces se necesita abordar la reconciliación de las diferencias al revés, pensar como el último paso del cambio podría ser trivial y continuar así. Por ejemplo, si se quiere eliminar varias subclases el paso trivial es si la clase no contiene nada, en este caso, la subclase se puede reemplazar por la superclase sin cambiar el comportamiento del sistema. Para vaciar la clase el método debe ser idéntico a uno en la superclase. Entonces se vacían todas las subclases y, cuando todas estén vacías, se puede reemplazar las referencias de estas por la superclase.

Aislar el cambio


En el momento que se quiere hacer un cambio hay que identificar en qué parte del código se va a implementar, esto es aislar la parte que va a cambiar.

Habrá ocasiones en las que hemos aislado y hecho el cambio este resulte tan trivial que se pueda deshacer el aislamiento. Si encontramos que todo lo que se necesitaba era devolver una variable in «findRate()» deberíamos considerar la inclusión de todos los sitios donde se usa y borrarlo. Antes de borrarlo hay que valorar si tener un método adicional con el valor de tener un concepto explícito adicional  en el código.

Algunas estrategias para aislar el cambio son «Extract Method», «Extract Object» o «Method Object».

Migrar los datos


Las migraciones de datos deberían duplicarse de manera temporal.

Una estrategia para la migración es desde el interior hacia el exterior. En esta estrategia se cambia la representación interna para, posteriormente, cambiar la interface externa visible.

  • Añadir una nueva variable de instancia en el nuevo formato.
  • Asignar a la nueva variable en todos los lugares donde la vieja variable estaba asignada.
  • Usar la nueva variable en todos los lugares donde la vieja variable se usaba.
  • Borrar la vieja variable.
  • Cambiar la interfaz externa para reflejar la nueva variable.

En otras ocasiones la estrategia será cambiar del exterior al interior. Se cambia la interface externa visible para, posteriormente, cambiar la representación interna. Entonces se debería:

  • Añadir un parámetro en el nuevo formato.
  • Traducir la representación interna del viejo formato al nuevo formato.
  • Borrar el parámetro antiguo.
  • Reemplazar el uso del viejo formato con el nuevo.
  • Borrar el formato antiguo.

Las migraciones de datos de uno a una colección («One to Many») siempre son complejas de abordar. Por ejemplo, en el caso se pasar de ejecutar una prueba a tener un conjunto de pruebas en xUnit. Se debería empezar:

La clase «TestSuite», la cual está en la parte «One» de «One to Many», tiene la siguiente implementación.

Ahora empezamos con la duplicación de datos. Primero inicializamos la colección de pruebas.

En todos los lugares donde se asigna un valor a la variable «test» añadimos a la colección.

Ahora es el momento de usar la colección de «test» en vez de usar la variable «test». En este caso el cambio preserva la semántica del código ya que sólo hay un elemento en la colección.

Finalmente se limpia el código de las variables de «test» que no se usan.

No hay comentarios:

Publicar un comentario