Git Bisect y herramientas avanzadas de depuración
Cómo usar git bisect, breakpoints condicionales y técnicas sistemáticas de depuración para encontrar bugs en minutos en lugar de horas.

La parte más costosa de un bug es encontrarlo. Una vez sabes qué línea está mal, la corrección suele ser obvia. Sin embargo, la mayoría de los desarrolladores depuran mirando fijamente el código, añadiendo console.logs y esperando que algo salte. Estas técnicas no escalan bien — funcionan para bugs triviales, pero se desmoronan cuando el problema abarca varios archivos, varios commits o varios servicios.
Existen herramientas de depuración sistemática para convertir una búsqueda de 4 horas en un proceso de 15 minutos. La más subutilizada de todas es git bisect.
Git Bisect: búsqueda binaria en el historial
Cuando un bug existe ahora pero no existía la semana pasada, git bisect encuentra el commit exacto que lo introdujo. Realiza una búsqueda binaria a través del historial de commits, reduciendo el espacio de búsqueda a la mitad en cada paso.
# Start bisecting
git bisect start
# Mark the current commit as broken
git bisect bad
# Mark a known-good commit (e.g., last week's release)
git bisect good v2.3.0
# Git checks out a middle commit — test it
# If the bug exists:
git bisect bad
# If the bug does NOT exist:
git bisect good
# Repeat 5-7 times for 100+ commits
# Git narrows down to the exact problem commitPara 1000 commits, bisect necesita como máximo 10 pasos (log₂ 1000 ≈ 10). Compáralo con revisar commits manualmente uno por uno.
Bisect automatizado
Cuando tienes una prueba que reproduce el bug, bisect puede ejecutarse de forma totalmente automática:
# Automated bisect — runs a test script at each step
git bisect start HEAD v2.3.0
git bisect run npm test -- --grep "payment calculation"
# Or with a custom script
git bisect run ./scripts/check-bug.sh#!/usr/bin/env bash
# check-bug.sh — exit 0 = good, exit 1 = bad
set -euo pipefail
npm run build 2>/dev/null
# Test the specific behavior that's broken
result=$(node -e "
const { calculateTotal } = require('./dist/pricing');
const total = calculateTotal([{ price: 10, qty: 3 }], 0.1);
process.exit(total === 27 ? 0 : 1);
")Escribe el script de prueba para que devuelva 0 si es "bueno" y un valor distinto de cero si es "malo". Git bisect hace el resto — vuelves y encuentras el commit exacto.
Puntos de interrupción condicionales
La mayoría de los desarrolladores colocan breakpoints y avanzan paso a paso por cada iteración. Para un bucle que se ejecuta 10,000 veces donde el bug ocurre en la iteración 8,437, avanzar paso a paso no es viable.
// Instead of a regular breakpoint, use a conditional one
// In Chrome DevTools: right-click breakpoint → "Edit breakpoint"
// Condition: item.id === 'problematic-id'
// Or use programmatic breakpoints
function processItems(items: Item[]) {
for (const item of items) {
// This breakpoint only triggers when the condition is true
if (item.price < 0) {
debugger; // Only pauses on the problematic item
}
const result = calculateDiscount(item);
applyResult(result);
}
}// ❌ Console.log bombing — noisy and manual
function processOrder(order: Order) {
console.log('order', order);
console.log('items', order.items);
console.log('total', order.total);
order.items.forEach((item, i) => {
console.log(`item ${i}`, item);
console.log(`price ${i}`, item.price);
});
}
// ✅ Targeted conditional logging
function processOrder(order: Order) {
// Only log when the total doesn't match expected
const expected = order.items.reduce((sum, i) => sum + i.price * i.qty, 0);
if (Math.abs(order.total - expected) > 0.01) {
console.table(order.items);
console.log('Expected:', expected, 'Got:', order.total);
debugger;
}
}Análisis de stack traces
Cuando un error incluye un stack trace, leerlo sistemáticamente ahorra más tiempo que adivinar. Los stack traces se leen de abajo hacia arriba — la causa raíz está abajo, el síntoma arriba.
// Error: Cannot read property 'name' of undefined
// at formatUser (src/formatters.ts:42) ← symptom
// at processUsers (src/handlers.ts:18) ← where it's called
// at fetchAndProcess (src/api.ts:67) ← the data source
// at handleRequest (src/server.ts:23) ← entry point
// The bug is likely in fetchAndProcess or handleRequest
// — something returns undefined where a user was expected.
// formatUser is innocent — it received bad input.Para los stack traces asíncronos que pierden contexto entre awaits, usa el flag --async-stack-traces en Node.js o marca la casilla "Async" en Chrome DevTools.
// Preserve context in async error handling
async function fetchUser(id: string): Promise<User> {
try {
const response = await api.get(`/users/${id}`);
return response.data;
} catch (error) {
// Wrap with context instead of re-throwing bare error
throw new Error(
`Failed to fetch user ${id}: ${error instanceof Error ? error.message : 'Unknown error'}`,
{ cause: error }
);
}
}La opción { cause: error } (ES2022) preserva la cadena de errores original, dándote tanto el contexto de negocio como los detalles técnicos.
Proceso de depuración sistemático
Depurar al azar desperdicia tiempo. Un proceso estructurado converge hacia la respuesta más rápido.
interface DebuggingProcess {
steps: [
"1. Reproduce — can you trigger the bug reliably?",
"2. Isolate — what's the smallest input that causes it?",
"3. Narrow — binary search the code (bisect, comment out halves)",
"4. Hypothesize — form a theory about the root cause",
"5. Test — verify the theory with a targeted experiment",
"6. Fix — change the minimum code necessary",
"7. Verify — confirm the fix AND that nothing else broke",
];
}El paso 2 — aislar — es donde la mayoría de los desarrolladores se saltan etapas. Reducir "la app se bloquea cuando hago este flujo" a "esta función devuelve null cuando se le pasa un array vacío" elimina el 90% del ruido.
# Isolate network issues with curl
# Instead of "the API is broken," narrow it down:
curl -v https://api.example.com/health # Server reachable?
curl -v https://api.example.com/users/1 # Specific endpoint?
curl -v -H "Authorization: Bearer $TOKEN" https://api.example.com/users/1 # Auth issue?Depuración de memoria y rendimiento
Los bugs de rendimiento requieren herramientas diferentes. La pestaña Performance de Chrome DevTools y --inspect de Node.js cubren la mayoría de los escenarios.
// Quick performance measurement
function measureExecution<T>(label: string, fn: () => T): T {
const start = performance.now();
const result = fn();
const duration = performance.now() - start;
if (duration > 100) {
console.warn(`⚠️ ${label} took ${duration.toFixed(2)}ms`);
}
return result;
}
// Memory leak detection pattern
function detectLeaks() {
const before = process.memoryUsage().heapUsed;
// Run the suspected operation multiple times
for (let i = 0; i < 1000; i++) {
suspectedLeakyFunction();
}
// Force garbage collection (run node with --expose-gc)
if (global.gc) global.gc();
const after = process.memoryUsage().heapUsed;
const leaked = after - before;
if (leaked > 1024 * 1024) {
console.warn(`Potential leak: ${(leaked / 1024 / 1024).toFixed(2)} MB`);
}
}Depuración en producción
Depurar en producción requiere técnicas no invasivas. No puedes colocar breakpoints ni añadir console.logs en un servidor de producción.
// Structured logging — queryable in log aggregation tools
import { logger } from './logger';
async function processPayment(payment: Payment) {
const correlationId = crypto.randomUUID();
logger.info('payment.started', {
correlationId,
amount: payment.amount,
currency: payment.currency,
userId: payment.userId,
});
try {
const result = await gateway.charge(payment);
logger.info('payment.completed', {
correlationId,
transactionId: result.id,
duration: result.duration,
});
return result;
} catch (error) {
logger.error('payment.failed', {
correlationId,
error: error instanceof Error ? error.message : 'Unknown',
stack: error instanceof Error ? error.stack : undefined,
});
throw error;
}
}El correlationId vincula todas las entradas de log de una sola operación. Cuando un usuario reporta "mi pago falló", buscas en los logs por correlationId y ves toda la línea de tiempo de la operación.
Puntos clave
git bisectencuentra commits de regresión en minutos — automatízalo con un script de prueba para una búsqueda binaria por el historial sin esfuerzo- Los breakpoints condicionales ganan al
console.log— se activan solo en el caso problemático en lugar de avanzar paso a paso por miles de iteraciones - Lee los stack traces de abajo hacia arriba — la causa raíz está abajo, el síntoma arriba
- Aísla antes de investigar — reduce el problema a la entrada más pequeña que lo reproduzca antes de sumergirte en el código
- Usa logging estructurado en producción — los
correlationIds permiten trazar una sola operación a través de servicios - Envuelve los errores con contexto —
{ cause: error }preserva toda la cadena de errores para la depuración


