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: 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.
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.
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.
Order@0x4f2a30.03Detenido en breakpoint · línea 3
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
} Modelar conceptos del dominio con tipos primitivos sueltos en vez de objetos con significado propio.
String telefono;
double precio;
String moneda; 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();
} Una clase que absorbió responsabilidades de todo el sistema.
class SistemaManager {
// 50 métodos: login(), facturar(),
// enviarEmail(), generarReporte()...
// 3.000 líneas en total
} 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.
Garantía de que el comportamiento actual está cubierto por tests que pasan.
Aplicar un único cambio estructural mínimo, por ejemplo Extract Method con el atajo del IDE.
public String obtenerDescuento(Cliente cliente) {
if (cliente != null) {
if (cliente.isActivo()) {
if (cliente.getAntiguedad() > 5) {
if (cliente.getSaldo() > 0) {
return "20%";
}
}
}
}
return "0%";
} 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%";
} 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.
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 ...
} 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;
}