Mal olor del código: qué son y cómo se relacionan con la deuda técnica.

Mal olor del código: qué son y cómo se relacionan con la deuda técnica.

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:

ConceptoDefinición¿Fallo inmediato?Como encontrar
ErrorCódigo que produce un comportamiento incorrectoSí, las pruebas fallan, los usuarios informan de errores.Pruebas, monitorización, registros de errores
olor a códigoPatrón estructural que predice problemas futurosNo, el código se ejecuta correctamente.Revisión de código, análisis estático
Deuda técnicaEl costo acumulado de los atajos y las malas decisiones del pasadoNo, 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íaHuele a código
Hinchazonescódigo que ha crecido hasta alcanzar un tamaño inmanejableMé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 objetosSentencias Switch, Campo temporal, Legado rechazado, Clases alternativas con diferentes interfaces
Impedimentos del cambiodificultar el cambioCambio divergente, cirugía de escopeta, jerarquías de herencia paralela
Artículos prescindiblescódigo innecesarioComentarios (excesivos), Código duplicado, Clase perezosa, Clase de datos, Código muerto, Generalidad especulativa
acopladoresacoplamiento excesivoEnvidia 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ódigoLo que pareceRiesgo primario
Código duplicadoLa misma lógica aparece en varios lugares.Las correcciones de errores deben aplicarse en todas partes; las copias divergen con el tiempo.
Método largoMé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 GrandeUna clase que lo hace todoCada cambio de característica afecta a la misma clase; conflictos de fusión, fragilidad
Lista de parámetros largaMétodos que aceptan 4 o más parámetrosEs fácil pasar valores incorrectos; es difícil leer los sitios de llamada.
Característica EnvidiaUn 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 divergenteUna clase modificada por muchas razones diferentesViola el principio de responsabilidad única; efectos secundarios impredecibles
Cirugía de escopetaUn cambio requiere modificaciones en muchas clases.Coste de cambio elevado; fácil de pasar por alto una instancia
Código muertoCódigo que nunca se llama ni se alcanzaConfunde a los desarrolladores; se acumula con el paso de los años; complica la migración.
Obsesión primitivaUtilizar tipos básicos (cadenas, enteros) en lugar de objetos de dominio.Validación dispersa por todas partes; escasa expresividad.
Grupos de datosEl mismo grupo de campos pasó junto repetidamenteDebe ser un objeto de dominio; indica que falta abstracción.
Generalidad especulativaCódigo escrito para necesidades futuras imaginadasComplejidad innecesaria; nadie entiende por qué está ahí.
Manejo de errores inconsistenteCapturas silenciosas, estrategias de excepción variablesLos 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étricoQué mideUmbral de advertenciaUmbral crítico
Complejidad ciclomáticaNúmero de ramas de decisión en un métodoPor encima de 10Por encima de 20
Método Longitud (líneas)Número de líneas en un método/funciónPor encima de 20Por encima de 50
Recuento de parámetrosNúmero de parámetros que acepta un métodoPor encima de 4Por encima de 7
Duración de la formaciónNúmero de líneas en una clasePor encima de 200Por encima de 500
Tasa de duplicaciónPorcentaje de código duplicadoPor encima de 3%Por encima de 10%
Complejidad cognitivaQué tan difícil es entender el códigoPor encima de 15Por encima de 25
Acoplamiento aferente (Ca)Número de clases que dependen de esta clasePor encima de 15Por encima de 30
Acoplamiento eferente (Ce)Número de clases de las que depende esta clasePor encima de 15Por 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 primarioLo que detecta
SonarQube / SonarCloudJava, Python, JS/TS, C# y másTaxonomía completa de olores de Fowler, puntos críticos de seguridad, duplicaciones
Checkstyle + PMDJavaViolaciones de estilo, duplicaciones, métricas de complejidad
ESLint + typescript-eslintJavaScript, mecanografiadoFunciones largas, complejidad, código sin usar
Pylint + RadónPythonÍndice de complejidad, estilo y mantenibilidad
ReSharper / RiderC#Código redundante, métodos largos, problemas de acoplamiento
ClippyHerrumbreViolaciones idiomáticas, patrones comunes que son malos olores de código en Rust.
CódigoClimaMulti-LenguajePuntuación de complejidad, duplicación y mantenibilidad
SMART TS XLCOBOL, JCL, Java, Python, RPG, SQL, .NETDuplicació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.