Code smell

El code smell es una indicación de la mala calidad del código. Si hay code smells, quien lea el código tendrá la sensación de que algo está mal. Te mostramos diferentes code smells y te explicamos cómo puedes deshacerte de ellos.

¿Qué es el code smell?

Un code smell es una característica del código que permite a los programadores experimentados intuir que el código no está limpio. Tener un buen “olfato” para los code smells es comparable a la intuición de un maestro artesano. Si ves una maraña de cables o unas tuberías mal colocadas, enseguida te das cuenta de que algo no funciona bien. En el caso del código fuente estos patrones reveladores tienen un aspecto visual diferente.

Martin Fowler, un conocido programador británico, apunta a su compañero Kent Beck como el que creó el término. En el blog de Fowler aparece la siguiente definición:

Cita

“A Code smell is a surface indication that usually corresponds to a deeper problem in the system.” – Fuente: https://martinfowler.com/bliki/CodeSmell.html
Traducción: “El code smell es una indicación superficial que suele hacer referencia a un problema más profundo en el sistema.” (traducido por IONOS)

Por lo tanto, un code smell indica un problema sistémico. Puede que el código lo escribiese un programador sin los conocimientos suficientes o que diferentes programadores hayan ido turnándose. En este último caso, las diferencias en el nivel de competencia, el conocimiento de la base de código y la familiaridad con las directrices y las normas de cada profesional conducen a una mala calidad del código. Es decir, el código puede empezar a “oler mal”.

No hay que confundir el code smell con las deficiencias puntuales, que son de esperar en toda base de código. En primer lugar, el code smell es únicamente una indicación. Si un programador detecta un code smell, debe comprobar más a fondo si realmente hay un problema sistémico.

Si resulta evidente que el código “huele lo suficiente mal” por ser deficiente, hay diferentes maneras de limpiarlo. A menudo se puede recurrir a enfoques como la refactorización, que sirven para mejorar la estructura del código manteniendo su funcionalidad. Sin embargo, dependiendo del tamaño y la complejidad del código, puede ser imposible eliminar los code smells. En ese caso, la única opción es el “Re-write”, es decir, reescribir el código desde cero.

¿Cuáles son los diferentes tipos de code smells?

El código es un medio que existe a diferentes niveles de abstracción. Por ejemplo, en las aplicaciones el código define una multitud de piezas individuales: variables, funciones, clases y módulos. Para considerar los diferentes code smells, hay que distinguir entre los niveles de abstracción:

  1. Code smells de forma general
  2. Code smells a nivel de función
  3. Code smells a nivel de clase
  4. Code smells a nivel de la aplicación

Code smells de forma general

Los code smells, por lo general, pueden encontrarse en todos los niveles de una aplicación. La mayoría de las veces no son errores graves, sino patrones que tienen un impacto negativo. En programación hay una regla general que establece que el código debe estar principalmente escrito para los usuarios. Sobre todo en el caso de los programadores inexpertos, el código ofrece el resultado deseado, pero no es incomprensible.

Mala nomenclatura en la estructura del código

Utilizar la nomenclatura correcta de las construcciones de código, como las variables y las funciones, es todo un arte. Lamentablemente, a menudo aparecen en el código nombres sin sentido, absurdos o incluso contradictorios. El mejor ejemplo lo constituyen los nombres de variables que constan de una sola letra: x, i, n, etc. Como estos nombres no dan ningún contexto, el significado y la finalidad no son obvios.

Es mejor utilizar nombres explícitos. De esta forma, el código se lee de forma natural, como un idioma, y es más fácil tanto seguir el flujo del programa como reconocer las incoherencias del mismo.

Falta de uniformidad en el estilo del código

Un código limpio parece haber sido escrito de una sola tirada; se puede ver de inmediato que se creó a partir de ciertas reglas. Si falta dicha uniformidad, se trata de un code smell.

