Why does misusing a host-language domain notation often produce a worse error than the same mistake against a plain API?
answer
- who prints the message, and in whose words
- names the author never wrote
- the block fails, not the word
- absence has no site to fail at
- error text is part of the contract
basics
~20 sThe host reports the failure in terms of the machinery that implements the notation, not the domain words the author wrote, and it points at the block rather than the offending line. Good messages have to be designed in.
solid answer
~40 sA misuse of a plain API fails at a named member with a named parameter, so the message names things the author wrote. A notation is implemented with types the author never wrote — the block's target, an overload set, intermediate shapes — and the host names those when nothing matches, sometimes pointing at the whole block rather than the offending word. Worse, the commonest misuse is an *omission*, and absence has no site to fail at unless the design makes the result impossible to produce without the word. The fix is deliberate: give illegal shapes named domain types so the leaked names still mean something, author messages where you do check, and test the message text as part of the contract.
go deeper
Recall the asymmetry: a failed call names the member and the parameter you wrote, while a failed notation block often names types you have never seen. Unfamiliar names in a message are a symptom, not your mistake.
Explain the mechanism: implementation types, overload sets and block-level positions are what the host has to report with, so the message is written in the machinery's vocabulary rather than the domain's.
Show you design for it — named illegal shapes, shallow machinery, authored complaints where checks happen, and tests over the message text — and that you say in the review who is doing that work.
Decide the bar: what quality of failure a shared notation must deliver before other teams may depend on it, and whether owning that bar is cheaper than keeping the plain API.
## Where a plain API's error comes from When a call to a plain configuration API does not fit, the failure has an obvious site and an obvious vocabulary. The site is the call. The vocabulary is the member's name and its parameter's name and type — all things the author typed or could see in the signature. Even a blunt mismatch message is intelligible, because every name in it appears in the source on screen. That intelligibility is not a property of the tooling. It is a property of the surface being made of ordinary declarations at the place where the mistake was made. ## Why a notation's message names things nobody wrote A notation is not made only of the words the author sees. Underneath there are types that carry the block, types that represent a partially built result, sets of overloads that give one word several shapes, and often generic parameters threaded through all of it. When nothing matches, the host reports what it was matching — that implementation vocabulary — because that is the only vocabulary it has. - **Leaked implementation names.** The message names the block's target type or an intermediate shape, neither of which appears in the author's text. - **Coarse position.** A block that fails as a unit points at the block, so the author is told that something in twelve lines is wrong rather than which word. - **Cascades.** One wrong word inside a nested block can invalidate the enclosing shape, producing several messages of which only one is the cause. - **Ambiguity rather than mismatch.** Where a word is available in several shapes, the failure reads as "nothing applies" or "several apply", which tells the author nothing about the domain rule they broke. - **Split phases.** What is expressible in the host's types is checked **during the build**; everything else slides to **run time**, where the message is whatever the notation's own validation says — which is nothing at all until someone writes it. ## The failure that produces no message at all The commonest misuse of a configuration notation is leaving a required word out. Absence has no site to fail at: nothing was written, so there is no shape to reject. Three outcomes are possible, and only the first is good. 1. The design makes the result **impossible to produce** without the word, so omission is a build failure with a real site. 2. The notation **checks and complains** where it assembles the result, at run time, with a message somebody authored. 3. The notation **silently defaults**, and the configuration is wrong in production with no message anywhere. A review that does not ask which of the three applies to each required word has not priced the notation's error quality. ## Designing the messages in 1. **Name the illegal shape.** If the type the host will print is called after the domain rule it enforces, the leaked name is still a useful name. This is the cheapest single improvement available. 2. **Keep the implementation shallow.** Fewer intermediate types and fewer overload shapes mean shorter messages with fewer unfamiliar names in them. 3. **Author the run-time complaints.** Where a check happens as the result is assembled, the message should name the word, the block it was in and what was expected — the domain's vocabulary, not the machinery's. 4. **Test the text.** Write cases that make each common mistake and assert on the message. Error text is part of the contract the notation publishes; untested, it degrades with every refactor of the machinery underneath. 5. **Budget it.** In the review, state the error work as work: which mistakes get a build failure, which get an authored message, and who writes them. ## What this means for the decision | Mistake | Against a plain API | Against a notation, by default | |---|---|---| | Wrong value type | names the parameter and the expected type | names an implementation type, possibly at the block | | Word used in the wrong place | no such member on this receiver | nothing matched, or several things matched | | Required thing omitted | argument missing at the call | often nothing, unless designed for | | Two settings that conflict | needs an explicit check either way | needs an explicit check either way | The honest summary for the review is that a notation does not make good error messages impossible — it makes them **somebody's job**. A plain API inherits adequate messages from the host for free; a notation inherits messages written in a vocabulary its authors never see, and only deliberate design moves them back into the domain. If nobody in the room is volunteering for that work, the notation's real error quality is whatever the machinery happens to print, and that is a cost the proposal should carry openly rather than discover after rollout.
- Which single design change most improves the default message a misuse produces?Give the shape that fails a name drawn from the domain rule it enforces. The host prints the names it was matching, so if those names describe the rule, the leaked message is still informative. It costs nothing at run time and survives refactoring of the machinery underneath.
- Why is a required word left out the hardest case of all?Because nothing was written, there is no site and no shape for the host to reject, so the ordinary failure path never fires. Either the design makes the result unproducible without the word, or the notation checks while assembling the result and complains itself, or the value silently defaults and the mistake reaches production unannounced.
- How do you stop the error quality from decaying after launch?Treat messages as tested behaviour: keep cases that commit each common mistake and assert on what is printed. Without them, every refactor of the intermediate types changes the text that users read, and nobody notices until someone files a confused bug report.
saying these in an interview costs you the question
- If the notation compiles, its error messages do not matter
- Type-checking the notation guarantees the messages will be readable
- A missing required word is always caught during the build
- Error text is not the kind of thing you write tests for
- Renaming internal types cannot change what a user sees
- Deferring all checks to run time gives better messages overall