Un repaso por los code smells más comunes

Podemos definir a los code smells como indicadores superficiales de posibles problemas en el sistema. Se tratan de estructuras que se pueden ver en el código y que sugieren la posibilidad de un refactor.

En esta nota trataremos de verlos en profundidad para identificarlos y solucionarlos rápidamente.

Tipos y subtipos de code smells

  • Bloaters: representan algo que ha crecido en proporciones tan grandes que resulta complejo tanto de entender como de manejar. Generalmente, se acumulan con el tiempo y no pueden ser detectados hasta que el programa evolucione. Son los siguientes:

  1. Long Method: cualquiera puede notar que mientras más largo sea un procedimiento, más difícil va a ser entenderlo. Aún así, a la mayoría de los programadores les genera desconfianza achicar demasiado los métodos porque temen tener demasiados y que, al leer el código, haya que cambiar de contexto constantemente para saber lo que hace un submétodo. La clave para que los pequeños métodos sean fáciles de entender es nombrarlos bien, con una abstracción simple que permita saber lo que hacen. Sabiendo esto, no necesitaremos ir a ver el cuerpo del mismo y cambiar de contexto. Entonces, Martin Fowler nos dice que debemos ser más agresivos al descomponer los métodos. ¿Cómo identificarlo? Si creemos necesario comentar el código de un método para que se entienda mejor, probablemente debamos sacarlo y ponerlo en un nuevo método para volverlo más expresivo.

     ¿Cómo solucionarlo? El 99% de las veces usaremos el refactor Extract Method. Si interfieren parámetros y variables locales, podemos emplear otros refactors para usar objetos de parámetros y reemplazar variables temporales con métodos.

  2. Large Class: ¿Cómo identificarlo?Una clase intenta tomar demasiadas responsabilidades, por lo que debería seguir el principio de single responsibility, o sea, cumplir solamente con una de las funcionalidades del software y estar bien encapsulada. Generalmente, muestran demasiadas variables de instancia y código duplicado entre métodos.

    ¿Cómo solucionarlo? Debemos usar el refactor Extract Class para extraer un conjunto de variables que puedan ir juntas con un nombre descriptivo. Así, evitaríamos el primitive obsession (que ya veremos). También podemos extraer subclases si parte del comportamiento puede ser usado de distintas formas, o extraer una interfaz si se requiere una lista de comportamientos para el cliente. Preserve Whole Object puede servir si hay parámetros que evitan que se pueda extraer la clase.

  3. Primitive Obsession: en realidad es más un síntoma que genera bloats que un bloat en sí mismo. Cuando existe, no hay clases pequeñas para entidades pequeñas (como números de teléfono) y se emplean muchas constantes. Como parece más fácil que crear una nueva clase, se empiezan a acumular. Los data clumps también son causa de bloating: distintas partes del código usan los mismos grupos de variables. Suele pasar cuando se hace mucho copy-paste, por lo que es importante evitar estas malas prácticas para no generar bloats ¿Cómo identificarlo? Por el uso de constantes para la codificación de la información y el uso de las constantes de cadena como nombres de campo para su uso en arreglos de datos ¿Cómo solucionarlo? Si se nos presenta una buena variedad de campos primitivos, podemos agrupar algunos en su propia clase o mover el comportamiento asociado con esta data también a la misma clase. Para hacer esto, podemos probar reemplazando el valor de datos con objeto. Cuando los datos se codifican de modo complicado en las variables, podemos reemplazar código tipo por clases o subclases. Si se nos presentan arreglos entre las variables, debemos hacer el reemplazo de matiz con objeto

  4. Long Parameter List: aparecen cuando un método es muy largo o cuando se intenta independizar a las clases. Pero al delegar menos entre clases, necesitamos utilizar demasiados parámetros para que el método tenga lo que precisa para actuar. De todos modos, nunca hay que pasarle a un método todo lo que necesita, si no lo necesario para que pueda conseguir lo que precisa ¿Cómo identificarlo? Cuando tenemos más de tres o cuatro parámetros para un método ¿Cómo solucionarlo? Debemos chequear los valores pasados como parámetro. Generalmente basta con reemplazar el parámetro con una llamada a otra clase y en vez de pasar una lista de variables como parámetro, debemos enviar el objeto entero. A veces se pueden juntar varios parámetros en un solo objeto antes de ser enviados, como un rango de fechas en vez de fecha de inicio y fin.

  •  Object-Orientation Abusers: la solución a ellos no hace uso de todas las posibilidades que nos ofrece la programación orientada a objetos. Por ejemplo, un switch en objetos es, digámoslo, horrible. Debería entonces hacerse uso del polimorfismo. Repasemos algunos de los object-orientation abusers:

  1. Refused bequest: ¿cómo identificarlo? Una subclase usa sólo algunos de los métodos y atributos de sus superclases. Alguien, probablemente, quiso reutilizar código, pero la jerarquía está mal utilizada porque las clases son totalmente diferentes ¿Cómo solucionarlo? Si la herencia no tiene sentido alguno, debemos reemplazarlo con delegación al objeto. Si tiene sentido, hay que extraer una superclase para obtener las variables y el comportamiento que necesitemos. Bequest se refiere a que la subclase rechaza el comportamiento de la clase padre que no necesita.

  2. Temporary field: ¿Como identificarlo? Un objeto tiene variables de instancia que utiliza sólo en ciertas situaciones. Es extraño porque esperamos que ese objeto necesite de todas sus variables y es difícil entender por qué está ahí si no la necesita ¿Cómo solucionarlo? Podemos extraer una clase donde estén todas las variables temporales y el comportamiento que las utiliza. También se puede eliminar código condicional utilizando un objeto para el valor Null, un refactor bastante utilizado.

  • Change Preventers: son smells que previenen el cambio o expansión del software. Algunos de ellos:

  1. Divergent Change: implica que tenemos una sola clase que debe ser modificada cuando se hacen muchos cambios distintos. Shotgun surgery (el próximo a explicar) es lo opuesto: necesitamos modificar muchas clases cuando hacemos un solo cambio en el sistema. Por eso, según Fowler y Beck, las clases y los posibles cambios deben tener una relación uno a uno.

  2. Shotgun Surgery: ¿Cómo identificarlo? Realizar cualquier modificación requiere de muchos pequeños cambios en muchas clases distintas ¿Cómo solucionarlo? Mover todo el comportamiento similar a una clase sola, así cada una tendrá su propia responsabilidad. Recordar el principio de single responsibility. Si no existe una clase apropiada a donde mover el comportamiento, crearla.

  3. Jerarquías paralelas de herencia: ¿Cómo identificarlas? Al crear una subclase para una clase y ver que no queda otra opción más que crear una nueva subclase para otra clase.¿Cómo solucionarlas?Deberías “desduplicar” las clase de jerarquía. Luego, hacer que las menciones de una jerarquía se refieran a casos de otra. Finalmente, retirar la jerarquía de la clase a la que se refiere utilizando Move Method y Move Field.

  • Dispensables: su nombre es elocuente. Son innecesarios y su ausencia hace que el código se vea más limpio y sea más eficiente. Si bien son cinco (Lazy Class, Data Class, Duplicate Code, Dead Code y Generalidad especulativa), hay de dos tipos principalmente: clases dispensables y código dispensable. Si una clase no hace demasiado, debe ser removida o hay que incrementar sus responsabilidades. El código que no se utilice o sea redundante, también. Son los siguientes:

  1. Data class: ¿Cómo identificarlo? Son clases que solamente contienen datos y sus respectivos accessors para que los utilicen otras clases. No tienen responsabilidades ni métodos que utilicen estos datos ¿Cómo solucionarlo? Tenés que revisar el código usado por la clase. Probablemente haya comportamiento que pueda ser responsabilidad de la data class, entonces, tenés que emplear Extract Method.

  2. Generalidad especulativa: ¿Como identificarla? Hay clases, métodos, variables o parámetros sin utilizar. A veces se genera código “por si acaso” para soportar features, pero nunca son implementados o, si lo son, se olvidan de emplear este código¿Cómo solucionarla? No es complejo. Hay que remover las variables sin usar y mergear clases con subclases innecesarias. Recordá que el código tiene que ser lo más simple posible, no te anticipes a cambios o funcionalidades futuras.

  • Acopladores: contribuyen a un acoplamiento excesivo entre clases o demuestran lo que sucede cuando el acoplamiento es reemplazado por una delegación excesiva. Son:

  1. Feature Envy

  2. Inappropriate Intimacy

  3. Message Chains

  4. Middle Man

Los tres primeros son de alto acoplamiento, mientras que Middle Man representa un problema que puede crearse al intentar evitar el alto acoplamiento con delegación constante. Inappropiate Intimacy sucede cuando dos clases están muy acopladas entre sí.

Feature Envy lo identificamos cuando un objeto está más interesado en otro que en sí mismo. Principalmente suele suceder con “envidia de datos”. Solucionarlo es sencillo: hay que mover los métodos a la clase envidiada. Pero no siempre es tan fácil, debemos tener en cuenta que un método puede incluir comportamiento de varias clases. Para evitar estos problemas, es ideal mantener siempre la responsabilidades y el encapsulamiento de las clases. Middle Man es identificable porque la única acción que realiza un objeto es delegar trabajo a otras clases. No debería existir. La solución pasa por remover la clase y hacer las llamadas al objeto final. Si la clase tiene otras responsabilidades propias, habrá que remover los métodos y hacer lo mismo (remove middle man).

Esperamos que, con este artículo, les sea más fácil identificar code smells en el futuro para tener  claro cuándo es necesario hacer refactoring.


¿Cómo podemos ayudarte?
Hablemos.

Contactanos

¿Te ves trabajando acá? ¡Genial!

Unite al equipo