Trabajando con código legado. Breaking (the) bad dependencies II.

En esta serie de artículos que he estado publicando en Nearsoft Jobs, hemos hecho un largo viaje desde el reconocimiento del código legado hasta hoy, donde continuamos presentando un conjunto de técnicas que nos serán útiles a la hora de enfrentarnos a dicha quimera. Así que tal vez quieras ver antes:

En el capítulo de hoy quiero analizar dos técnicas más: Breakout method class y Encapsular globales. Como en el caso de las anteriores, se tratan de técnicas sencillas, quizás obvias pero que exigen disciplina de nuestra parte para no entusiasmarnos y acabar enredados en un mar de refactorings que todavía no debían ocurrir.

Breakout method class.

Cuando presenté esta plática en las pasadas October Talks de Nearsoft, no encontré una traducción adecuada para el nombre de esta técnica. Así que si tuviera que hacerlo, diría que este método se ocupa de abstraer lógica de un método dios dentro de una clase ejecutora.

En este extraño lenguaje inventado, debes pasar el tipo del parámetro junto con el parámetro.

Imaginemos que tenemos una aplicación que se encarga de dar de alta una entidad financiera “empresa” en el SAT (Servicio de Administración Tributaria en México), de manera que al final tenemos una entidad totalmente constituida (probablemente esto no sea un caso tan práctico, pero imaginemos que lo que digo es posible). Supongamos además que el método “buildEnterprise” entre otras cosas toma una entrada con el nombre de la empresa y en base a ciertos parámetros escoge si debe ser AC, SA de CV, etcétera, y que nosotros queremos probar justamente esa lógica. Para nuestra mala suerte dentro del mismo método “buildEnterprise” tenemos también la llamada “setUpSatContract” quién se encarga de hacer la llamada a los servicios del SAT que nos permitan dar de alta una empresa. Cuando navegamos hacia el último método descubrimos que se trata de una pesadilla de 500 LOC que no queremos involucrar en nuestra prueba… ¿qué hacemos?

Por supuesto que BreakOut Method Class tal vez no sea la única solución, pero es una que debemos tomar en cuenta no solo por el mejoramiento en la abstracción de la prueba sino porque además en el futuro da pauta a un mejor diseño del sistema de clases con el que estemos trabajando. Usemos entonces BreakOut Method Class…

  1. Creemos la clase SatContractSetUpExecutor , quien será el host del método “setUpSatContract”.
  2. Creemos un constructor para dicha clase que Preserve la firma del método “setUpSatContract”.
  3. Copiemos el cuerpo del método a la nueva clase y llamemos a este método como execute.
  4. Confiemos en el compilador.
  5. Reemplacemos la llamada al método en su lugar de origen, en este caso el método “buildEnterprise” por la llamada a la clase y su posterior llamada a SatContractSetUpExecutor#execute.
De la línea 2 a la 12 tenemos una vaga definición de cómo debe quedar la clase que contiene al método dios. En las líneas 15 a 20 vemos como se debe usar esta clase en sustitución a la llamada directa de “setUpSatContract”.

Con esto obtenemos la flexibilidad de definir el comportamiento que necesitemos en nuestras pruebas de SatContractSetUpExecutor, sin que tengamos que forzosamente obligarnos a ejecutar el código de producción o siquiera modificarlo. Nos da la habilidad de introducir Mocks o Fakes.

Encapsular globales

Las globales han sido y seguirán siendo por mucho tiempo, tal vez para siempre, pequeños troles que pueden con facilidad conducir a problemas de concurrencia que no son nada fáciles de reproducir o incluso de atacar. Las globales siempre son indicaciones claras de un mal diseño y deben siempre atacarse.

En el caso de la introducción de pruebas, el problema con las globales es que aunque corramos diferentes pruebas, guardarán el estado en que la prueba anterior haya dejado dichas variables. Además tratar de hacer que las pruebas regresen a dichas piezas problemáticas a un estado inicial, puede convertirse también un problema porque en algunas ocasiones tendríamos que tener que escribir código que hiciese que una prueba dependiese de otra rompiendo con la intención de la introducción de las pruebas.

Un pequeño ejemplo de una referencia global.

Lo más fácil en estos casos es remover cualquier referencia a dichas variables globales y encapsularlas en una sola referencia que puede ser global también, pero que nos ayude a introducir Mocks o Fakes sobre esas variables. Podemos aplicar la siguiente técnica:

  1. Crear una clase con atributos idénticos a las globales a sustituir.
  2. Crear una referencia global a dicha clase, puede ser un Singleton.
  3. Substituir las referencias globales por referencias a la nueva clase u objeto que encapsula las variables globales.
  4. Si es posible se debe utilizar setters estáticos sobre las variables globales que se han encapsulado.

De esta forma ahora es más fácil identificar los puntos en los que estamos haciendo uso de estas variables y podemos atacar el problema en futuras iteraciones. Es importante aclarar que si bien no hemos hecho una gran diferencia en el código al simplemente encapsular, siempre debemos tener en mente que la introducción de pruebas debe ser metódica y siempre velando por modificar lo menos posible el código base antes de tener pruebas.

Aquí tenemos un ejemplo de cómo encapsular la referencia global mostrada en la imagen anterior.

Preservar la firma.

En la primera técnica hemos hablado de preservar la firma, esta pequeña herramienta se trata de nunca modificar la lista de parámetros que un método recibe cuando lo estemos refactorizando para introducir pruebas, esto porque hace que sea más fácil la introducción de errores. Podremos modificar la firma del método después, pero siempre cuando ya tengamos pruebas sobre ese código de forma que estemos seguros que no hemos modificado el comportamiento.


En el siguiente capítulo hablaremos de dos técnicas más, entre ellas una de mis favoritas y de las más útiles que vamos a encontrar. No desesperen que ya casi estamos llegando al final de este “Unexpected Journey”.

Estos posts están basados en el libro “Working effectively with legacy code” por Michael C. Feathers

César Ricardez, Software Engineer @Nearsoft