The Scientific Debugging Loop and the Stack Trace
Debugging is not randomly poking at code with System.out.println: it is an empirical investigation cycle. A stack trace reads top to bottom until it hits the first frame of our own code.
1. Reproduce: make the failure happen consistently and on demand, never rely on luck.
2. Hypothesis: state a concrete, testable explanation of the likely cause before touching any code.
3. Observe: use breakpoints, watches, and logs to confirm or discard the hypothesis with real evidence.
4. Fix root cause: repair the origin of the problem, never the superficial symptom.
5. Verify: confirm the failure is gone and that nothing else broke.
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Order.subtotal()"
at OrderCalculator.total(OrderCalculator.java:14) ◄ relevant frame
at Main.main(Main.java:9) The exception type names the error class: NullPointerException means a null reference was used.
The message details exactly which call failed: Order.subtotal() invoked on a null Order object.
The call stack reads top to bottom: the first frame belonging to our own code (OrderCalculator.java:14) is where investigation should start.
The Debugger Cockpit
A debugger freezes the JVM at runtime and lets you inspect live memory state without touching the source file. Try the stepping controls over the code.
Order@0x4f2a30.03Stopped at breakpoint · line 3
Code Smells Gallery
A code smell does not stop the code from compiling: it signals the code will be fragile and costly to maintain later. Pick one to see its code excerpt and technical prescription.
A method that does too much, line after line, impossible to read at a glance.
public void procesarPedido(Pedido pedido) {
// valida cliente...
// valida stock...
// calcula subtotal...
// aplica descuentos...
// calcula impuestos...
// ... 150 líneas más ...
// genera factura y notifica
} Modeling domain concepts with loose primitive types instead of objects with their own meaning.
String telefono;
double precio;
String moneda; A method more interested in another class’s data than its own.
// dentro de Factura
double total() {
return cliente.getPrecioBase()
* cliente.getFactorDescuento()
+ cliente.getRecargoEnvio();
} A class that absorbed responsibilities from the whole system.
class SistemaManager {
// 50 métodos: login(), facturar(),
// enviarEmail(), generarReporte()...
// 3.000 líneas en total
} JUnit’s Safety Belt
Refactoring means changing internal structure without changing observable behavior. Golden rule: never refactor code without tests in the green.
Guarantee that current behavior is covered by passing tests.
Apply a single minimal structural change, e.g. Extract Method via the IDE shortcut.
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%";
} From Spaghetti Code to Clean Code
A billing function with multiple responsibilities and magic numbers, transformed into a modular, testable, self-documenting design.
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;
}