skip to content

When a suite discovers its extensions by scanning instead of an explicit list, what breaks at debugging time?

level: seniorimportance: should knowfreq 34%

answer

  1. Convention finds it; nobody wrote it down
  2. Ask what actually got registered
  3. Scan order is not a guarantee
  4. Silent absence beats loud failure, badly
  5. Print the resolved set at start-up

basics

~20 s

Scanning makes the active set invisible: no file names it, call order follows the scan, and a stray implementation on the search path runs everywhere while a missing one goes unnoticed. Publishing the resolved set restores traceability.

solid answer

~50 s

Discovery means the framework finds implementations by convention — a marked type, a naming rule, a directory it walks — instead of a list someone wrote. It scales contribution well: adding an extension is adding a file, and no shared registration file becomes a merge battleground. The cost lands when a run misbehaves. **You cannot read the wiring**, because nothing in source names what is active. **Order is emergent**, decided by the scan and liable to change with a directory listing or a build layout. **Presence is accidental** and **absence is silent** — a stray implementation runs everywhere, a missing one takes its behaviour away without failing anything. The mitigation is to make the resolved set observable: record what was found and in what order at start-up, fail when a required extension resolved to nothing, and sort by a declared priority rather than by scan order.

code

pseudocode · 13 lines
pseudocode
# discovery: nothing in source names the participants
found = scan_declared_root_for(CaseObserver)

# make the outcome of the scan an observable fact
record("resolved extensions", [(e.name, e.origin) for e in found])

# turn silent absence into an early, loud failure
missing = required_names - {e.name for e in found}
if missing:
    fail_run("required extensions not resolved: " + missing)

# order by a declared priority, never by scan order
run_order = sort_by_declared_priority(found)

go deeper

for a junior

Know the two ways a framework learns about its extensions: someone lists them, or the framework searches for them by a naming or marking convention. Be able to say which one the suite you work on uses.

for a middle

Explain the trade honestly: discovery removes a shared file everyone must edit, and in exchange the active set stops being readable in source. Describe how a scan ends up deciding call order, and why that order is not a promise.

for a senior

Show a diagnosis, not an opinion. Given a run where an extension did not fire, say how you establish what was resolved, in what order and from where — and how you make a missing required extension fail the run early instead of passing quietly.

for a principal

Own the default across many suites: whether teams may add run-wide behaviour by dropping in a file, what the platform must publish about the resolved set, and how ambiguity is settled. The tradeoff is contribution friction against a run whose behaviour anyone can read.

## Two ways a framework learns what to run A suite's extensions have to be found before they can be called, and there are only two families of answer. **Explicit wiring**: somebody writes the participants down. A list in a source file, an entry in a configuration document, a call that adds an implementation to a registry. The set is a text you can read, review and diff. **Discovery**: the framework searches for implementations by convention — a marked type, a naming rule, a directory it walks, anything on the search path that satisfies the contract. Nobody writes the participants down; adding one is adding a file. Discovery is genuinely attractive on a large suite. Adding behaviour stops requiring an edit to a file every team touches, which removes a standing merge conflict and a review bottleneck. Contributions get cheaper, and cheap contribution is why frameworks reach for it. | | Explicit wiring | Discovery | | --- | --- | --- | | Where the set lives | in a file you can read | implied by what is on the search path | | Cost of adding one | edit a shared file | add a file | | Visible in review | yes, as a diff line | no, unless you know the convention | | Ordering | as written | as the scan returns | | Missing extension | fails to resolve at the named entry | silently absent | | Duplicate implementations | a visible second entry | whichever the scan hits first | ## What breaks when something goes wrong The costs are all deferred to the moment a run does not do what you expected. 1. **You cannot read the wiring.** No file names what is active, so answering *which extensions ran?* means reproducing the scan rather than reading a list. A reviewer looking at a change cannot see that it added run-wide behaviour. 2. **Order is emergent.** Whatever the scan returns first runs first. That order can change with a directory listing, a packaging change, or a rearranged build, and none of those look like behaviour changes in review. Any extension that depends on running before another is depending on an accident. 3. **Presence is accidental.** An implementation left on the search path — vendored in for one experiment, pulled in transitively by something else — is active everywhere. Nothing declared it and nothing rejects it. 4. **Absence is silent.** The counterpart is worse. A marker that was renamed, a file that landed outside the scanned root, a build that excluded a directory: the extension simply is not there, the run stays green, and the behaviour it provided quietly stopped. Explicit wiring turns the same mistake into an unresolved entry at start-up. 5. **Provenance is hard.** Even having established that an extension ran, saying *where it came from* means tracing what put it on the path. ## Making discovery debuggable The fix is not to abandon discovery; it is to make its result a first-class, observable fact: - **Publish the resolved set.** At start-up, record the extensions that were found, in the order they will be called, with where each was loaded from. This one habit answers most incident questions in seconds and costs a few lines. - **Assert the required ones.** Let the run declare a small list of extensions it must have and fail the run at start-up if any resolved to nothing. That converts silent absence into a loud, early failure. - **Order explicitly.** Do not depend on scan order. Have the contract carry a declared priority, or a stated *runs-after* relationship, and sort by it. Scan order is a coincidence, not an interface. - **Fail on ambiguity.** Two implementations of a contract that admits only one should stop the run and name both. Silently picking one makes behaviour a function of a directory listing. - **Scope the scan.** Search a declared root that your suite owns, not everything reachable. A narrow scan makes accidental presence far less likely. - **Generate a manifest.** For the strongest form, have a build step write the resolved, ordered list to a committed artefact and fail when the generated file differs from the committed one. Discovery stays the source of truth, and a new extension shows up as a reviewable diff line. ## How to answer the diagnosis version When an extension that should have run did not fire, the order of questions is fixed and it is not the extension's own logic. First, **was it resolved at all**, and from where? Second, **in what position** did it sit relative to the others? Third, **did it throw** and get swallowed by the isolation policy? Only after those three does reading its body pay. Teams that skip to the body lose hours to code that never ran — and a suite that cannot answer question one without a debugger has a diagnosability defect, whatever the merits of its discovery mechanism.

  • How would you keep discovery and still let a code review show what is active?
    Generate the resolved list as a build artefact and commit it. The scan remains the source of truth, a generation step writes the ordered set it found, and a difference between the generated file and the committed one fails the build. A reviewer then sees a new run-wide extension as a line in a diff instead of a file whose effect nobody correlates with behaviour.
  • Two implementations of the same contract are discovered. What should the framework do?
    Fail at start-up and name both, with where each came from. Silently picking one by scan order makes behaviour depend on a directory listing, and the loser's absence is invisible. If more than one is legitimate, the contract should say so and define the calling order explicitly through a declared priority rather than through whatever the scan returned first.

saying these in an interview costs you the question

  • Treats scan order as a stable ordering
  • Cannot say which extensions a run actually loaded
  • Adds a marker and never verifies it was found
  • Lets a missing required extension pass quietly
  • Debugs the extension body before confirming it ran