Un ejercicio de refactorización (Carlos Fontela)

El concepto de refactorización – una de las traducciones más habituales del inglés “refactoring”, que otros traducen como refabricación – tiene que ver con el cambio de un diseño una vez que se desarrolló una funcionalidad y la misma ya está codificada.

La idea de la refactorización es mejorar el diseño de parte de una aplicación que ya está funcionando. Por lo tanto, un buen término sería rediseño, pero lamentablemente no es muy usual.

El problema con las refactorizaciones es que son riesgosas, ya que estamos cambiando código que sabemos que funciona por otro que – aunque presumimos que va a ser de mejor calidad – no sabemos si funcionará.

Para permitir refactorizaciones más seguras y con menos estrés, una buena táctica es trabajar con pruebas unitarias automatizadas, escritas antes de refactorizar. Es decir, no importa si hemos escrito o no pruebas unitarias a nuestro código en funcionamiento. Pero si vamos a refactorizar, debemos escribirlas antes. Y – por supuesto – correrlas después para asegurarnos que nuestro programa sigue funcionando.

Veámoslo con un ejemplo.

Supongamos que tenemos una clase Fecha, para manejar fechas, como la que se muestra parcialmente más abajo:

public class Fecha {

private int dia;

private int mes;

private int anio;

public Fecha(int dia, int mes, int anio) {

this.dia = dia;

this.mes = mes;

this.anio = anio;

}

public boolean valida ( ) {

if (dia < 1 || dia > 31)

return false;

if (mes < 1 || mes > 12)

return false;

// determinamos la cantidad de días del mes:

int diasMes = 0;

switch (mes) {

case 1:

case 3:

case 5:

case 7:

case 8:

case 10:

case 12: diasMes = 31; break;

case 4:

case 6:

case 9:

case 11 : diasMes = 30; break;

case 2 : // verificación de año bisiesto

if ( (anio % 400 == 0) ||

( (anio % 4 == 0) && (anio % 100 != 0) ) )

diasMes = 29;

else diasMes = 28;

break;

}

if (dia > diasMes)

return false;

else return true;

}

// … más métodos

}

El método valida que se muestra allí es poco menos que horrible. Poco legible, poco cohesivo, y unas cuantas cosas más que no pretendo analizar aquí, pero que sí traté en mi artículo sobre calidad de código.

Alineados con los criterios de buena calidad de código, deberíamos separar en un método aparte el cálculo de la cantidad de días del mes. En segundo lugar, podríamos separar también la verificación de si el año es o no bisiesto.

Hagamos entonces la refactorización.

Lo primero sería escribir el código de pruebas, ya que no lo tenemos. Como sólo debemos chequear el método valida, utilizamos JUnit sólo para ese método. El código de pruebas sería:

import junit.framework.TestCase;

public class PruebaFechaValida extends TestCase {

private Fecha fechaCorrecta = new Fecha (20, 6, 2008);

private Fecha mesMal1 = new Fecha (21, 0, 3000);

private Fecha mesMal2 = new Fecha (21, 13, 3000);

private Fecha diaMal1 = new Fecha (0, 11, 2000);

private Fecha diaMal2 = new Fecha (32, 11, 2000);

private Fecha diaMalNoviembre = new Fecha (31, 11, 2000);

private Fecha diaBienDiciembre = new Fecha (31, 12, 2000);

private Fecha diaMalFebrero = new Fecha (30, 2, 2008);

private Fecha diaBienFebreroBisiesto1 = new Fecha (29, 2, 2008);

private Fecha diaBienFebreroBisiesto2 = new Fecha (29, 2, 2000);

private Fecha diaMalFebreroBisiesto1 = new Fecha (29, 2, 2007);

private Fecha diaMalFebreroBisiesto2 = new Fecha (29, 2, 1900);

public void testValida( ) {

assertTrue(fechaCorrecta.valida());

assertFalse(mesMal1.valida());

assertFalse(mesMal2.valida());

assertFalse(diaMal1.valida());

assertFalse(diaMal2.valida());

assertFalse(diaMalNoviembre.valida());

assertTrue(diaBienDiciembre.valida());

assertFalse(diaMalFebrero.valida());

assertTrue(diaBienFebreroBisiesto1.valida());

assertTrue(diaBienFebreroBisiesto2.valida());

assertFalse(diaMalFebreroBisiesto1.valida());

assertFalse(diaMalFebreroBisiesto2.valida());

}

}

