12. Refactorización (Refactoring)

La refactorización es una de las prácticas más importantes de Extreme Programming (XP), intrínsecamente ligada al desarrollo iterativo y a la mejora continua. Consiste en reestructurar el código existente, alterando su estructura interna sin cambiar su comportamiento externo. Es el "cómo" se limpia el código después de haberlo hecho funcionar.

12.1. Qué es y por qué es esencial

Martin Fowler, una autoridad en la materia, define la refactorización como "un cambio en la estructura interna del software para hacerlo más fácil de entender y más barato de modificar, sin cambiar su comportamiento observable".

En el contexto de XP, es esencial por varias razones:

  • Combate la deuda técnica: A medida que un proyecto evoluciona, es natural que el código se vuelva más complejo y desordenado. La refactorización es la herramienta principal para pagar esta "deuda técnica" de forma proactiva, evitando que el sistema se degrade con el tiempo.
  • Habilita el cambio: Un código limpio y bien estructurado es más fácil de entender, extender y modificar. Al refactorizar constantemente, el equipo se asegura de que el costo del cambio se mantenga bajo y constante a lo largo del ciclo de vida del proyecto.
  • Mejora la comprensión: Un código refactorizado es más legible y autoexplicativo. Esto facilita la incorporación de nuevos miembros al equipo y la programación en parejas (Pair Programming), ya que el código comunica su propósito con mayor claridad.
  • Complementa a TDD: La refactorización es el tercer paso del ciclo Red-Green-Refactor de TDD. Sin este paso, TDD solo garantizaría que el código funciona, pero no que está bien diseñado. Las pruebas de TDD actúan como una red de seguridad que garantiza que la refactorización no introduce errores.

12.2. Mejora continua del diseño del código sin cambiar su comportamiento

El principio fundamental de la refactorización es la separación entre cambiar el comportamiento y cambiar la estructura. Cuando un desarrollador está refactorizando, no debe añadir nuevas funcionalidades. Del mismo modo, cuando está añadiendo una nueva funcionalidad, debe intentar no refactorizar masivamente al mismo tiempo.

El proceso es el siguiente:

  1. Asegurar la red de seguridad: Antes de refactorizar, es crucial tener un conjunto sólido de pruebas automáticas que cubran el comportamiento del código a modificar. Si no existen, se deben escribir primero.
  2. Identificar "code smells": Los "olores de código" son síntomas de un problema en el diseño. Ejemplos comunes son métodos muy largos, clases con demasiadas responsabilidades, código duplicado o nombres de variables poco claros.
  3. Aplicar pequeños cambios atómicos: La refactorización no consiste en reescribir grandes módulos desde cero. Se trata de aplicar una serie de pequeños cambios (refactorings), uno a la vez. Por ejemplo, "renombrar variable", "extraer método" o "mover campo".
  4. Probar después de cada cambio: Después de cada pequeño cambio, se ejecutan las pruebas para verificar que el comportamiento del sistema no se ha alterado. Esto permite detectar y corregir errores de inmediato.

Este enfoque disciplinado garantiza que el diseño del software evolucione de manera segura y sostenible.

12.3. Ejemplos de refactorización

Veamos un ejemplo práctico en Python. La técnica que usaremos es "Extraer Método" (Extract Method), que se aplica cuando un método es demasiado largo y contiene bloques de código que pueden ser agrupados y extraídos a un nuevo método con un nombre descriptivo.

Código ANTES de la refactorización

Imaginemos una función que calcula e imprime los detalles de una factura. Tiene varias responsabilidades mezcladas.


def imprimir_factura(factura):
    total = 0
    print(f"Cliente: {factura['cliente']}")
    print("------------------------")

    # Calcular total
    for item in factura['items']:
        total += item['precio'] * item['cantidad']

    # Aplicar impuestos
    impuestos = total * 0.21
    total_con_impuestos = total + impuestos

    # Imprimir detalles
    print(f"Subtotal: ${total:.2f}")
    print(f"Impuestos (21%): ${impuestos:.2f}")
    print(f"Total a pagar: ${total_con_impuestos:.2f}")

Código DESPUÉS de la refactorización

El código anterior es difícil de leer y probar. Podemos extraer la lógica de cálculo a su propia función.


def calcular_total(items):
    """Calcula el subtotal a partir de una lista de items."""
    total = 0
    for item in items:
        total += item['precio'] * item['cantidad']
    return total

def imprimir_factura(factura):
    """Imprime los detalles de una factura de forma ordenada."""
    print(f"Cliente: {factura['cliente']}")
    print("------------------------")

    subtotal = calcular_total(factura['items'])
    impuestos = subtotal * 0.21
    total_con_impuestos = subtotal + impuestos

    print(f"Subtotal: ${subtotal:.2f}")
    print(f"Impuestos (21%): ${impuestos:.2f}")
    print(f"Total a pagar: ${total_con_impuestos:.2f}")

El nuevo código es más claro. La función `imprimir_factura` ahora se centra en la presentación, mientras que `calcular_total` se encarga de la lógica de negocio. Además, `calcular_total` es ahora una función pura y mucho más fácil de probar de forma aislada.

12.4. Herramientas que ayudan al refactoring

Aunque la refactorización puede hacerse manualmente, los entornos de desarrollo modernos (IDEs) y otras herramientas ofrecen un soporte potente para automatizar estas tareas de forma segura.

  • IDEs (Entornos de Desarrollo Integrado): Son la herramienta más poderosa para la refactorización. Ofrecen menús contextuales para aplicar refactorings automáticamente.
    • PyCharm, IntelliJ IDEA, Eclipse: Permiten hacer "Extract Method", "Rename Variable/Class", "Move Method" y docenas de otras refactorizaciones con unos pocos clics. El IDE se encarga de encontrar todas las referencias y actualizarlas, lo que reduce el riesgo de error humano.
    • Visual Studio Code: Con las extensiones adecuadas para cada lenguaje (por ejemplo, la extensión de Python de Microsoft), también proporciona un soporte robusto para las refactorizaciones más comunes.
  • Linters y Analizadores de Código Estático: Herramientas como SonarQube, ESLint (para JavaScript) o Pylint (para Python) pueden analizar el código e identificar "code smells" automáticamente, sugiriendo oportunidades de refactorización.
  • Herramientas de formateo de código: Herramientas como Prettier, Black (para Python) o gofmt (para Go) aseguran un estilo de código consistente, lo que es una forma básica de refactorización que mejora la legibilidad.

El uso de estas herramientas permite a los equipos de XP aplicar la refactorización de manera rápida, segura y consistente, manteniendo el código limpio y manejable a lo largo del tiempo.