La falta de homogeneidad en la forma de nombrar variables sugiere que varias personas escribieron partes del código de forma independiente. Si se trata de un equipo, puede resultarles útil tener unas normas predefinidas.

Consejo

¿Curioso? Lee también nuestro artículo “¿Qué es el clean code?”.

Variables sin definir

Algunos lenguajes como C++, Java y JavaScript permiten declarar variables sin darles un valor inicial. La variable existe desde su creación, pero no está definida, lo que puede dar lugar a pequeños errores. Este code smell es especialmente crítico en los lenguajes débilmente tipados. El hecho de no definirla inicialmente, junto a que el lenguaje no controla los tipos de variables, hace imposible saber qué tipo de variable se ha creado.

Valores codificados en el código

Este error lo suelen cometer los programadores inexpertos: para comparar una variable con un valor concreto, introducen el valor directamente (se denomina “valores codificados”). Los valores codificados son problemáticos si aparecen en varios lugares del mismo programa, pues las copias individuales del valor tienden a mutar de forma independiente con el tiempo.

Es mejor definir una constante para el valor. Esto crea una “Single Source of Truth” (SSOT): todo el código que necesita el valor accede a la misma constante definida en un único lugar.

Números mágicos en código

Un número mágico es un caso especial de valores codificados. Imagina que el código funciona con valores limitados a 24 bits. 24 bits corresponden a 16 777 216 valores posibles o a los números de 0 a 16 777 215.

Desgraciadamente, falta añadir un comentario, por lo que no está claro, a posteriori, cómo se ha llegado a ese valor. Resultado: ha surgido un número mágico. El desconocimiento del origen del valor aumenta el riesgo de que se modifique por error.

Estos errores pueden evitarse al incluir la definición del valor como una expresión al asignar la constante.

Sentencias de control profundamente anidadas

En la mayoría de los lenguajes, es posible cambiar las construcciones de código a cualquier profundidad. Por desgracia, esto también aumenta su complejidad, lo que hace más difícil entenderlo cuando se lee. Un code smell especialmente común es el de las sentencias de control profundamente anidadas, como los bucles y las ramas.

Hay varias maneras de resolver una anidación como, por ejemplo, utilizando el operador booleano AND para colocar ambas condiciones dentro de una misma sentencia if:

Otro método que funciona con cualquier número de condiciones utiliza “Guard Clauses”.

Aquí un ejemplo más complejo. Si quieres analizar los datos que se puedan necesitar preparar primero, como parte de la preparación, debes normalizar algunos elementos de datos. El primer enfoque contiene cuatro niveles de sentencias de control anidadas:

Nota

La calificación de “profundamente anidado” obedece a una medida subjetiva. Como regla general se establece que las sentencias de control deben estar anidadas a una profundidad máxima de tres niveles, en alguna excepción un máximo de cuatro. Más profundo casi nunca es aconsejable o siquiera necesario. Si te tienta anidar a una mayor profundidad, deberías considerar el refactoring.

El code smell a nivel funcional

En la mayoría de los lenguajes, las funciones son la unidad básica de ejecución del código. Hay piezas de código más pequeñas, como las variables y las expresiones, pero estas son independientes. Escribir funciones sencillas y claras requiere cierta experiencia. Mostramos algunos de los code smells más comunes a nivel de función.

Falta de atención a las excepciones

Las funciones reciben argumentos a modo de valores de entrada. En muchos casos, solo son válidos ciertos valores o rangos de valores. Es responsabilidad de los programadores comprobar la autenticidad de los valores introducidos y gestionar las excepciones en consecuencia.

Acceso a la variable desde un nivel superior de la función

Las áreas de validez son una característica fundamental de la mayoría de los lenguajes de programación. Determinan qué nombres se definen en qué lugares del código. Los ámbitos subordinados heredan de los superiores. Si se accede a una variable definida externamente dentro de una función, se convierte en un code smell. Esto se debe a que el valor puede haber cambiado entre que se definió la variable y cuando se llama a la función.