Obviamente, lo primero que hay que hacer es probar que los tests corran sin problema. Si no fuese así, no necesitamos una refactorización: o bien el código que tenemos funciona mal, o la prueba está mal escrita. Por suerte en nuestro caso, obtenemos la deseada barra verde.

La primera refactorización es separar el método diasMes. Un primer paso es declararlo para que compile:

private int diasMes( ) {

return 0;

}

Ahora cambiamos el método valida, para que use diasMes:

public boolean valida ( ) {

if (dia < 1 || dia > 31)

return false;

if (mes < 1 || mes > 12)

return false;

if (dia > diasMes())

return false;

else return true;

}

Por supuesto, como diasMes está vacío, las pruebas van a arrojar problemas. Y era lo que esperábamos. Si no fuese así, de nuevo, el problema está en la prueba. Para solucionarlos, debemos escribir diasMes de modo tal que nos dé los días del mes:

private int diasMes ( ) {

int diasMes = 0;

switch (mes) {

case 1:

case 3:

case 5:

case 7:

case 8:

case 10:

case 12: diasMes = 31; break;

case 4:

case 6:

case 9:

case 11 : diasMes = 30; break;

case 2 : if ( (anio % 400 == 0) ||

( (anio % 4 == 0) && (anio % 100 != 0) ) )

diasMes = 29;

else diasMes = 28;

break;

}

return diasMes;

}

Ahora sí, corremos la prueba, y obtenemos un resultado satisfactorio.

Lo que todavía no se puede considerar un buen ejemplo de calidad de código es la verificación de año bisiesto. Hagamos la refactorización necesaria, con un método bisiesto, en principio vacío:

private boolean bisiesto ( ) {

return true;

}

Y el método valida quedará:

private int diasMes ( ) {

int diasMes = 0;

switch (mes) {

case 1:

case 3:

case 5:

case 7:

case 8:

case 10:

case 12: diasMes = 31; break;

case 4:

case 6:

case 9:

case 11 : diasMes = 30; break;

case 2 : if ( bisiesto() )

diasMes = 29;

else diasMes = 28;

break;

}

return diasMes;

}

De nuevo, chequeo el código, y debo obtener errores. Como ocurre lo esperado, ahora escribo el código de bisiesto para que chequee si el año es bisiesto o no:

private boolean bisiesto ( ) {

if ( (anio % 400 == 0) ||

( (anio % 4 == 0) && (anio % 100 != 0) ) )

return true;

else return false;

}

Ahora sí obtengo la barra verde.

Y lo habremos probado completamente, siempre dando pasos pequeños y seguros.

Una pregunta que tal vez quede rondando en las cabezas de los lectores. ¿Por qué declaré los métodos bisiesto y diasMes como privados?

Lo hice así, respetando lo que en mi artículo sobre calidad de código he denominado “principio de mínimo privilegio”. En efecto, los dos métodos nuevos no eran parte del contrato de la clase, de modo tal que ningún cliente los venía usando por el momento. Si deseo mantener inalterado el contrato de los clientes, debo declararlos privados para que éstos no los puedan usar. Más sobre el tema en el artículo recién mencionado.

Algunos entornos de desarrollo tienen herramientas para facilitar la refactorización. El Eclipse que uso para mis programas Java es un caso bastante bueno. De hecho, no necesité hacer tantos pasos como mostré en este ejercicio.

1 Responses to Un ejercicio de refactorización (Carlos Fontela)

  1. […] Si alguien con mayor voluntad desea ver un ejemplo, le recomiendo leer mi ejercicio de refactorización. […]

Deja un comentario