Los malos olores en el código no son errores. Un programa con errores se bloquea, devuelve resultados erróneos o falla en las pruebas. Un programa con malos olores puede funcionar perfectamente durante años, pero cada cambio cuesta más de lo debido, cada nueva funcionalidad conlleva riesgos inesperados y cada intento de refactorización revela dependencias desconocidas. Los malos olores en el código son características estructurales que predicen problemas futuros: no provocan un fallo inmediato, pero hacen que cada cambio futuro sea más difícil, lento y peligroso de lo necesario.
El término fue popularizado por Martin Fowler y Kent Beck en Fowler's Refactorización: mejora del diseño del código existente (1999), que catalogó 22 "malos olores" de código y asoció cada uno con una técnica de refactorización correspondiente. Ese catálogo sigue siendo la referencia canónica, y los "malos olores" que Fowler denominó (Método largo, Clase divina, Código duplicado, Envidia de características, Cambio divergente, Cirugía de escopeta, entre otros) aparecen en los conjuntos de reglas de SonarQube, las herramientas de análisis estático y las listas de verificación de revisión de código en toda la industria actual.
Limpiar los olores del código
SMART TS XL Ayuda a mapearlos y corregirlos en sistemas complejos.
MÁS INFORMACIÓN¿Qué es un "olor a código"?
Un código defectuoso es una característica superficial del código fuente que sugiere un problema estructural o de diseño más profundo. El código compila, pasa las pruebas y produce la salida correcta, pero algo en su estructura dificulta su lectura, extensión o modificación segura. La definición de Fowler es: «una indicación superficial que generalmente corresponde a un problema más profundo en el sistema».
Los malos olores en el código no son violaciones en el mismo sentido que un error de sintaxis o una aserción fallida. Son indicadores, patrones que los desarrolladores experimentados reconocen como señales de advertencia, incluso cuando no se observa ningún fallo inmediato. El peligro reside en su carácter acumulativo: un único método extenso en un código de 10 000 líneas es una molestia menor. Cientos de métodos extensos, lógica duplicada repartida en decenas de módulos y clases omnipotentes en el centro del gráfico de dependencias conforman un sistema que se ha vuelto realmente difícil de modificar de forma segura.
Mal olor del código vs. errores vs. deuda técnica
Estos tres conceptos están relacionados pero son distintos, y confundirlos conduce a una mala priorización:
| Concepto | Definición | ¿Fallo inmediato? | Como encontrar |
|---|---|---|---|
| Error | Código que produce un comportamiento incorrecto | Sí, las pruebas fallan, los usuarios informan de errores. | Pruebas, monitorización, registros de errores |
| olor a código | Patrón estructural que predice problemas futuros | No, el código se ejecuta correctamente. | Revisión de código, análisis estático |
| Deuda técnica | El costo acumulado de los atajos y las malas decisiones del pasado | No, pero se acumula con el tiempo. | Métricas, análisis de complejidad, estimaciones del esfuerzo de refactorización |
Los malos olores en el código son el mecanismo mediante el cual se acumula la deuda técnica. Cada método largo añadido al código fuente es una unidad de deuda técnica contraída; su pago de intereses es el tiempo extra que cada desarrollador futuro dedica a comprenderlo, y cada cambio futuro dedica tiempo a evitar los efectos secundarios de su tamaño.
¿Qué es un "mal olor de código" en SonarQube?
SonarQube clasifica los problemas de código en tres categorías: errores (definitivamente incorrectos), vulnerabilidades (problemas de seguridad) y el código huele (Problemas de mantenibilidad). Los problemas de código de SonarQube se corresponden directamente con el catálogo de Fowler e incluyen reglas para métodos largos (que superan los umbrales de líneas configurables), bloques duplicados, demasiados parámetros, puntuaciones de complejidad cognitiva complejas, manejo de errores inexistente e infracciones de acoplamiento arquitectónico. Las reglas de problemas de código en SonarQube constituyen la operacionalización automatizada más utilizada en la industria de la taxonomía original de Fowler.
Los olores del código de Martin Fowler: La taxonomía clásica
Los 22 "malos olores" de código originales de Fowler, organizados por categoría, siguen siendo la referencia estándar. El conjunto de reglas de todas las principales herramientas de análisis estático se deriva de esta taxonomía.
| Categoría | Huele a código |
|---|---|
| Hinchazonescódigo que ha crecido hasta alcanzar un tamaño inmanejable | Método largo, clase grande, obsesión con las primitivas, lista de parámetros larga, agrupaciones de datos |
| Abusadores de la orientación a objetosmal uso de los principios de la programación orientada a objetos | Sentencias Switch, Campo temporal, Legado rechazado, Clases alternativas con diferentes interfaces |
| Impedimentos del cambiodificultar el cambio | Cambio divergente, cirugía de escopeta, jerarquías de herencia paralela |
| Artículos prescindiblescódigo innecesario | Comentarios (excesivos), Código duplicado, Clase perezosa, Clase de datos, Código muerto, Generalidad especulativa |
| acopladoresacoplamiento excesivo | Envidia de rasgos, intimidad inapropiada, cadenas de mensajes, intermediario |
Comprender a qué categoría pertenece un problema ayuda a priorizar su solución: los elementos que generan problemas y los que impiden el cambio se correlacionan directamente con un alto coste de refactorización; los acopladores se correlacionan directamente con la fragilidad arquitectónica; los elementos prescindibles son los más seguros de eliminar.
Los errores más comunes en el código: Guía rápida
| Olor a código | Lo que parece | Riesgo primario |
|---|---|---|
| Código duplicado | La misma lógica aparece en varios lugares. | Las correcciones de errores deben aplicarse en todas partes; las copias divergen con el tiempo. |
| Método largo | Métodos que superan las 20-30 líneas con múltiples responsabilidades. | Alta carga cognitiva; difícil de evaluar el comportamiento aislado. |
| Clase Dios / Clase Grande | Una clase que lo hace todo | Cada cambio de característica afecta a la misma clase; conflictos de fusión, fragilidad |
| Lista de parámetros larga | Métodos que aceptan 4 o más parámetros | Es fácil pasar valores incorrectos; es difícil leer los sitios de llamada. |
| Característica Envidia | Un método que utiliza los datos de otra clase más que los suyos propios. | Acoplamiento estrecho; un cambio en una clase rompe la otra. |
| Cambio divergente | Una clase modificada por muchas razones diferentes | Viola el principio de responsabilidad única; efectos secundarios impredecibles |
| Cirugía de escopeta | Un cambio requiere modificaciones en muchas clases. | Coste de cambio elevado; fácil de pasar por alto una instancia |
| Código muerto | Código que nunca se llama ni se alcanza | Confunde a los desarrolladores; se acumula con el paso de los años; complica la migración. |
| Obsesión primitiva | Utilizar tipos básicos (cadenas, enteros) en lugar de objetos de dominio. | Validación dispersa por todas partes; escasa expresividad. |
| Grupos de datos | El mismo grupo de campos pasó junto repetidamente | Debe ser un objeto de dominio; indica que falta abstracción. |
| Generalidad especulativa | Código escrito para necesidades futuras imaginadas | Complejidad innecesaria; nadie entiende por qué está ahí. |
| Manejo de errores inconsistente | Capturas silenciosas, estrategias de excepción variables | Los fallos pasan desapercibidos; la depuración lleva mucho más tiempo. |
Definiciones y ejemplos de "mal olor" en el código
Código duplicado
El problema de código más común y costoso en sistemas grandes. La duplicación surge del desarrollo mediante copiar y pegar, de la presión del tiempo y de equipos que trabajan de forma aislada, resolviendo el mismo problema de manera independiente. La consecuencia inmediata es un alto costo de mantenimiento: cada cambio en la lógica compartida debe aplicarse a cada copia.
Java
// ServiceA -- discount calculation
double calculateDiscount(double amount) {
if (amount > 1000) return amount * 0.1;
return 0;
}
// ServiceB -- same logic, copied and forgotten
double computeDiscount(double value) {
if (value > 1000) return value * 0.1;
return 0;
}
Cuando cambia la regla de negocio (el umbral pasa a ser 1500 y la tasa pasa a ser 12 %), una copia se actualiza y la otra no. Ahora, dos módulos discrepan en la lógica de negocio fundamental, y la discrepancia sale a la luz en producción durante una auditoría, en lugar de durante las pruebas.
Solución: Extraer la lógica compartida en una única función, clase de utilidad o biblioteca compartida a la que hagan referencia ambos llamadores.
Método largo
Un método que ha trascendido su propósito original al asumir responsabilidades adicionales con el tiempo. La carga cognitiva de leer un método de 200 líneas es cualitativamente diferente a la de leer veinte métodos de 10 líneas, no solo cuantitativamente. Los métodos extensos son difíciles de probar porque realizan demasiadas tareas como para evaluarlas de forma aislada, y difíciles de comprender porque el lector debe mantener todo el contexto de ejecución en la memoria de trabajo.
Umbral de detecciónLos métodos de más de 20-30 líneas requieren revisión; de más de 50 líneas, la refactorización casi siempre está justificada. En COBOL, los párrafos que superan las 100 instrucciones son el equivalente.
pitón
class OrderProcessor:
def process_order(self, order):
# Validate order -- 40 lines
# Calculate discounts -- 30 lines
# Update inventory -- 25 lines
# Send notification emails -- 20 lines
# Generate invoice -- 35 lines
# 150+ lines total
pass
Cada responsabilidad en este método debe ser una clase o función independiente. Agruparlas implica que cualquier actualización futura de facturación, inventario o notificaciones podría desestabilizar todo el flujo de procesamiento de pedidos.
Clase de Dios
Una clase que ha acumulado responsabilidades en múltiples dominios, violando el Principio de Responsabilidad Única de forma tan grave que se convierte en el centro de gravedad de un código base: todo depende de ella, y cambiar cualquier cosa en ella requiere comprenderlo todo sobre ella.
Señal de detección: Una clase con más de 20-30 métodos públicos, o una cuyo nombre contiene “Manager”, “Processor”, “Handler”, “Utils” o “Helper” aplicados a múltiples dominios no relacionados.
Cambio divergente
Una clase que se modifica por muchos motivos distintos y sin relación entre sí. Cada vez que cambia el esquema de la base de datos, se edita esta clase. Cada vez que cambian las reglas de precios, se edita esta clase. Cada vez que cambia el formato de notificación, se edita esta clase. Esta clase tiene demasiadas responsabilidades y debería dividirse.
Definición: Una clase que cambia constantemente por diferentes razones. Lo opuesto a la cirugía de escopeta.
Cirugía de escopeta
Un único cambio conceptual requiere modificaciones en numerosas clases. Modificar un tipo impositivo exige alterar un cálculo en el servidor, una validación en el frontend, un disparador de base de datos, un proceso por lotes y una consulta de informes, todo ello en cinco lugares distintos. Si falta alguno, el comportamiento será inconsistente.
sql
-- Tax logic duplicated across queries
SELECT amount * 0.05 FROM invoices;
SELECT amount * 0.05 FROM payments;
SELECT amount * 0.05 FROM reports;
Cambiar 0.05 a 0.07 ahora requiere encontrar cada ocurrencia en los archivos SQL, los procedimientos almacenados y el código de la aplicación.
Característica Envidia
Un método que dedica más tiempo a utilizar los datos y métodos de otra clase que los suyos propios. Esto indica que probablemente el comportamiento pertenece a la otra clase.
Java
// In ReportGenerator -- envious of Customer's data
double calculateCustomerRating(Customer customer) {
return customer.getOrderCount() * customer.getAverageOrderValue()
/ customer.getDaysSinceRegistration();
}
// This logic belongs in Customer, not ReportGenerator
Código muerto
Código que existe en el repositorio pero que nunca se ejecuta en producción. Este código obsoleto se acumula con el tiempo a medida que se eliminan, reemplazan o reestructuran funcionalidades sin borrar el código antiguo. Genera confusión en las revisiones de código, dificulta la incorporación de desarrolladores al código fuente, complica el análisis de migraciones y, ocasionalmente, se reactiva accidentalmente.
Detección: Herramientas de análisis estático, incluyendo SonarQube, Knip (para TypeScript/JavaScript) y SMART TS XL Identificar funciones inaccesibles, métodos no llamados y variables no utilizadas en todo el código fuente.
Violaciones del principio DRY
El principio DRY (Don't Repeat Yourself) establece que cada fragmento de conocimiento debe tener una única representación inequívoca dentro de un sistema. Las infracciones de DRY son la causa principal del código duplicado, la acumulación de datos y muchos escenarios de "cirugía de escopeta". Cuando la lógica de negocio se representa en múltiples lugares, estas representaciones inevitablemente divergen. DRY es el principio; el código duplicado es la señal que indica la infracción.
pitón
# DRY violation: same validation logic in three places
def validate_email_in_registration(email):
return "@" in email and "." in email
def validate_email_in_profile_update(email):
return "@" in email and "." in email
def validate_email_in_checkout(email):
return "@" in email and "." in email
# DRY-compliant: one function, three callers
def is_valid_email(email):
return "@" in email and "." in email
Umbrales de detección: ¿Cuándo deja de ser sospechoso el código?
La detección de malos olores en el código requiere umbrales medibles. A continuación se muestran las métricas más utilizadas y los valores que indican un mal olor que requiere atención:
| Métrico | Qué mide | Umbral de advertencia | Umbral crítico |
|---|---|---|---|
| Complejidad ciclomática | Número de ramas de decisión en un método | Por encima de 10 | Por encima de 20 |
| Método Longitud (líneas) | Número de líneas en un método/función | Por encima de 20 | Por encima de 50 |
| Recuento de parámetros | Número de parámetros que acepta un método | Por encima de 4 | Por encima de 7 |
| Duración de la formación | Número de líneas en una clase | Por encima de 200 | Por encima de 500 |
| Tasa de duplicación | Porcentaje de código duplicado | Por encima de 3% | Por encima de 10% |
| Complejidad cognitiva | Qué tan difícil es entender el código | Por encima de 15 | Por encima de 25 |
| Acoplamiento aferente (Ca) | Número de clases que dependen de esta clase | Por encima de 15 | Por encima de 30 |
| Acoplamiento eferente (Ce) | Número de clases de las que depende esta clase | Por encima de 15 | Por encima de 30 |
Estos umbrales son configurables en SonarQube, y la mayoría de las plataformas de análisis estático permiten reglas personalizadas basadas en estas métricas. Las clases y los métodos con umbrales críticos son los objetivos de refactorización de máxima prioridad: son las fuentes más probables de futuros defectos y los componentes más costosos de mantener.
Herramientas para la detección de errores de programación
La detección automatizada es el único método escalable para identificar problemas de código en grandes bases de código. La revisión manual detecta solo una fracción de lo que encuentran las herramientas automatizadas, y no es escalable para sistemas heredados con millones de líneas de código.
| Idioma primario | Lo que detecta | |
|---|---|---|
| SonarQube / SonarCloud | Java, Python, JS/TS, C# y más | Taxonomía completa de olores de Fowler, puntos críticos de seguridad, duplicaciones |
| Checkstyle + PMD | Java | Violaciones de estilo, duplicaciones, métricas de complejidad |
| ESLint + typescript-eslint | JavaScript, mecanografiado | Funciones largas, complejidad, código sin usar |
| Pylint + Radón | Python | Índice de complejidad, estilo y mantenibilidad |
| ReSharper / Rider | C# | Código redundante, métodos largos, problemas de acoplamiento |
| Clippy | Herrumbre | Violaciones idiomáticas, patrones comunes que son malos olores de código en Rust. |
| CódigoClima | Multi-Lenguaje | Puntuación de complejidad, duplicación y mantenibilidad |
| SMART TS XL | COBOL, JCL, Java, Python, RPG, SQL, .NET | Duplicación entre lenguajes, código muerto, acoplamiento, deriva de dependencias |
Malos olores en el código de Rust son detectados principalmente por Clippy, que impone patrones idiomáticos de Rust. Los olores más comunes específicos de Rust incluyen la clonación innecesaria, el mal uso de unwrap() en rutas de producción, expresiones de coincidencia excesivamente anidadas y funciones que deberían devolver Result pero usa pánicos en su lugar.
Mal olor del código y deuda técnica: la conexión
La deuda técnica es el costo acumulado de decisiones pasadas que priorizaron la velocidad sobre la calidad. Los defectos de código son el mecanismo mediante el cual esa deuda se manifiesta en la estructura del código. La relación es directa: cada defecto de código no corregido es una unidad de deuda técnica, y la tasa de interés es el tiempo adicional que cada cambio futuro deberá dedicar a solucionarlo.
Como se describe en el contexto de análisis de impacto para la gestión de cambios de softwareLos problemas estructurales que indican los malos olores del código, como el acoplamiento excesivo, la lógica duplicada y la acumulación de código muerto, aumentan directamente el alcance de cada cambio porque dificultan aislar a qué afectará cada cambio.
Explica la deuda técnica en términos de malos olores en el código.Si un código fuente tiene un 40 % de duplicación, cada corrección de errores cuesta 1.4 veces más de lo que debería. Si la clase de procesamiento principal es una clase omnipotente de la que todo depende, cada nueva funcionalidad requiere comprender y probar la clase completa. Si el manejo de errores es inconsistente, cada incidente en producción requiere más tiempo de investigación porque las señales de fallo no son fiables. La deuda técnica no es abstracta, sino la suma de estas ineficiencias acumulativas.
Las investigaciones de CISQ demuestran sistemáticamente que los desarrolladores dedican entre el 30 % y el 40 % de su tiempo a solucionar problemas de deuda técnica en lugar de desarrollar nuevas funcionalidades. La densidad de código problemático es la medida más directa de la cantidad de deuda acumulada.
Cómo SMART TS XL Detecta problemas de código a escala empresarial.
Las herramientas individuales como SonarQube y Clippy operan en un solo lenguaje. En entornos empresariales donde los programas COBOL escriben en conjuntos de datos que leen los servicios Java, donde los flujos de trabajos JCL invocan programas en varios lenguajes y donde la misma lógica de negocio se ha duplicado de forma independiente en tres sistemas diferentes escritos en tres décadas distintas, las herramientas de un solo lenguaje no pueden tener una visión completa.
SMART TS XL, análisis de código estático Detecta simultáneamente problemas de código en todos los lenguajes del entorno: lógica duplicada entre un archivo de copia COBOL y una clase de utilidad Java, código muerto en programas RPG que no son invocados por ningún trabajo JCL, patrones de clases "Dios" en programas COBOL donde un solo párrafo realiza el trabajo de cincuenta, y patrones inconsistentes de manejo de errores en la frontera entre lenguajes.
El mapeo de dependencias de aplicaciones Esta capacidad identifica los problemas arquitectónicos que las herramientas individuales a nivel de archivo no pueden detectar: qué componentes tienen el mayor acoplamiento aferente (los más utilizados, con mayor riesgo de que se produzcan fallos al modificarlos), dónde existen dependencias circulares entre módulos que deberían ser independientes y dónde se ha mantenido lógica empresarial duplicada de forma independiente en diferentes sistemas sin que ninguna de las copias sea consciente de la otra.
El análisis de impacto Esta capacidad convierte los problemas de código en acciones concretas: antes de refactorizar cualquier componente con alta dependencia, el análisis de impacto enumera todos los componentes dependientes que necesitan ser probados, validados o actualizados. Esto transforma la «parálisis por refactorización» que experimentan los equipos en grandes bases de código problemáticas en un programa de remediación estructurado y con alcance definido, donde cada cambio tiene un alcance determinado en lugar de un riesgo desconocido.
Para los equipos que realizan modernización heredada En los programas, el análisis del código mal hecho es la base del plan de modernización: el código muerto se elimina antes de que comience la migración (reduciendo el alcance), la lógica duplicada se consolida en implementaciones canónicas, los componentes de mayor acoplamiento se modernizan al final (después de que se haya abordado todo lo que depende de ellos) y las clases "God Classes" se descomponen antes de convertirlas a un nuevo lenguaje, porque convertir una "God Class" a Java produce una "God Class" en Java.
Cómo abordar los problemas de código: un marco de priorización
No todos los problemas de código justifican una refactorización inmediata. El enfoque correcto es la priorización basada en riesgos:
Prioridad 1: Olores en componentes con alta tasa de cambio. El código que cambia con frecuencia y presenta alta complejidad o acoplamiento genera la mayor cantidad de defectos. Estos componentes son los más costosos por cambio y generan la mayor cantidad de incidencias en producción. Solucione estos problemas primero.
Prioridad 2: Olores en los límites arquitectónicos. Las clases todopoderosas y los componentes de alto acoplamiento de los que todo depende son los más peligrosos de modificar, pero también los más costosos de dejar sin corregir. Estos requieren un análisis de impacto exhaustivo antes de la refactorización.
Prioridad 3, código duplicado entre sistemas. Cuando la misma lógica de negocio existe en varios sistemas, los cambios deben coordinarse simultáneamente en todas las copias. Consolidar esta duplicación reduce la sobrecarga de coordinación y evita divergencias.
Prioridad 4, Eliminación de código obsoleto. El código muerto es la categoría más segura para abordar: eliminarlo no altera el comportamiento, solo revela dependencias previamente ocultas. Debe eliminarse antes de cualquier migración o conversión para evitar esfuerzos innecesarios al convertir código que nunca se utilizará.
Prioridad 5: Olores de estilo y estructurales en zonas de bajo riesgo. Los métodos extensos y las listas de parámetros en código estable y con baja tasa de cambios pueden abordarse de manera oportuna, cuando el código cercano necesita cambiar por otros motivos, refactorizando al mismo tiempo los problemas circundantes.
La disciplina de detectar, medir y abordar los problemas de código de forma sistemática, en lugar de reaccionar ante una falla en producción, es lo que distingue a los equipos de desarrollo que mantienen la velocidad de entrega a lo largo del tiempo de aquellos que la reducen progresivamente a medida que sus sistemas crecen.