Saltar a contenido

Métricas de Software

1. ¿Qué son las Métricas de Software?

Las métricas de software son herramientas cuantitativas para:

  • Medir la calidad del software
  • Mejorar el código y procesos
  • Evaluar objetivamente (no opiniones)
  • Tomar decisiones basadas en datos

Sin métricas, la calidad es subjetiva y difícil de controlar. Con métricas, puedes saber exactamente dónde está el problema.

2. Acoplamiento

2.1 ¿Qué es el Acoplamiento?

El acoplamiento se refiere a cuán dependientes son los módulos/componentes entre sí.

  • Bajo acoplamiento: Los módulos son independientes
  • Alto acoplamiento: Los módulos dependen mucho los unos de los otros

2.2 ¿Por qué es importante?

Con bajo acoplamiento:

    • Puedes cambiar un módulo sin afectar otros
    • El código es más fácil de probar
    • Reutilización de código más fácil
    • Mantenimiento simplificado

Con alto acoplamiento:

  • -Cambiar un módulo rompe otros
  • -Difícil de probar aisladamente
  • -Código frágil
  • Difícil de mantener

2.3 Ejemplo de Acoplamiento

Alto acoplamiento (Malo):

public class Carrito {
    private BaseDatos bd = new BaseDatos();
    private Email email = new Email();
    private Impuesto impuesto = new Impuesto();

    public void procesoCompra(Producto p) {
        // El carrito DEPENDE de 3 clases diferentes
        bd.guardar(p);
        email.enviar("Compra realizada");
        impuesto.calcular(p.getPrecio());
    }
}
// Si cambio Email, debo cambiar también Carrito
// Si cambio BaseDatos, debo cambiar también Carrito

Bajo acoplamiento (Bueno):

public class Carrito {
    private ProcesadorCompra procesador;

    // Inyección de dependencias - el carrito NO necesita saber
    // qué clases concretas usa el procesador
    public Carrito(ProcesadorCompra p) {
        this.procesador = p;
    }

    public void procesoCompra(Producto p) {
        procesador.procesar(p);
    }
}
// Si cambio Email o BaseDatos, Carrito no se ve afectado

3. Cohesión

3.1 ¿Qué es la Cohesión?

La cohesión se refiere a cuán relacionadas están las tareas dentro de un módulo.

  • Alta cohesión: Todos los métodos del módulo trabajan juntos para un propósito claro
  • Baja cohesión: Los métodos hacen cosas no relacionadas

3.2 ¿Por qué es importante?

Con alta cohesión:

  • El módulo tiene una responsabilidad clara
  • Código más comprensible
  • Más fácil de mantener
  • Menos propenso a errores

Con baja cohesión:

  • El módulo hace demasiadas cosas
  • Difícil de entender
  • Difícil de reutilizar
  • Propenso a errores

3.3 Ejemplo de Cohesión

Baja cohesión (Malo):

public class GestorTodo {
    // Cosas no relacionadas

    public void procesarPago() { /* ... */ }

    public void enviarEmail() { /* ... */ }

    public void generarReporte() { /* ... */ }

    public void calcularImpuestos() { /* ... */ }

    public void conectarBaseDatos() { /* ... */ }
}
// ¿Qué es realmente la responsabilidad de esta clase?
// Hace 5 cosas completamente diferentes

Alta cohesión (Bueno):

public class ProcesadorPago {
    // Una responsabilidad clara: procesar pagos

    public void validarTarjeta(Tarjeta t) { /* ... */ }

    public void procesarTransaccion(Pago p) { /* ... */ }

    public void verificarFondos(double cantidad) { /* ... */ }
}

public class NotificadorEmail {
    // Una responsabilidad clara: enviar emails

    public void enviarConfirmacion(Usuario u) { /* ... */ }

    public void enviarRecuperacionPassword(Usuario u) { /* ... */ }
}

4. Complejidad Ciclomática

4.1 ¿Qué es la Complejidad Ciclomática?

La complejidad ciclomática es una métrica que mide cuántos caminos diferentes puede tomar la ejecución de un método.

Desarrollada por Thomas J. McCabe en 1976.

Fórmula: M = E - N + 2P

Donde: - E = número de aristas (conexiones) en el grafo de flujo - N = número de nodos (bloques de código) en el grafo de flujo - P = número de componentes conectados (normalmente 1)

Para propósitos prácticos: Cada estructura de control (if, while, for, switch) suma 1.

4.2 Interpretación

Complejidad Ciclomática    Significado
═════════════════════════════════════════════
1-3                         Muy simple - Fácil de mantener
4-7                         Moderado - Todavía manejable
8-10                        Complejo - Difícil de mantener
>10                         Muy complejo - Refactorizar necesario

4.3 Ejemplo Práctico

Ejemplo 1: Complejidad 1 (Simple)

public int maximo(int a, int b) {
    return a > b ? a : b;
}
// Complejidad = 1 (no hay ramificaciones)
// Un único camino de ejecución

Ejemplo 2: Complejidad 2 (Bajo)

public int maximo(int a, int b) {
    if (a > b) {                    // +1 por el if
        return a;
    } else {
        return b;
    }
}
// Complejidad = 2
// Dos caminos: if verdadero, if falso

Ejemplo 3: Complejidad 4 (Moderado)

public void procesarPedido(Pedido p) {
    if (p != null) {                        // +1
        if (p.tieneProductos()) {           // +1
            if (p.montototal() > 0) {       // +1
                if (cliente.tieneCredito()) {   // +1
                    guardar(p);
                }
            }
        }
    }
}
// Complejidad = 5 (4 ifs + 1 base)
// 5 = 2 - 1 + 2 * 1 - 1 = ... mejor contar los if's

