Code Design
Communicate with the user in English. When any of the following terms first appears, present both the English term and its translation; thereafter, use the Chinese term consistently according to this table.
Design deep module: put substantial behavior behind a small interface, place it on a clean seam, and make it testable through that interface. Use this language and these principles wherever you design or refactor code. The goal: give callers leverage, give maintainers locality, and make everything easy to test.
Glossary
Use the following terms precisely. Do not substitute "component," "service," "API," or "boundary." Consistent language is the point.
模組 (Module): Anything that has an interface and an implementation. Deliberately scale-agnostic: it can be a function, class, package, or a slice across multiple layers. Avoid: unit, component, service.
介面 (Interface): Everything a caller must know to use this module correctly: type signature, plus invariants, call sequencing constraints, error modes, required setup, and performance characteristics. Avoid: API, signature (too narrow—only the type-level surface).
實作 (Implementation): What is inside the module—its body of code. Different from 轉接器 (Adapter): a thing can be a small adapter with a lot of implementation (e.g., a SQLite repository), or a big adapter with only a little implementation (e.g., an in-memory fake). Use "adapter" when discussing seams, and "implementation" otherwise.
深度 (Depth): Leverage on the interface. How much behavior a caller (or test) gets for each unit of interface they learn. A module that hides a lot of behavior behind a small interface is deep; a module whose interface is almost as complex as its implementation is shallow.
Seam (Michael Feathers): A place where you can change behavior without editing that place—that is, the location of a module's interface. Where to place a seam is itself a design decision, separate from what goes behind it. Avoid: boundary (confuses with DDD's bounded context).
轉接器 (Adapter): A concrete thing that fulfills an interface at a seam. Describes its role (which slot it fills), not its content (what is inside).
槓桿 (Leverage): The benefit a caller gets from depth. For each unit of interface learned, they get more power. The value of an implementation is recouped across N call sites and M tests.
局部性 (Locality): The benefit a maintainer gets from depth. Changes, bugs, knowledge, and validation are concentrated in one place, not scattered across call sites. Fix it once, it works everywhere.
Deep vs. Shallow
Deep module = small interface + large implementation:
┌─────────────────────┐
│ 小介面 │ ← few methods, simple parameters
├─────────────────────┤
│ │
│ 深層實作 │ ← complex logic hidden inside
│ │
└─────────────────────┘
Shallow module = large interface + thin implementation (avoid):
┌─────────────────────────────────┐
│ 大介面 │ ← many methods, complex parameters
├─────────────────────────────────┤
│ 薄實作 │ ← just passes things through
└─────────────────────────────────┘
When designing an interface, ask yourself:
- Can I reduce the number of methods?
- Can I simplify the parameters?
- Can I push more complexity inside?
Principles
- Depth is a property of the interface, not the implementation. A deep module can be internally composed of small, mockable, replaceable parts; those parts just aren't part of the interface. A module can have internal seams (implementation-private, for its own tests) and external seams (on the interface).
- Delete the test. Imagine deleting the module. If the complexity vanishes, it was just a pass-through. If the complexity reappears across N call sites, it was doing real work.
- The interface is the test surface. Callers and tests cross the same seam. If you need to reach past the interface to test, the module's shape is likely wrong.
- One adapter suggests a seam; two adapters make it a real seam. Do not introduce a seam unless something actually varies on either side of it.
Designing for Testability
A good interface makes testing natural:
-
Receive dependencies, don't create them.
# testable def fetch_alternates(mpn: str, client: PartSearchClient) -> list[Part]: ... # hard to test def fetch_alternates(mpn: str) -> list[Part]: client = DistributorClient(api_key=os.environ["DIST_API_KEY"]) -
Return results, don't produce side effects.
# testable def landed_cost(bom: Bom, freight: Freight) -> Money: ... # hard to test def apply_landed_cost(bom: Bom) -> None: bom.total += freight_charge -
Small surface area. Fewer methods means fewer tests to write; fewer parameters means simpler test setup.
Relationships
- A module has exactly one interface (the surface it presents to callers and tests).
- Depth is a property of a module, measured by its interface.
- A seam is where a module's interface sits.
- An adapter lives on a seam and fulfills an interface.
- Depth gives leverage to callers and locality to maintainers.
Rejected Frameworks
- Defining depth as the ratio of implementation lines to interface lines (Ousterhout): This rewards bloated implementations. We use "depth as leverage" instead.
- Equating "interface" with Python's
Protocol/abstract class, or a class's public methods: Too narrow; here, the interface includes everything a caller must know. - "Boundary" : Confuses with DDD's bounded context. Say seam or interface.
Further Reading
- Deepening a family of modules under existing dependencies, see DEEPENING.md: dependency classification, seam discipline, and a "replace, don't stack" testing strategy.
- Exploring alternative interface designs, see DESIGN-IT-TWICE.md: dispatch multiple sub-agents in parallel to design the interface in several distinct ways, then compare by depth, locality, and seam placement.