5. Cómo identificar duplicación de lógica en el código

El primer paso para aplicar el principio DRY es reconocer dónde ocurre la duplicación. Muchas veces la repetición pasa inadvertida hasta que enfrentamos un defecto o necesitamos incorporar una mejora. En esta sección veremos técnicas, señales y herramientas que ayudan a identificar duplicaciones antes de que se conviertan en un problema grave.

Adoptaremos una mirada práctica enfocada en proyectos escritos en Java, aunque las recomendaciones son aplicables a cualquier lenguaje orientado a objetos.

5.1 Clasificación de duplicaciones

Detectar duplicaciones es más sencillo cuando podemos clasificarlas. Las principales categorías son:

  • Duplicación exacta: se copia un bloque completo sin modificarlo.
  • Duplicación estructural: el flujo de sentencias es el mismo, pero cambian nombres de variables o constantes.
  • Duplicación semántica: la intención es idéntica, aunque la implementación varíe.
  • Duplicación de datos: claves, constantes o reglas se mantienen en múltiples archivos de configuración.

5.2 Señales en la base de código

Algunas pistas habituales indican que hay duplicación de lógica:

  • Métodos con nombres similares como `calcularTotal`, `calcularTotal2`, `calcularTotalConDescuento`.
  • Comentarios que comienzan con "igual que en..." o "copiado de...".
  • Bloques condicionales casi idénticos en distintos servicios.
  • Constantes repetidas en varias clases (`0.21`, `3600`, `DEFAULT_TIMEOUT`).
  • Tests que repiten el mismo escenario en distintos paquetes.

5.3 Revisiones de código y programación en pareja

Las revisiones de código son un espacio ideal para identificar duplicaciones tempranas. La persona revisora puede señalar similitudes con otras partes del sistema, mientras que la programación en pareja permite detectar patrones repetidos mientras se escribe el código.

5.4 Uso de análisis estático

Herramientas como SonarQube o SpotBugs incluyen detectores de duplicación textual y estructural. Configurarlas en la integración continua genera reportes automáticos que alertan sobre la repetición de bloques de código.

Algunos IDEs, como IntelliJ IDEA, ofrecen inspecciones que resaltan fragmentos similares dentro del proyecto, facilitando la refactorización inmediata.

5.5 Búsquedas dirigidas

Los comandos `rg`, `grep` o `findstr` permiten localizar patrones de texto repetidos. Buscar por firmas de métodos o cadenas literales ayuda a detectar duplicaciones semánticas. Ejemplos:

rg "IVA" src/main/java
rg "if \\(status == Status.ACTIVO\\)" -g"*.java"

Estas búsquedas se pueden automatizar en scripts de verificación previa a los commits para prevenir que la duplicación llegue a la rama principal.

5.6 Métricas orientadas a DRY

Las métricas de duplicación calculan el porcentaje de código repetido. Mantenerlo por debajo del 5% es un objetivo razonable para proyectos medianos. Otra métrica útil es la longitud promedio de los métodos: secciones extensas suelen contener lógica duplicada o responsabilidades mezcladas.

5.7 Detección mediante pruebas

Las pruebas son una herramienta indirecta para detectar duplicación. Si múltiples tests replican la misma configuración o datos, probablemente el código de producción también esté duplicado. Extraer métodos de utilidad en los tests puede revelar refactorizaciones necesarias en el código real.

5.8 Indicadores en las historias de usuario

Historias con descripciones muy similares o que reutilizan exactamente el mismo criterio de aceptación pueden dar origen a duplicaciones. Documentar la motivación del cambio y referenciar el código afectado ayuda a evitar que se creen variantes del mismo flujo en distintas capas.

5.9 Ejemplo en Java: detectando duplicación semántica

Observemos un servicio que calcula bonos para distintos perfiles. A simple vista parece correcto, pero repite lógica con pequeñas diferencias:

class BonoService {
    double calcularBonoGerente(Empleado empleado) {
        if (!empleado.activo()) {
            return 0.0;
        }
        double base = empleado.salario() * 0.2;
        if (empleado.antiguedad() >= 5) {
            base += empleado.salario() * 0.05;
        }
        if (empleado.resultados().esSobresaliente()) {
            base += 5000;
        }
        return base;
    }

    double calcularBonoLiderTecnico(Empleado empleado) {
        if (!empleado.activo()) {
            return 0.0;
        }
        double base = empleado.salario() * 0.18;
        if (empleado.antiguedad() >= 5) {
            base += empleado.salario() * 0.05;
        }
        if (empleado.resultados().esSobresaliente()) {
            base += 3000;
        }
        return base;
    }
}

Hay una duplicación semántica: ambos métodos repiten los mismos chequeos y solo cambian porcentajes y bonos adicionales. Podemos revelar esa duplicación aplicando pruebas y extrayendo los parámetros variables:

class BonoService {
    double calcularBono(Empleado empleado, PoliticaBono politica) {
        if (!empleado.activo()) {
            return 0.0;
        }
        double base = empleado.salario() * politica.porcentajeBase();
        if (empleado.antiguedad() >= politica.antiguedadMinima()) {
            base += empleado.salario() * politica.porcentajeAntiguedad();
        }
        if (empleado.resultados().esSobresaliente()) {
            base += politica.bonoExtra();
        }
        return base;
    }
}

record PoliticaBono(double porcentajeBase,
                    int antiguedadMinima,
                    double porcentajeAntiguedad,
                    double bonoExtra) {}

El refactor permite reutilizar el mismo flujo para distintos perfiles. Las políticas se definen como datos y el código de negocio queda centralizado.

5.10 Checklist de preguntas al revisar

Para detectar duplicaciones durante una revisión de código, asegúrate de responder:

  • ¿Existe otro método que resuelva la misma tarea?
  • ¿Se repiten condiciones, cálculos o constantes?
  • ¿Estamos duplicando validaciones tanto en el backend como en el frontend?
  • ¿Los tests comparten preparación de datos que podría extraerse?

5.11 Buenas prácticas para prevenir duplicaciones futuras

Aunque identificar duplicaciones es fundamental, prevenirlas es más eficiente. Algunas medidas son:

  1. Crear componentes reutilizables desde el primer uso significativo, sin esperar a que existan tres copias.
  2. Documentar patrones de código aprobados por el equipo y promover su reutilización.
  3. Establecer catálogos de APIs y servicios compartidos para evitar que cada equipo implemente su versión.
  4. Incorporar reglas de linter que alerten sobre nombres sospechosos o constantes repetidas.
  5. Realizar retrospectivas enfocadas en deuda técnica para identificar áreas donde la duplicación se promueva.

5.12 Resumen y próximos pasos

Identificar la duplicación es un trabajo continuo que requiere observación, herramientas y colaboración. Al mantener la lógica centralizada, aumentamos la calidad y reducimos el coste de mantener el sistema.

En el siguiente tema veremos técnicas de refactorización basadas en abstracciones para eliminar la duplicación detectada.