Ejemplo 4: Complejidad Alta (MALO - Necesita Refactorización)

public String validarUsuario(String email, String password, boolean premium) {
    String resultado = "";

    if (email == null) {                                // +1
        resultado = "Email inválido";
    } else if (!email.contains("@")) {                  // +1
        resultado = "Email sin @";
    } else if (password.length() < 8) {                // +1
        resultado = "Password muy corta";
    } else if (!estaRegistrado(email)) {               // +1
        resultado = "Usuario no existe";
    } else if (!verificarPassword(email, password)) {  // +1
        resultado = "Password incorreta";
    } else if (premium && !verificarSuscripcion()) {   // +1
        resultado = "Suscripción vencida";
    } else if (estaBaneado(email)) {                   // +1
        resultado = "Usuario baneado";
    } else {
        resultado = "OK";
    }

    return resultado;
}
// Complejidad = 7 (alto - deberían refactorizar)
// 7 caminos posibles de ejecución
// Muy difícil de probar todas las combinaciones

4.4 ¿Cómo Reducir la Complejidad Ciclomática?

Opción 1: Extraer métodos

// Refactorizado - más legible y testeable
public String validarUsuario(String email, String password, boolean premium) {
    if (!esEmailValido(email)) {
        return "Email inválido";
    }

    if (!esPasswordValida(password)) {
        return "Password inválida";
    }

    if (!esUsuarioValido(email)) {
        return "Usuario inválido";
    }

    if (premium && !esSubscripcionValida()) {
        return "Suscripción vencida";
    }

    return "OK";
}

// Complejidad de validarUsuario = 5 (mejorado)
// Complejidad de cada método individual = 1-2 (fácil)

Opción 2: Usar patrones como Strategy

public String validarUsuario(Usuario u, List<Validador> validadores) {
    for (Validador v : validadores) {
        String error = v.validar(u);
        if (error != null) return error;
    }
    return "OK";
}
// Complejidad baja, fácil agregar nuevos validadores

4.5 ¿Por Qué Importa?

Código con complejidad ciclomática baja: - Más fácil de entender - Más fácil de probar (menos casos de test) - Menos bugs - Más fácil de mantener


5. Relación entre las Tres Métricas

BAJO ACOPLAMIENTO + ALTA COHESIÓN + BAJA COMPLEJIDAD CICLOMÁTICA = CÓDIGO DE CALIDAD

┌──────────────────────────────────────────────────────────┐
│                   CÓDIGO DE CALIDAD                       │
├──────────────────────────────────────────────────────────┤
│ ✓ Bajo acoplamiento                                       │
│   → Módulos independientes                                │
│   → Cambios localizados                                   │
│                                                           │
│ ✓ Alta cohesión                                           │
│   → Una responsabilidad clara por módulo                  │
│   → Código comprensible                                   │
│                                                           │
│ ✓ Baja complejidad ciclomática                            │
│   → Métodos simples y claros                              │
│   → Fáciles de probar y mantener                          │
└──────────────────────────────────────────────────────────┘

Actividades

  • AC905. Se te proporciona el siguiente código:

    public class GestorPedidos {
    
        public void procesarPedido(Pedido pedido) {
            if (pedido == null) {
                System.out.println("Pedido nulo");
                return;
            }
    
            if (!pedido.tieneProductos()) {
                System.out.println("Pedido sin productos");
                return;
            }
    
            double total = pedido.calcularTotal();
    
            if (total > 1000) {
                System.out.println("Descuento 10%");
                total = total * 0.9;
            } else if (total > 500) {
                System.out.println("Descuento 5%");
                total = total * 0.95;
            } else if (total > 100) {
                System.out.println("Descuento 2%");
                total = total * 0.98;
            }
    
            if (esClienteVIP(pedido.getCliente())) {
                total = total * 0.95;
                System.out.println("Descuento VIP aplicado");
            }
    
            if (pedido.requiereEnvio()) {
                double envio = calcularEnvio(total);
                total = total + envio;
    
                if (total > 2000) {
                    total = total - 100;
                    System.out.println("Envío gratis por monto alto");
                }
            }
    
            System.out.println("Total final: " + total);
        }
    }
    

    Tu tarea:

    1. Calcula la complejidad ciclomática (cuenta todos los puntos de ramificación)
    2. Identifica si es demasiado complejo
    3. Refactoriza el código para reducir la complejidad extrayendo métodos
    4. Calcula la nueva complejidad
    5. Explica qué mejoras de mantenibilidad logras

    Entrega: Código refactorizado comentado + documento con tu análisis.

  • AC906. Revisa un proyecto de código que hayas hecho (puede ser de otra asignatura).

    Elige una clase y analiza:

    1. Acoplamiento: - ¿Cuántas clases depende? - ¿Son dependencias reales o podrían reducirse? - ¿Usa inyección de dependencias o instancia directamente?

    2. Cohesión: - ¿Cuál es la responsabilidad principal de la clase? - ¿Todos los métodos contribuyen a esa responsabilidad? - ¿Hay métodos que hacen cosas no relacionadas?

    3. Mejoras: - Propone cómo reducir el acoplamiento - Propone cómo mejorar la cohesión - Refactoriza la clase aplicando tus mejoras

    Entrega: Código original + código refactorizado + documento explicando cambios.