Cómo Depurar Código

Depurar no es mirar el código con más intensidad. Es reducir un campo de sospechosos hasta que solo una explicación sobreviva. Este es el flujo de trabajo que uso cuando el mensaje de error es horrible, la fecha límite está cerca y adivinar resulta tentador.

🎙️ Publicado y grabado: ·

01Reproduce antes de reparar

Un bug que no puedes provocar es un rumor. Escribe la ruta más corta y exacta desde un inicio limpio hasta el fallo: entrada, comando, entorno, resultado esperado, resultado real. Después ejecútala dos veces. Si la ruta no es fiable, registra qué cambia entre ejecuciones antes de tocar el código.

# Weak report: "upload is broken"
# Useful reproduction:
1. Start app with an empty data directory
2. Upload a file named report.final.csv, 0 bytes
3. Click Import once
Expected: "empty file" validation
Actual: TypeError: Cannot read properties of undefined (reading 'trim')
Mi regla
No empieces con una corrección. Empieza con una receta que falle. Una reproducción limpia ya es la mitad del diagnóstico, porque convierte una discusión sobre posibilidades en un evento observable.

02Lee la última línea útil primero

Las trazas largas invitan al pánico y a la lectura de arriba abajo. Resiste ambas cosas. Las primeras líneas suelen describir el wrapper que detectó el crash. Empieza por la excepción final, luego sube hasta el primer frame que pertenece a tu código. Los internos de la librería son testigos, no suelen ser la escena del crimen.

Traceback (most recent call last):
  File "app.py", line 41, in <module>
    total = price * quantity
TypeError: can't multiply sequence by non-int of type 'float'

# Inspect the operands, not the multiplication operator:
print(repr(price), type(price), repr(quantity), type(quantity))
# '12.50' is text → convert at the input boundary:
price = float(raw_price)

El mensaje dice que el runtime vio texto donde tu modelo mental veía un número. Corrige la conversión donde los datos entran al sistema, no con un cast aleatorio junto al crash.

03Corta el espacio de búsqueda a la mitad

Cuando una petición cruza un navegador, una API, un worker y una base de datos, revisar cada línea es un derroche. Pon una observación cerca del medio. ¿El valor ya está mal ahí? Quédate con la mitad culpable y descarta la inocente. Repite. Esto es búsqueda binaria aplicada a la causalidad.

# Boundary probes, not twenty random print statements
client payload  ✓ {"count": 3}
API input       ✓ count=3
queue message   ✗ {"count": ""}
worker input      {"count": ""}

# The defect lives between API input and queue message.
# Now bisect only that serializer path.
Opinión firme
"Leerlo todo hasta que algo parezca sospechoso" no es un método. Coloca sondas en las fronteras y sigue partiendo a la mitad. El hábito funciona con código, despliegues, configuración e incluso commits históricos; para búsqueda en historial de Git, consulta Git.

04Registra estado, identidad y tiempo

Un log útil responde qué operación, con qué entradas, llegó a qué estado y cuánto tardó. "Llegué aquí" no responde ninguna de esas preguntas. Añade un ID de petición o job para poder seguir una operación a través de la salida intercalada. Registra decisiones en las fronteras; no narres cada línea.

# Bad
print("got here")

# Useful and searchable
logger.info("invoice_send", extra={
  "invoice_id": invoice.id,
  "customer_id": customer.id,
  "attempt": attempt,
  "elapsed_ms": elapsed_ms,
})

Nunca registres contraseñas, tokens, cookies ni datos completos de pago. Más logs no son automáticamente más evidencia. Una inundación cambia tiempos, oculta el evento y puede crear un incidente de seguridad por sí sola.

05Detente donde la realidad diverge

Un breakpoint es valioso cuando tienes una pregunta. Detente justo antes de que se use el primer valor incorrecto, inspecciona las variables locales y la pila de llamadas, luego avanza paso a paso sobre la operación sospechosa más pequeña. Los breakpoints condicionales son mejores que detenerse dentro de un bucle diez mil veces.

# Break only on the failing order, not every order:
condition: order.id == "ord_8472"

# Watch the invariant you believe:
order.total >= 0

# Typical discovery:
subtotal = 18.00
credit   = 25.00
total    = -7.00
Usa el instrumento correcto
Los logs explican fallos que no puedes pausar. Un debugger explica el estado local que puedes reproducir. No fuerces incidentes de producción en un flujo de breakpoints, y no entierres un bug determinista local bajo logging permanente.

