Skip to content

← Orientation and the Gate step 10 of 13

Medium Primitives

Placement 3/6: exception boundary design

Placement diagnostic, 3 of 6. About seven minutes. If this one is opaque, T6 (Failure by Design) is where you start.


A parser that lets int() raise ValueError at its caller has leaked its implementation. The caller now has to know that this config format is parsed with int(), and the day you swap in Decimal you break every except clause in the codebase. A boundary translates: it raises errors in the vocabulary of its own domain, and it attaches the original as the cause so the traceback still tells you the truth.

ConfigError is given. Write both halves:

def parse_line(line: str) -> tuple[str, int]:

Parse "key=value" into (key, int(value)), stripping surrounding whitespace from both. Split on the first = only. Raise ConfigError(line) when:

  • there is no = at all, or the key is empty or whitespace — raise it without a cause, because nothing failed underneath you; this line simply is not config;
  • the value is not an integer — raise it from the ValueError that int() produced, because something genuinely did fail underneath you and the reader deserves to see it.
def solve(lines: list[str]) -> dict[str, list[str]]:

The boundary. Parse every line; a bad line must never abort the batch. Return {"ok": [...], "errors": [...]} where:

  • ok entries are f"{key}={value}" with the parsed key and int, so " spaced = 42 " becomes "spaced=42" and "n=+0" becomes "n=0".
  • error entries are f"{line}|ConfigError|{cause}" where line is the raw input line and cause is the class name of exc.__cause__, or "-" when there is no cause.

Why the tests can see __cause__

Because raise X from Y is not decoration. It sets X.__cause__ = Y, which is what makes the traceback print

ValueError: invalid literal for int() with base 10: 'x'

The above exception was the direct cause of the following exception:

ConfigError: bad config line: 'a=x'

Omit the from and Python still records __context__ implicitly and prints “During handling of the above exception, another exception occurred” — the wording that tells a reader on a bad night that your error handler crashed, rather than that your error handler was doing its job. The two are not interchangeable, and __cause__ is the one you can assert on.

Production consequence

except Exception: pass around a parse loop is how a config file silently loses half its keys. The shape here — a narrow domain exception, raised from the cause, caught at exactly one boundary that collects rather than aborts — is what lets a batch job report “14 of 400 rows rejected, here is why” instead of dying on row 3.