Depuración Sistemática

El Bucle Científico de Depuración y la Traza de Pila

Depurar no es probar cosas al azar con System.out.println: es un ciclo de investigación empírico. Una traza de pila se lee de arriba hacia abajo hasta encontrar el primer marco de nuestro propio código.

1 Reproducir 2 Hipótesis 3 Observar 4 Corregir causa raíz 5 Verificar
Hacé clic en un paso del ciclo

1. Reproducir: conseguir que el error ocurra de forma consistente y controlada, no depender de la suerte.

2. Hipótesis: formular una explicación concreta y comprobable de la causa probable, antes de tocar código.

3. Observar: usar breakpoints, watches y logs para confirmar o descartar la hipótesis con evidencia real.

4. Corregir causa raíz: arreglar el origen del problema, nunca el síntoma superficial.

5. Verificar: confirmar que el error desapareció y que no se rompió nada más.

Simulador: Anatomía de la Traza de Pila Hacé clic en cada parte para entender la traza
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Order.subtotal()"
    at OrderCalculator.total(OrderCalculator.java:14) ◄ marco relevante
    at Main.main(Main.java:9)

El tipo de excepción indica la clase de error: NullPointerException señala que se intentó usar una referencia nula.

El mensaje explicativo detalla exactamente qué invocación falló: Order.subtotal() sobre un objeto Order nulo.

El call stack se lee de arriba hacia abajo: el primer marco de nuestro propio código (OrderCalculator.java:14) es donde hay que empezar a investigar.

Herramientas del IDE

La Cabina de Mando del Depurador

Un depurador congela el tiempo de ejecución de la JVM y permite examinar el estado de la memoria en vivo, sin modificar el archivo fuente. Probá los controles de stepping sobre el código.

OrderCalculator.java
? public int total(Order order) { int subtotal = 0; for (Item item : order.getItems()) { subtotal += item.getPrecio(); if (itemCount > 10) { subtotal = applyBulkDiscount(subtotal); } } return subtotal;
Breakpoint incondicional Breakpoint condicional: itemCount > 10
Dentro de applyBulkDiscount(int subtotal)
Variables Locales
orderOrder@0x4f2a
itemCount3
subtotal0.0
Watches
order.getItems().size()3

Detenido en breakpoint · línea 3

Diseño Débil

Museo Gráfico de Code Smells

Un code smell no impide compilar: es una señal de que el código será frágil y costoso de mantener a futuro. Elegí uno para ver su extracto de código y su prescripción técnica.

Un método que hace demasiado, línea tras línea, imposible de leer de un vistazo.

public void procesarPedido(Pedido pedido) {
    // valida cliente...
    // valida stock...
    // calcula subtotal...
    // aplica descuentos...
    // calcula impuestos...
    // ... 150 líneas más ...
    // genera factura y notifica
}
Prescripción: Extract Method — dividir cada bloque de responsabilidad en un método propio, con nombre descriptivo.

Modelar conceptos del dominio con tipos primitivos sueltos en vez de objetos con significado propio.

String telefono;
double precio;
String moneda;
Prescripción: Introducir PhoneNumber y Money — objetos de valor que validan y agrupan su propio comportamiento.

Un método que está más interesado en los datos de otra clase que en los propios.

// dentro de Factura
double total() {
    return cliente.getPrecioBase()
        * cliente.getFactorDescuento()
        + cliente.getRecargoEnvio();
}
Prescripción: Move Method — trasladar el cálculo a Cliente, que es dueño real de esos datos.

Una clase que absorbió responsabilidades de todo el sistema.

class SistemaManager {
    // 50 métodos: login(), facturar(),
    // enviarEmail(), generarReporte()...
    // 3.000 líneas en total
}
Prescripción: Dividir por responsabilidad única: AuthService, FacturacionService, NotificacionService...
Refactorización Segura

El Cinturón de Seguridad de JUnit

Refactorizar es alterar la estructura interna sin cambiar el comportamiento observable. Regla de oro: nunca refactorizar sobre código sin tests en verde.

Paso 1
Suite JUnit en Verde

Garantía de que el comportamiento actual está cubierto por tests que pasan.

Paso 2
Un Solo Cambio Pequeño

Aplicar un único cambio estructural mínimo, por ejemplo Extract Method con el atajo del IDE.

Paso 3
Correr la Suite de Nuevo
Sigue en verde: paso completado, commit atómico.
Se puso en rojo: revertir de inmediato con git checkout o Ctrl+Z. No acumular errores.
Pirámide de la Fatalidad (4 niveles)
public String obtenerDescuento(Cliente cliente) {
    if (cliente != null) {
        if (cliente.isActivo()) {
            if (cliente.getAntiguedad() > 5) {
                if (cliente.getSaldo() > 0) {
                    return "20%";
                }
            }
        }
    }
    return "0%";
}
Guard Clauses (retornos tempranos)
public String obtenerDescuento(Cliente cliente) {
    if (cliente == null) return "0%";
    if (!cliente.isActivo()) return "0%";
    if (cliente.getAntiguedad() <= 5) return "0%";
    if (cliente.getSaldo() <= 0) return "0%";

    return "20%";
}
Caso de Estudio

De Código Espagueti a Código Limpio

Una función de facturación con múltiples responsabilidades y números mágicos, transformada en un diseño modular, testeable y autodocumentado.

Antes 45 líneas · 3 niveles de anidación
public double calcularTotal(List<Object> items, boolean flag) {
    double aux = 0;
    for (int i = 0; i < items.size(); i++) {
        Object it = items.get(i);
        if (it != null) {
            if (flag == true) {
                double precio = (double) ((Map) it).get("precio");
                int cantidad = (int) ((Map) it).get("cantidad");
                aux = aux + (precio * cantidad);
            }
        }
    }
    aux = aux + (aux * 0.21);
    if (aux > 1000) {
        aux = aux - (aux * 0.05);
    }
    return aux;
    // ... 30 líneas más de validaciones ...
}
Después 12 líneas · sin números mágicos
private static final double IVA_GENERAL = 0.21;
private static final double UMBRAL_DESCUENTO = 1000.0;

public double calcularTotal(List<ItemFactura> items) {
    double subtotal = calcularSubtotal(items);
    double conIva = subtotal + (subtotal * IVA_GENERAL);
    return aplicarDescuento(conIva);
}

private double calcularDescuentoVolumen(double total) {
    return total > UMBRAL_DESCUENTO
        ? total * 0.05
        : 0;
}