Crap4j, ¿es tu código difícilmente mantenible?
1. Introducción
Crap4j es una implementación de la métrica CRAP (Change Risk Analisys and Predictions), algo que en castellano podriamos traducir como «Análisis y Predicciones sobre los Riesgos de realizar Cambios». Sin embargo esta es la manera «fina» de llamarlo, ya que la palabra ‘crap’ tiene, en inglés, un significado mucho más directo. Para hacernos una idea del significado real del término ‘crap’ solo tenemos que fijarnos en que el logotipo de Crap4j es un rollo de papel higiénico y que aparecen moscas alrededor.
Pero, ¿para qué sirve este plugin exactamente?. Su función es analizar código fuente y buscar aquellas clases y métodos que alguien hizo en el pasado y que, a estas alturas del desarrollo, son muy dificiles de modificar, bien sea porque el código está programado de un modo que sea dificil de leer y de entender, o bien porque es prácticamente imposible determinar cual puede ser el alcance de nuestros cambios. Si funciona bien, se suele decir que «si funciona no lo toques» (más que nada porque a ver quen es el valiente que se atreve a tocarlo), pero si funciona mal o queremos cambiar su modo de funcionamiento podemos tener un problema de los gordos.
Digamos que trata de localizar ese código con el que todos nos hemos encontrado alguna vez al auditar, al mantener o al continuar con un desarrollo y hemos tenido algún pensamiento bastante negativo sobre él. Podemos poner muchos adjetivos sobre ese código, y ninguno bueno. Por educación me limitaré a decir que es un código dificil de mantener.
Pero, ¿en qué se basa la métrica CRAP para evaluar un método?. Pues en la combinación de dos factores:
- Su complejidad ciclomática, o número de bifurcaciones únicas presentes en un método.
- Su cobertura por pruebas unitarias, o % del código que es ejecutado cuando se lanzan las pruebas unitarias pobre ese método.
Para obtener un índice utiliza la siguiente fórmula:
CRAP(m) = comp(m)^2 * (1 - cov(m)/100)^3 + comp(m)
Donde
- CRAP(m) es un índice que, a mayor valor, implicará mayor dificultad de mantenimiento en el método m
- comp(m) es el valor de la complejidad ciclimática del método m
- cov(m) es el % de cobertura de código del método m por pruebas unitarias
Valores pequeños de CRAP(m) indican que los métodos son fácilmente mantenibles, mientras que a medida que el valor aumenta, tambien aumenta la dificultad de mantenimiento. Para disminuir el valor de CRAP(m) debes hacer dos cosas: escribir pruebas unitarias que aseguren que cualquier cambio en el código no va a afectar al resto de la aplicación y reducir su complejidad ciclomática partiendo ese método en otros más pequeños (que tambien incluyan pruebas unitarias). Un método se considera difícilmente mantenible cuando su índice CRAP es mayor que 30.
Una vez vista la teoría y la razón de ser de la métrica vamos a seguir en el tutorial con la instalación del plugin de Crap4j para Eclipse, el cual os dirá si un método es dificilmente mantenible o no. Crap4j genera informes en html donde podreis ver qué métodos son los más problemáticos y cuales los menos para que podais obrar en consecuencia y, además, puede ser lanzado desde ant, con lo cual podreis lanzarlo programadamente. Si alguien se anima a escribir un tutorial sobre ello que no dude en hacerlo 🙂
2. Entorno
3. Instalación
Para instalar el plugin es imprescindible que tengamos una versión de Eclipse superior a la 3.2.1. Si lo tenemos lo ejecutamos, y una vez haya terminado de cargar deberemos ir al apartado ‘Help > Software Updates > Find and Install‘ para acceder al gestor de actualizaciones de Eclipse.
Indicamos que queremos instalar nuevas caracteristicas.
Nos aparecerá una pantalla similar a esta. Vamos a instalar el plugin de Crap4j desde un repositorio remoto, asi que pinchamos sobre ‘New Remote Site’
Y una vez ahi damos de alta un nuevo repositorio, lo llamamos crap4j e indicamos que la URL donde se ubica es http://www.crap4j.org/downloads/update
Una vez hayamos dado de alta el repositorio nos aparecerá una pantalla como la anterior, seleccionamos solo el repositorio crap4j y pulsamos ‘Finish’. Tras eso nos aparecerá una ventana como la siquiente. Marcamos las opciones indicadas en la captura de pantalla y pulsamos sobre ‘Next >’
En caso de estar conforme con las licencias las aceptamos y seguimos
Nos avisará del lugar donde va a instalar el plugin por si queremos poner otra ruta. Pulsamos ‘Finish’
Esperamos a que se instale el plugin. Tardará más o menos en función al ancho de banda del que dispongamos.
Una vez descargado lo instalamos con ‘Install All’ para no tener que ir aceptando los jar del plugin uno por uno.
Y tras eso os aconsejo reiniciar Eclipse para que se apliquen los cambios correctamente y asi no tener problemas.
Al reiniciar veremos que el plugin se ha instalado correctamente si aparecen los elementos señalados en rojo y en verde. También aparecerá el submenú ‘Agitar’ a la derecha del submenú ‘Crap4j’, y es que Agitar Software es la empresa que está tras el desarrollo de Crap4j.
4. Funcionamiento del plugin
package com.autentia.crap4j; public class CrappyClass { public void CrappyMethod (final boolean param1, final boolean param2, final boolean param3) { if (param1) { System.out.println ("param1 es true"); if (param2) { System.out.println ("param2 es true"); if (param3) { System.out.println ("param3 es true"); } else { System.out.println ("param3 es false"); } } else { System.out.println ("param2 es false"); if (param3) { System.out.println ("param3 es true"); } else { System.out.println ("param3 es false"); } } } else { System.out.println ("param1 es false"); if (param2) { System.out.println ("param2 es true"); if (param3) { System.out.println ("param3 es true"); } else { System.out.println ("param3 es false"); } } else { System.out.println ("param2 es false"); if (param3) { System.out.println ("param3 es true"); } else { System.out.println ("param3 es false"); } } } } }
Para ejecutar el plugin basta con seleccionar un proyecto cualquiera y pulsar el botón remarcado en rojo en la imagen anterior. Tras la ejecución, en el directorio donde tengamos el proyecto, se nos creará un directorio llamado ‘agitar/reports/crap4j’ con un informe sobre lo que ha encontrado. Si no lo veis, usad el navegador de archivos o recargad el proyecto seleccionándolo y pulsando F5.
Voy a poneros 3 ejemplos con los 3 resultados para que los interpretemos. Sobre un proyecto vacio creo la clase siguiente:
El funcionamiento de este método es muy simple: recibe 3 valores booleanos y muestra sin son true o false. Sin embargo, si veis el código veis que es bastante malo porque tiene una complejidad ciclomática bastante elevada (muchos if … else encadenados) y no tenemos pruebas unitarias para este método, con lo cual ante no podemos saber si cambiando código estamos estropeando otra parte de la aplicación. En este caso un cambio no afecta a nada, pero imaginaros que estuviesemos abriendo o cerrando transacciones, cambiando el estado de alguna parte de la aplicación, definiendo navegabilidad, etc.
Ejecutamos crap4j pulsando sobre el botón recuadrado en rojo y vemos los resultados:
El 50% de los métodos de la clase son CRAPpy (dificilmente mantenibles). Pero, ¿por qué dos si sólo hemos escruto un método?. Son dos porque está evaluando el constructor (en este caso el constructor por defecto) y el método CrappyMethod() que programamos anteriormente. Si vemos la mantenibilidad por métodos, veremos lo siguiente:
El código está cubierto al 0% por pruebas unitarias, la complejidad ciclomática del método CrappyMethod() es de 8 y su indice CRAP de 72. Un método se considera difícilmente mantenible cuando su índice CRAP es mayor que 30.
¿Cómo se puede arreglar esto?. Intentemos al menos crear una prueba unitaria que nos garantice que si cambiamos código los cambios no van a ocasionar ningún desastre. Vamos a crear una prueba unitaria que pase por varios if … else para aumentar la cobertura de las pruebas unitarias sobre el código (lo cual no es muy dificil ya que ahora mismo es del 0,00%). La prueba que voy a crear siempre valida al método, con lo cual no es una prueba apropiada, pero estamos en un tutorial y todo es a modo didáctico. El código de la prueba es el siguiente:
package com.autentia.crap4j; import org.junit.Assert; import org.junit.Test; public class CrappyClassTest { @Test public void testCrappyMethod() { CrappyClass crappyClass = new CrappyClass(); crappyClass.CrappyMethod(true, false, false); crappyClass.CrappyMethod(false, true, true); Assert.assertTrue(true); } }
Hacemos click con el botón derecho sobre la clase ‘CrappyClass.java‘, seleccionamos ‘Agitar > Find Test for CrappyClass’ y una vez ahi escogemos CrappyClassTest. Con esto lo que vamos a conseguir es que al lanzar Crap4j se van a lanzar las pruebas unitarias que hayamos indicado y, de este modo, va a ser posible saber qué grado de cobertura tienen las pruebas sobre el código. Lanzamos Crap4j y vemos los resultados:
Vaya, ahora no tenemos métodos con un indice CRAP mayor que 30. ¿Qué indices tienen ahora los métodos?
El constructor ahora está cubierto al 100% por las pruebas y el método CrappyMethod() ha bajado de un CRAP de 72 a uno de 18.75 gracias a la cobertura de las pruebas unitarias, que alcanza un 44,83% del código del método. El índice es inferior a 30 asi que se considera que no es difícil de mantener.
¿Y si creamos un método igual, solo que sin pruebas y con una complejidad ciclomática inferior?
package com.autentia.crap4j; public class NonCrappyClass { public void NonCrappyMethod (final boolean param1, final boolean param2, final boolean param3) { showContent ("param1", param1); showContent ("param2", param1); showContent ("param3", param1); } private void showContent (final String name, final boolean value) { if (value == true) { System.out.println (name + "es true"); } else { System.out.println (name + "es false"); } } }
Habriamos obtenido el siguiente resultado:
Ningún método con índice CRAP mayor a 30, y sin pruebas unitarias
Y encima el indice CRAP mayor es de 6, y porque no hay pruebas unitarias, porque el método showContent() es muy fácilmente mantenible (aunque faltarian comprobaciones ya que, por ejemplo, el parámetro name podria recibirse con un valor null).
5. Conclusiones
Pues como podeis ver el índice CRAP es bastante útil y puede ayudar bastante sobre todo a la hora de iniciar un proyecto, ya que gracias a él nos aseguraremos que, desde un principio, nuestra aplicación va a ser fácilmente mantenible mientras la desarrollemos o, al menos, vamos a poder tener localizados los puntos donde este mantenimiento no va a ser fácil en un futuro si éste es necesario.
Como no podria ser de otro modo, tambien va a ayudarnos a saber si es posible realizar una refactorización de un modo fácil una vez llegado el momento. Esto puede sernos muy útil si estamos siguiendo una metodología de desarrollo ágil.
Para terminar, simplemente indicar que el índice CRAP, y por ende el plugin, lo que hace es «obligarnos» a seguir unas buenas prácticas de programación que nos haga la vida más sencilla a medio y largo plazo, y seguramente a corto plazo tambien, como son:
- hacer los métodos lo más sencillos y cortos posibles.
- usar pruebas unitarias.
Como dice un amigo mio «hay que intentar que el pan para hoy no sea hambre para mañana». El índice CRAP lo que nos indica es hasta qué punto podemos pasar hambre mañana 🙂
Espero que os sea de utilidad.