El "mal olor de código" es un término informal utilizado por los programadores para describir los patrones de diseño de software que son comunes al código incorrecto. Los malos olores de código se entienden mejor no como errores o defectos en sí mismos, sino como señales de advertencia de una mala calidad del código.
El término "mal olor de código" lo acuñó Kent Beck y se popularizó en el fundamental libro de 1999 que escribió junto a Martin Fowler, Refactoring: Improving the Design of Existing Code, específicamente en el capítulo titulado "Bad Smells in Code". El propio Fowler articula sucintamente el concepto de "mal olor de código" como "una indicación superficial que generalmente corresponde a un problema más profundo en el sistema".1
A diferencia de un verdadero error de software, el mal olor de código no impide que el código fuente se compile, ejecute y realice la función deseada, ni su presencia siempre indica un problema real. Imagine entrar en una casa y oler algo raro o con moho. Ese olor puede deberse a la comida caducada de la nevera o a la basura que hay que sacar; más en serio, puede deberse al moho o a algo que se pudre en las paredes (pero también puede deberse a un queso muy picante que es perfectamente seguro para comer). En cualquier caso, el olor merece una inspección más profunda.
Para un ingeniero de software con experiencia, ciertos patrones del código simplemente no "huelen" bien. Con el tiempo, se puede asociar instintivamente su presencia con ciertas complicaciones, ineficiencias o problemas futuros. Nombrar y popularizar olores de código específicos facilita el conocimiento de estos patrones contraproducentes (o "antipatrones") y los principios de diseño que infringen. Esa familiaridad, a su vez, hace que estos antipatrones sean más propensos a ser detectados y corregidos durante las revisiones del código. Como dice Fowler, “un olor es, por definición, algo que se detecta rápidamente o que se puede oler”.
Los malos olores de código, si no se abordan, contribuyen en gran medida a la deuda técnica. Como afirma un estudio, el 75 % de los defectos detectados en la revisión de código no afectan a la ejecución del programa, pero sí “impactan en la capacidad de evolución del software”.2 Incluso si un mal hábito o antipatrón determinado no provoca un error en la actualidad, aumenta significativamente el riesgo de que surjan errores, bloqueos o vulnerabilidades de seguridad en su base de código más adelante. Como mínimo, la presencia de malos olores de código conocidos reduce la legibilidad y la capacidad de mantenimiento del código, lo que dificulta su comprensión, actualización y mejora.
Manténgase al día sobre las tendencias más importantes e intrigantes del sector en materia de IA, automatización, datos y mucho más con el boletín Think. Consulte la Declaración de privacidad de IBM.
Aunque muchos malos olores de código comunes continúan siendo conocidos por los nombres acuñados por Beck y Fowler en Refactoring, no existe una lista única canónica y universalmente aceptada de malos olores de código. Pero si un mal olor de código determinado debe denominarse con un nombre u otro, o categorizarse como su propio antipatrón en lugar de como un subtipo de otro, es en gran medida irrelevante. Lo que importa es si el nombre y la descripción de un mal olor de código son lo suficientemente claros e intuitivos como para ayudar a los equipos de desarrollo de software a compartir e implementar principios de diseño productivos.
Asimismo, no existe una taxonomía universalmente aceptada para las diferentes categorías de malos olores. Este artículo se basa principalmente en la taxonomía propuesta por Jerzyk y Madeyski en 2023.3 Sus hallazgos se publicaron en el catálogo útil mantenido en codesmells.org, que proporciona contexto adicional, ejemplos de código y técnicas adecuadas de refactorización para cada uno.
Jerzyk y Madeyski señalan que la taxonomía de malos olores de código más comúnmente citada sigue las cinco agrupaciones propuestas por Mäntylä y Lassenius en 2006, quienes categorizaron los 22 malos olores de código introducidos por Fowler y Beck (y uno más propio) en cinco grupos distintos: Bloaters, Object-Orientation Abusers, Change Preventers, Dispensables y Couplers.4 Después de su propio intento de una revisión exhaustiva tanto de la literatura publicada formalmente como del "material gris" (como blogs, foros y wikis), Jerzyk y Madeyski catalogaron 56 malos olores de código diferentes en 9 agrupaciones (que incluyen los 5 mencionados anteriormente en este párrafo).
Aunque esta sección sigue mayormente la estructura de 9 categorías que propusieron, es importante señalar que tales agrupaciones son informales y subjetivas: la taxonomía más útil es la que le resulte más intuitiva. Comprender los diferentes tipos de complicaciones o ineficiencias que pueden introducir los malos olores de código puede proporcionar una concepción más significativa de estos olores que memorizar de forma mecánica los malos olores individuales. Como Mäntylä explica, “con una larga lista plana de problemas de código es fácil perder de vista el panorama general”.5
Los bloaters son malos olores de código que a menudo hacen que métodos, clases o bloques de código crezcan tanto que se vuelven inmanejables. Esto disminuye la legibilidad y hace que el código sea más difícil de mantener o modificar.
Algunos ejemplos de bloaters son:
Agrupaciones de datos: grupos de variables que aparecen juntas con frecuencia en todas partes.
Clase grande: una clase que intenta abarcar demasiado y contiene demasiadas variables, por lo que carece de cohesión.
Función larga (método largo): un método que contiene demasiadas líneas de código.
Lista larga de parámetros: funciones que requieren demasiados argumentos para funcionar correctamente.
Obsesión por los primitivos: el uso de tipos de datos primitivos en lugar de pequeños objetos especializados.
Los bloqueadores del cambio, como su nombre indica, son malos olores de código que dificultan la capacidad de modificar, añadir o desarrollar aún más el software. Un ejemplo típico de bloqueador del cambio es una estructura de código que le obliga a realizar una serie de ediciones en varios lugares solo para implementar una única y sencilla modificación.
Este tipo de malos olores de código violan el principio de responsabilidad única (SRP) de Robert C. Martin, que sostiene que los cambios en un módulo de código solo deberían poder originarse en un solo lugar. Como sugiere Martin en una entrada de blog sobre SRP: "Junte las cosas que cambian por las mismas razones. Separe aquellas cosas que cambian por razones diferentes".6
Algunos ejemplos de bloqueadores del cambio son:
Cirugía de escopeta: implementar un solo cambio requiere modificaciones en muchos módulos dispersos simultáneamente (lo cual es esencialmente lo opuesto a una clase grande).
Cambio divergente: al igual que la "cirugía con escopeta", implementar un solo cambio requiere muchas modificaciones dentro de una sola clase.
Infierno de callback: estructura de código en la que muchos métodos están anidados profundamente unos dentro de otros a través de muchas pestañas sangradas y corchetes, ocultando causa y efecto y dificultando la lectura y el mantenimiento del código.
Los acopladores huelen a código que infringen el principio básico de diseño del acoplamiento bajo al crear dependencias excesivas entre las diferentes clases. Esto reduce la reutilización del código y complica (o incluso impide) las pruebas unitarias independientes.
Algunos ejemplos de acopladores son:
Envidia de características: un método accede a los datos de otro objeto en exceso.
Insider Trading (también conocido como intimidad inapropiada): una clase utiliza los campos o métodos internos de otra clase.
Cadenas de mensajes: una secuencia larga de llamadas a métodos encadenadas entre objetos.
Los intermediarios de datos hacen pasar los datos por muchas más clases o funciones de las necesarias. Esto crea dependencias y complejidad innecesarias, y a menudo se traduce en elementos de código que se limitan a almacenar o distribuir datos sin proporcionar ningún comportamiento significativo.
Algunos ejemplos de intermediarios de datos son:
Middle man: una clase cuya única función es delegar en otros.
Datos trampa: cuando los datos «hacen autostop» a través de una cadena de métodos que no los utilizan.
Datos globales: estructura en la que las variables se pueden modificar desde cualquier parte de la base de código, lo que hace que cada función de la base de código sea sospechosa cuando algo se interrumpe.
Los dispensables son, intuitivamente, elementos de código de los que se puede prescindir: su eliminación haría que la base de código fuera más limpia y fácil de leer sin ningún impacto significativo en la funcionalidad general.
Entre los ejemplos de dispensables se incluyen:
Comentarios: si bien los comentarios suelen ser algo positivo, a veces se utilizan como un "desodorante" para disimular los malos olores del código, explicando en lugar de mejorarlo. Por ejemplo, si un comentario describe lo que sucede en una sección particular del código, se está utilizando para ocultar código que no es lo suficientemente intuitivo o legible por sí mismo. Los comentarios individuales también pueden volverse redundantes u obsoletos con el tiempo. Comportamientos de comentarios más específicos aparecen en otros olores de código.
Clase de datos: clases que contienen solo campos y descriptores de acceso, carecen de un comportamiento significativo.
Código muerto: elementos de código que ya no se ejecutan porque, con el tiempo, la refactorización y otras modificaciones los han dejado obsoletos.
Código duplicado: estructuras de código idénticas o muy similares en varios lugares.
Clase perezosa (también conocida como elementos perezosos): clases o funciones que aportan muy poco.
Generalidad especulativa: se ha añadido código innecesario para dar soporte a hipotéticas características futuras.
Los abusadores funcionales son olores de código que evitan los principios de la programación orientada a objetos al forzar los patrones funcionales a una base de código orientada a objetos.
Algunos ejemplos de abusadores funcionales son:
Bucles: utilizar bucles tradicionales en lugar de operaciones de canalizaciones modernas. Aunque Fowler consideraba que casi todos los bucles estaban obsoletos, Jerzyk sostenía que los bucles imperativos, en particular, son el principal problema.
Datos mutables: variables cuyo estado cambia inesperadamente, causando efectos secundarios impredecibles.
Efectos secundarios (también conocidos como funciones impuras): métodos que hacen más de lo que su nombre y propósito principal implicarían.
Los abusadores léxicos son olores de código que se originan en convenciones de nombres deficientes, formato inconsistente o sintaxis críptica. En pocas palabras, son casos en los que la redacción del código no coincide intuitivamente con el comportamiento del código correspondiente, lo que dificulta la legibilidad.
Algunos ejemplos de abusadores léxicos son:
Número mágico: números sin nombre insertados en el código sin la explicación ni el contexto adecuados.
Comentario falaz: comentarios que ya no son precisos porque el código que los rodea ha cambiado. Como los comentarios no se ejecutan realmente, suelen escapar a los punteros y a otras comprobaciones automáticas.
Nombre misterioso: funciones o variables con nombres deficientes, ocultando su intención real.
Nombre del método falaz: funciones cuyos nombres engañan activamente, según convenciones y expectativas comunes. Por ejemplo, una función llamada getSomething que en realidad no devuelve nada.
Los ofuscadores son elementos de código escritos de una manera innecesariamente compleja, enrevesada o "ingeniosa" que oculta la intención subyacente del código tras una abstracción innecesaria. Estos olores de código hacen que el código fuente sea confuso de leer y, por lo tanto, dificulta que los programadores en el futuro lo entiendan y modifiquen según sea necesario.
Algunos ejemplos de ofuscadores son:
Separación vertical: distancias innecesariamente grandes entre elementos de código relevantes y relacionados, como una variable declarada en la parte superior de un método que no se utiliza hasta 50 líneas después.
Intención oculta: la categoría más amplia de funciones, variables, nombres y números cuyo propósito no es intuitivo ni queda claro en el contexto.
Expresión booleana complicada: flujos lógicos intrincados, como los que contienen dobles negativos o cadenas complejas de
Código inteligente: código que funciona, pero que sustituye un lenguaje personalizado difícil de seguir en situaciones para las que ya existen soluciones integradas y otras soluciones convencionales que se entienden ampliamente.
Los abusadores orientados a objetos (o abusadores con orientación a objetos) son olores de código que no aplican completa o correctamente los principios de diseño orientado a objetos. Por ejemplo, las sentencias switch son útiles en la programación procedimental, pero deberían evitarse en la programación orientada a objetos.
Algunos ejemplos de abusadores orientados a objetos son:
Clase alternativa con diferentes interfaces: clases que realizan funciones similares usando nombres de métodos completamente diferentes.
Legado rechazado: subclases que no necesitan ni utilizan métodos heredados.
Sentencias switch (también conocidas como Complejidad condicional, también conocidas como Conmutadores repetidos): exactamente la misma sentencia switch duplicada en la base de código.
Campo temporal: una variable que se crea cuando no suele ser necesaria, normalmente solo se usa en determinadas circunstancias.
El principal beneficio de entender los olores del código es que reconocerlos permite una refactorización más eficaz: la práctica de actualizar el código fuente sin modificar su comportamiento o funcionalidad externa. Ordenar el código de forma rutinaria mediante refactorización es esencial para reducir la deuda técnica y facilitar mejoras y adiciones rápidas y efectivas a su base de código con el tiempo.
La refactorización puede entenderse en gran medida como el proceso de identificar y abordar los olores del código. Su objetivo es realizar correcciones antes de que afecten negativamente a la funcionalidad, momento en el que el proceso se parece más a depurar que a refactorizar.
Las plataformas modernas de ingeniería agéntica, como IBM Bob, suelen ofrecer sugerencias de refactorización automatizadas en tiempo real. Gracias a una amplia formación sobre los olores de código comunes en el contexto de las bases de código reales (en lugar de memorizar las definiciones del olor de código), las capacidades de refactorización de código de IA de estas plataformas permiten a los desarrolladores escalar su producción sin un aumento correspondiente en el código generado por la IA que será difícil de entender y modificar más adelante.
Acelere la entrega de software con IBM Bob™, su socio de IA para un desarrollo seguro y orientado a la intención.
Desarrolle, implemente y gestione aplicaciones de IA más rápido con herramientas preparadas para la empresa.
Reinvente los sistemas heredados con una modernización inteligente de la IA.