Debugging Tooling
16 min
Beyond reading tracebacks, three tools cover most real debugging:
print debugging (fast, universal, but requires guessing in advance
what's worth printing), assertions (cheap sanity checks that fail
LOUDLY and immediately, right where an assumption breaks), and
interactive debuggers (like Python's pdb, or your editor's
breakpoint UI — pause execution and inspect EVERYTHING at that moment).
def process(data):
assert len(data) > 0, "process() called with empty data"
return data[0] * 2
An assertion failing tells you EXACTLY which assumption was violated, immediately, at the exact line — much faster than tracing a confusing downstream error back to its actual root cause several function calls later.
Without the assertion, calculate_average([]) would raise a confusing ZeroDivisionError deep inside the function -- the assertion catches the REAL problem (empty input) at the door, with a message that explains what actually went wrong.
A minimal manual call tracer is the same idea behind print-debugging,
formalized slightly:
def trace_calls(func, *args):
result = func(*args)
return result, f"called with args: {args}"
result, log = trace_calls(lambda x, y: x + y, 2, 3)
print(log) # called with args: (2, 3)
print(result) # 5
A real debugger does this automatically for EVERY function call, without you writing any wrapper — that's the core tradeoff: print/manual tracing needs no setup and works anywhere, but only shows what you thought in advance to log; a debugger needs a bit more setup but lets you inspect anything, on demand, after the fact.
Write `trace_calls(func, *args)`: call `func(*args)`, and return a tuple `(result, "called with args: {args}")` — a minimal manual version of what a debugger's call tracer does.
Write `assert_invariant(condition, message)`: raise `AssertionError(message)` if `condition` is False; otherwise return True.
What's a key advantage of using an interactive debugger (breakpoints, step-through) over scattering print() statements?
You can use assertions to catch violated assumptions immediately and clearly, and understand the tradeoffs between print debugging, assertions, and interactive debuggers.