06Construye una reproducción mínima

Copia el camino que falla a un caso pequeño y desechable, luego elimina entradas, dependencias y configuración de una en una. Ejecuta después de cada eliminación. La última eliminación que hace desaparecer el fallo identifica un ingrediente necesario. Mínimo significa que cada línea restante se ha ganado su lugar.

# Production symptom:
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x96 in position 14

# Minimal proof: the file is Windows-1252, not UTF-8
raw = b"July \x96 August"
raw.decode("utf-8")       # reproduces
raw.decode("cp1252")      # "July – August"

# Repair: detect/declare the source encoding at ingestion,
# then normalize to UTF-8 once.

No pegues la aplicación entera en un issue y lo llames reproducción. La reducción es la investigación; el caso pequeño es su resultado.

07Explícaselo a un pato de goma

Di lo que el código debe hacer, una operación a la vez, sin usar verbos vagos como "maneja" o "procesa". Nombra el valor y su tipo en cada frontera. En el momento en que tu explicación se salte un paso o se apoye en "obviamente", inspecciona ese paso.

# "It checks whether the cache has the user" hides the bug.
if cached_user:
    return cached_user

# Spoken precisely:
# "An empty dictionary means a cached user with no fields,
#  but this branch treats it as no cached value."
if cached_user is not None:
    return cached_user
Por qué funciona
El pato no aporta nada. Ese es el punto. Convertir una historia interna difusa en afirmaciones explícitas expone la suposición oculta antes de que otra persona sea arrastrada al problema.

08Atrapa Heisenbugs sin ahuyentarlos

Un Heisenbug cambia cuando lo observas. Añadir un log lo hace desaparecer; el modo debug lo hace fiable; una máquina nunca lo ve. Sospecha de races, estado no inicializado, relojes, caché, límites de recursos o código que depende accidentalmente del orden de iteración. Preserva los tiempos antes de añadir instrumentación pesada.

# Real intermittent symptom:
FileNotFoundError: [Errno 2] No such file or directory: 'result.tmp'

Thread A: write result.tmp → rename result.json
Thread B: delete result.tmp during cleanup

# Step-by-step repair:
1. Correlate both actions with file ID + monotonic timestamp
2. Reproduce under repeated/concurrent load
3. Make ownership explicit; cleanup ignores in-flight files
4. Rename atomically only after close/fsync
5. Add a stress test that ran thousands of iterations

Mi postura: añadir sleeps no es una corrección de concurrencia. Solo mueve la ventana de la race y le envía el bug a un cliente más lento.

09Demuestra la corrección, no la historia

Antes de editar, haz que la reproducción falle. Después de editar, haz que la misma reproducción pase. Luego revierte o desactiva la edición y observa cómo falla de nuevo cuando sea práctico. Ese último paso atrapa el caso vergonzoso donde un reinicio, una caché obsoleta o datos de test cambiados arreglaron el síntoma por ti.

# The three observations
before patch   → FAIL: duplicate invoice sent
with patch     → PASS: one invoice sent
patch reverted → FAIL: duplicate returns

# Keep the smallest failing case as a regression test.
# Also test adjacent boundaries: zero, one, many; retry; timeout.
Un parche no es evidencia
"Parece arreglado" significa que la verificación no fue diseñada. Declara qué observación refutaría tu explicación, y ve a hacer esa observación.

10El checklist para cuando estás bloqueado a las 2 a.m.

Úsalo en orden. Es deliberadamente aburrido; aburrido le gana a ingenioso cuando estás cansado.

□ Can I reproduce it from a clean start?
□ What changed: code, config, data, dependency, environment?
□ What is the exact final error and first frame in my code?
□ Which value first differs from the expected value?
□ Can I halve the remaining search area?
□ Do logs include identity, state, and time without secrets?
□ Would a conditional breakpoint answer a specific question?
□ Can I remove half the reproduction and keep the failure?
□ Could observation be changing timing or order?
□ Did the same case fail before, pass after, and become a test?
□ Did I remove temporary logs, flags, sleeps, and debug access?

La velocidad de depuración viene de negarse a adivinar. Reproduce, reduce, inspecciona, explica, demuestra. Las herramientas cambian; esa secuencia no.

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.