lunes, 7 de julio de 2014

Code Smells - Métodos Largos




Todos conocemos la máxima "Si bueno y breve, dos veces bueno" verdad?
Nunca es mas cierto esto que en el desarrollo de software.


Los Metodos Largos son Malvados

Hace mucho tiempo, incluso antes del surgimiento del paradigma de la Orientación a Objetos (POO), que se sabe que las funciones, procedimientos y  subrutinas son mucho mas mantenibles, tienen menos problemas y errores y sobreviven mucho más si son cortos.

En realidad la teoría de Calidad de software habla de cohesión funcional cuando todo el código del elemento al que se referencia (modulo, método, clase o función) se ocupa de implementar una y sola una función bien específica. Es decir, realizan una sola y bien definida tarea. Y para implementar esa única y bien definida tarea no se necesitan muchas lineas de código.

La POO real esta basada en una serie de intercambios (envío y recepción) de mensajes claros y bien definidos entre objetos pequeños y con un propósito especifico (ver el Principio de Responsabilidad Unica, SRP, en este mismo blog). Y la inversa en este caso es totalmente cierta, sin un código hace varias cosas a la vez, tiene varios propósitos, genera mas de un efecto a la vez, o  hace dos o mas cosas con mas de un objetivo , entonces, por lo general eso ocupara muchas lineas de código, generando asi metodos largos.  Los Métodos largos representan uno de los Code Smells más claros respecto a marcar código... que apesta (En Inglés: APESTA!).

A riesgo de caer en la obviedad vamos a preguntarnos:

Motivos por los que los metodos largos...apestan ?

Para empezar, cuanto más largo es un método es mucho mas difícil de entender en su totalidad y  de comprender los efectos colaterales que genera. Como sabemos, esto tiene relación directa con la mantenibilidad, porque cuanto mas difícil es comprender un código mas riesgo hay de que al modificarlo se introduzcan errores.

En segundo lugar un método largo casi con seguridad no posee cohesión funcional.  Inclusive podemos apostar sin temor a equivocarnos que tendrá el peor tipo de cohesión que hay: coincidental. Es decir el código hace varias cosas, muchas no relacionadas mas que por la coincidencia, lo cual aumenta a su vez la probabilidad de tener que tocarlo durante la vida de mantenimiento del sistema (que como dijimos alguna vez...es el 80% al 90% del tiempo de vida de un sistema, siendo el 10% restante el tiempo original utilizado para su desarrollo inicial).

Pero en ningún otro lado afecta la longitud de un método tanto como en el atributo de su testeabilidad, es decir, cuan sencillo/factible es testearlo, y ahi mis amigos, es cuando los agarro sin escapatoria.

Un método largo es imposible de testear correctamente, porque simplemente, hace DEMASIADAS COSAS, y entonces hay que testear todas esas cosas y peor que peor, la diferente combinación de ellas. Esto produce rápidamente un efecto de multiplicación geométrica y entonces, los tests, cuando los hay , tambien son complejos y largos. Entonces perdemos por los dos lados, porque encima el código quedara con bajo cubrimiento de tests o ninguno, y si hay tests sera una pesadilla manternerlos.


Como Solucionarlo

Aquí van una lista simple de algunas heuristicas a aplicar, no son infalibles ni, aclaro, los absuelven, queridos chimpancés entrenados,  de utilizar el sentido común :

  1. En la mayoría de los casos alcanza con descomponer el método largo con varios métodos cortos, refactorizando utilizando el refactor "Extract Method". La forma más simple que funciona es ir recorriendo el método de arriba a abajo, encontrando 4 o 5 lineas de código que realizan una unica función claramente definida  y extraerla como un método nuevo llamado desde el método largo.
  2. Si el método tiene además largas listas de parámetros es una indicación de que sufre de 2 o más Code Smells a la vez (lo mas común), para lo cual es una buena práctica primero reemplazar los parámetros relacionados por un Objeto que los contenga (una Clase generalmente nueva que pide, "a gritos!" ser creada) y recién ahí aplicar el punto 1.
  3. Candidatos a extraer métodos son las líneas intermedias de un bloque IF y por separado, en otro método, en general, las líneas del bloque else.
  4. En Java, por ejemplo, o lenguajes con manejo de excepciones existe el típico caso de bloques (encima Java con su azúcar sintáctica (Syntactic sugar) lo hace más largo todavía!): 
     try {
           hacerA()
           hacerB()
           hacerC()
      } Catch (AgarrateException A) {
      }  Catch (AgarrateOtraException B) {
      } Catch (AgarrateTodas C) { }

En este caso ya es bastante mala la sintaxis del lenguage para que encima le agreguemos fuego al incendio. Un buen remedio, en general , es reemplazar "hacer A", "hacer B" y "hacer C" por un extract method, "metodoConEfectoABC" : try { metodoConEfectoABC() } catch (){ ...

     5. En cada paso, agregue un pequeño test de unidad que testee el nuevo metodo extraido.



Concluyendo porque los artículos largos también son malvados.

Este es en realidad uno de los code smell más común con que nos encontramos. Si hubiera que elegir un y solo un code smell para atacar para lograr mejorar lo máximo posible con un solo tiro la mantenibilidad del código, este es el que elegiría. Por supuesto que no alcanza con el para lograr tener una mantenibilidad aceptable o buena calidad, es solo un  buen comienzo  hacia el lado correcto.

Es muy común, luego de refactorizar uno de estos métodos largos en varios mas pequeños  que  varios de los nuevos metodos se parecen o realizan funciones similares a otros metodos ya existentes encontrándonos asi antes uno de los mas peligrosos code smell: Duplication is Evil.

Así que, porque se que se lo estaban preguntando, la tarea de un profesional del Código Limpio nunca termina y debe seguir la maxima de los Boys Scouts: Siempre  Listo....para mejorar la calidad del código.