skip to content

In Ruby's debug gem, how do the break and catch commands set breakpoints without editing code, including conditional ones?

level: middleimportance: should knowfreq 36%

answer

  1. break file:line or Class#method
  2. if: for a condition
  3. catch KeyError stops at raise
  4. matches subclasses by ancestor name
  5. break alone lists, delete removes

basics

~20 s

In the debug gem, break sets a breakpoint on a line or a method (break tax_calc.rb:15, break TaxCalculator#tax_on) and accepts if: for a condition; catch KeyError stops when that exception is raised, even if it is rescued later.

solid answer

~40 s

At an `(rdbg)` prompt, `break 15` stops at line 15 of the current file, `break tax_calc.rb:15` names the file, and `break TaxCalculator#tax_on` or `break calc.total_for` stops when that method is called. Adding `if: country == "UK"` makes any of them conditional; `pre:` runs a command before stopping and `do:` runs one and continues without stopping. `catch KeyError` stops at the moment a matching exception is **raised** — before any `rescue` handles it — and matches subclasses, because it compares the pattern with the exception class's ancestors. `break` alone lists breakpoints, `delete 2` removes one and `delete` removes all. `watch @total` stops when an instance variable changes, at a real speed cost. The same keywords work in code: `binding.break(pre: "info")`.

code

bash · 5 lines
bash
$ rdbg tax_report.rb
(rdbg) catch KeyError
(rdbg) break TaxCalculator#tax_on if: country == "UK"
(rdbg) break          # lists both, numbered for delete
(rdbg) continue

go deeper

for a junior

Know that break sets a breakpoint from the prompt on a line or method, and that break alone lists them.

for a middle

Use if: conditions, catch for exceptions at their raise point, and delete to clean up, explaining why catch sees rescued exceptions.

for a senior

Debug without code changes: rdbg plus catch and conditional method breakpoints, do: log points, and awareness of slow watch and location-less conditions.

for a principal

Encourage debugging workflows that need no committed edits, and judge when breakpoints should give way to structured logging.

## Why command breakpoints Putting `binding.break` into a file works, but it means editing and later reverting code, and it cannot express "stop only when this exception happens". The debug gem's **`break`** and **`catch`** commands set breakpoints from the `(rdbg)` prompt, typically right after `rdbg` has stopped at the first line of a script. ## break: lines and methods | Form | Stops | |---|---| | `break 15` | at line 15 of the current frame's file | | `break tax_calc.rb:15` | at that file and line | | `break TaxCalculator#tax_on` | when the instance method is called | | `break calc.total_for` | when `total_for` is called on the object `calc` | | `break` (no argument) | lists all breakpoints with their numbers | Options can be appended to any form: - **`if: <expr>`** — stop only when the Ruby expression is truthy in that frame: `break TaxCalculator#tax_on if: country == "UK"`. - **`pre: <command>`** — run a debug command, then stop: `pre: "info locals"`. - **`do: <command>`** — run a debug command and **continue** without stopping, which turns a breakpoint into a lightweight log point. - **`path: <path>`** — only in files matching a path or `/regexp/`. `break if: <expr>` with no location checks the condition on every line, and the documentation warns it is very slow. ## catch: stop where the exception starts **`catch KeyError`** sets an exception breakpoint. It uses a `:raise` trace, so it stops **at the `raise`**, with the raising frame's locals still intact — before any `rescue` runs and even when some caller will rescue it. Two consequences: - It matches **subclasses**: the pattern is compared with the names of the exception class's ancestors, so `catch StandardError` also stops on `KeyError`. - It can be noisy: libraries raise and rescue exceptions internally, and `catch` stops on those too. Add `if:` or `path:` to narrow it. `catch` accepts the same `if:`, `pre:`, `do:` and `path:` options; `catch` alone lists breakpoints. ## watch and cleanup - **`watch @total`** stops whenever the current scope's instance variable changes value. Like a location-less condition, it is documented as very slow. - **`delete 2`** removes breakpoint 2; **`delete`** removes all of them after confirmation. - `info breakpoints` lists breakpoints and watchpoints together. ## The same options in code `binding.break` accepts **`pre:`** and **`do:`** too. `binding.break(do: "info ;; bt")` prints locals and the stack each time the line runs and keeps going, a quick trace without editing further; `;;` separates commands. ## Chasing the UK tax bug without editing code 1. `rdbg tax_report.rb` — stops at line 1. 2. `catch KeyError` — the lookup `RATES.fetch(country)` will raise. 3. `continue` — stops inside `tax_on` at the `fetch`, with `country` and `net` visible. 4. `bt` shows the path; `p RATES.keys` shows the missing entry. 5. For the next run, `break TaxCalculator#tax_on if: country == "UK"` stops on the bad order only.

  • Your code rescues KeyError three frames up and logs a generic message. Will catch KeyError still stop?
    Yes. `catch` hooks the `raise` event, so it stops in the frame that raised, before Ruby unwinds to any `rescue`. That is its advantage over waiting for the error to surface: the locals that caused it are still there.
  • What is the difference between pre: and do: on a debug gem breakpoint?
    `pre:` runs the given debug command and then stops at the prompt as usual. `do:` runs the command and continues without stopping, so the breakpoint behaves like a log point that prints `info` or `bt` output on every hit.
  • Why does catch StandardError stop far more often than expected?
    It matches every exception whose ancestors include `StandardError`, and it fires at every `raise`, including exceptions that libraries raise and rescue internally. Narrow it with a specific class, `if:`, or `path:` to limit which files count.

saying these in an interview costs you the question

  • catch only fires for exceptions that nobody rescues.
  • catch StandardError ignores subclasses such as KeyError.
  • break TaxCalculator#tax_on needs a binding.break inside the method.
  • do: stops at the prompt after running its command.
  • watch on an instance variable costs nothing at runtime.