feat(coding-dev): require domain values to be typed, not primitives

A value with rules of its own — a URL, a path, a closed set, an identifier —
gets a type of its own. Passing it as a bare string discards those rules at the
first function boundary and leaves every consumer re-deriving them.

Added as a subsection of Architecture Preferences, with a table mapping the
common cases to the type to reach for in any language, the reasoning, and the
boundary rule: parse once at the edge, keep the type inward, serialize back only
where the wire actually demands a string. Notes the counter-case, so this is not
read as a mandate to wrap every string.

python.md gains the concrete choices: yarl.URL (already present wherever aiohttp
is), pathlib, StrEnum, and NewType for non-interchangeable identifiers.
This commit is contained in:
Bluscream committed 2026-09-18 18:22:26 +02:00
1 parent 3171595940
commit b08fe50e92
2 files changed
+47

No files matched your search

+30
View File
@@ -257,6 +257,36 @@ Run the **whole** gate, not the convenient part of it. A typical full gate:
- **A search-and-replace that matches nothing is a silent failure.** Assert the target string
exists before replacing, and verify the result afterward.
### Model domain values as types, not primitives
**A value with rules of its own gets a type of its own.** Passing it around as a bare string,
int, or dict throws those rules away at the first function boundary and makes every consumer
re-derive them.
| Value | Use | Not |
| :--- | :--- | :--- |
| URL | the platform's URL type (`yarl.URL`, `Uri`, `url::Url`, `URL`) | `str` + f-string assembly |
| Filesystem path | `Path` / `PathBuf` / `std::filesystem::path` | `str` + manual separators |
| Closed set of values | enum / union of literals | bare strings compared with `==` |
| Structured record | struct, dataclass, or typed record | `dict[str, Any]` / `map[string]any` |
| Quantity with a unit | a wrapper carrying the unit | a bare number and a naming convention |
| Identifier of a specific kind | a distinct newtype | a `str` that any other id also fits |
**Why:** the type is where the rules live. A URL type knows how to join a path without
producing a double slash, escapes query parameters, and cannot be concatenated into something
malformed. A path type knows the platform separator. An enum cannot hold a typo. Each one
turns a class of runtime bug into something the compiler or checker rejects — and makes the
signature say what it accepts instead of `str`.
- **Convert at the boundary.** Parse incoming strings into the real type once, at the edge,
and keep the type inward. Serialize back to a string only where the wire or the filesystem
actually demands one.
- **Don't reach for a wrapper where the primitive genuinely is the value** — a person's name
is a string. The test is whether the value has invariants, a format, or operations that
callers keep re-implementing.
- Retrofit opportunistically: convert what you touch rather than rewriting a stringly-typed
codebase in one pass.
---
## 8. Scope Discipline