Debugging patterns

A systematic procedure

The following is intended as a sequentially ordered schematic rather than a script to be followed strictly in every situation. (If a close-reading of a stack trace immediately indicates that a problem is limited in scope and simple to fix, for example, obviously just go fix the problem immediately.) Experience—both general and domain-specific—develops the intuition for what point in the script at which to enter for systematic debugging.

  1. Tighten the feedback loop
    • Automate reproduction
  2. Isolate suspect code / component
    • RTFEM
    • Bisection: statically (in code), dynamically (in control flow), diachronically (in change history)
    • Automate bisection and reproduction together if cost-effective and feasible (git bisect run)
  3. Precisely identify the elements of state that violate expectations
    • Inspect local state: Inputs and any proximate modifications
    • Inspect non-local state: object state, function preconditions, compiler flags, data shape, dependency versions, language / framework magic, etc.
    • Compare to a reference implementation (another component, same component elsewhere in time)
  4. Perform retrograde analysis to identify root causes and permissive background conditions

Mixed strategies

Systematic application of unsystematic modifications to implementation, state, execution order, or execution form, in order to provoke the system into revealing more of itself (when used as a disclosure stratagem: see “Beat the grass to startle the snake”) and/or catalyze lateral thinking (when used as an oblique strategy: see “Steelmanning divination”). Sometimes referred to as “banging at it”.