Lo ideal es que solo se acceda a los valores que aparecen como sentencias dentro de las funciones. Se utilizan parámetros por defecto para evitar tener que pasar el mismo valor repetidas veces:

Nota

El acceso a una variable desde fuera del ámbito de una función que la encierra crea un “Closure”. No se trata de code smell. Los cierres son un concepto importante en la programación de JavaScript.

Code smells a nivel de clase

La programación orientada a objetos (OOP) puede ayudar a aumentar la reutilización del código y reducir su complejidad. Desgraciadamente, hay muchas ocasiones en las que se pueden cometer errores en el diseño de las clases que luego desencadenan en un code smell.

Uno de los code smells más comunes a nivel de clase es el “God Object” o su correspondiente “Clase Dios”. Un god object combina todo tipo de funcionalidades que normalmente no van juntas y, por tanto, violan el principio de “separación de intereses”.

Los code smells se encuentran a menudo en relación con la jerarquía de la herencia. A veces el código se distribuye innecesariamente a través de múltiples niveles de herencia. A menudo se comete el error de utilizar la herencia en lugar de la composición como enfoque principal para componer objetos.

El uso involuntario de “Data Classes” también se considera un code smell. Son clases que no implementan su propio funcionamiento. Si no se definen métodos aparte de los getters y setters genéricos, se debe considerar el uso de una estructura de datos más simple como un Dict o un Struct.

Code smells a nivel de aplicación

Es la pesadilla de todo programador: que te designen a trabajar en una aplicación existente y con un primer vistazo al código veas que toda la aplicación se encuentra en un único archivo enorme, donde no queda claro cómo se relacionan los componentes individuales del código. Como un plato de fideos, todo se desmadra en el “Spaghetti Code”.

Faltan todas las abstracciones, no hay subdivisión entre las clases y, en el mejor de los casos, solo hay unas pocas funciones. En cambio, hay todo tipo de duplicaciones de código que dan lugar a pequeños bugs. Además, se utilizan mucho las variables globales.

El uso de variables globales es un code smell especialmente invasivo, pues estas pueden ser potencialmente modificadas desde cualquier parte del código, lo que abre la puerta a errores difíciles de corregir. Para limpiar el código, te ves en la obligación de buscar errores por todas sus secciones.

¿Cómo se puede evitar un code smell?

En sí, es preferible mantener el código limpio en todo momento. La mejor manera de hacerlo es seguir los siguientes pasos:

  1. Prototype: crear un diseño
  2. Test: probar su funcionalidad
  3. Refactor: limpiar el código
  4. Ship: incorporar el código a su entorno de producción

Desgraciadamente, el tercer punto no suele tenerse en cuenta, sobre todo cuando la decisión la toma un directivo sin experiencia en codificación. El razonamiento es el siguiente: el código funciona, así que ¿por qué seguir trabajando en él? Sin embargo, a la larga, el código empieza a no oler bien y la deuda técnica se acumula.

Si un código fuente que ya existe contiene code smells, no es posible empezar desde cero. Por lo que es necesario limpiar el código. Lo mejor es proceder de forma gradual y delimitar primero las áreas del código fuente que pueden limpiarse con relativa facilidad. Separa los componentes que funcionan o que son mejorables de los “rincones oscuros” que es mejor no tocar.

Separar las áreas de código “menos apestosas” de las “más apestosas” reduce la complejidad del código. Esto facilita la comprobación del código y la adopción de medidas de debugging. También pueden ser de gran ayuda las herramientas automatizadas de code Review que detectan los code smells y ofrecen sugerencias o ayuda para limpiarlos.

Los enfoques de refactorización son eficaces para eliminar los code smells. La funcionalidad se encapsula en funciones o se dividen las funciones existentes. Eliminar las secciones de código que no se utilizan y mejorar la nomenclatura del código hace que el código fuente sea más sencillo. Las reglas de oro “Don’t Repeat Yourself” (DRY) y “You ain’t gonna need it” (YAGNI) sirven de guía.