Lesson 0029 · Master Designer · Module 1
Configuration vs Convention
Every config knob is a promise that the system varies in that way. Convention says "we already decided" — and removes a class of decisions. Choose by measuring real variation.
Mission tie-in: the third Complexity Trade-Off — deciding where the system stays open and where it decides for you.
Knowledge: knobs are promises
Configuration — the system exposes a setting, an env var, a parameter. The cost: every knob must be documented, validated, tested, and understood; every combination is a potential state the code must survive.
Convention — the system decides: "tables named by the model", "endpoints in api/", "config from config.py". The cost: the decision is invisible; newcomers must learn the convention; and when the convention doesn't fit, you fight it.
The deciding question is measured variation:
# convention: one decision, made once, everywhere
class OrderService:
TIMEOUT_SECONDS = 30 # the team decided; no knob to mis-set
# configuration: real, measured variation — per deployment, per tenant
class OrderService:
def __init__(self, timeout: timedelta, retries: int) -> None:
self._timeout = timeout # differs between prod and a slow partner
The Twelve-Factor rule gives the boundary: settings that vary between deploys (credentials, hosts, feature flags) are configuration. Settings that don't vary are convention — hardcode the decision, don't expose a knob. And the framework lesson (Rails' "convention over configuration"): when you control the whole stack, conventions buy productivity for the 80% case; configuration exists only where the 20% genuinely diverges.
† The anti-pattern is the config graveyard: knobs nobody varies, kept "just in case" — every one is a YAGNI (lesson 0002) wearing env-var clothes. Grep for unused settings; delete them.| The setting | Verdict | Because |
|---|---|---|
DATABASE_URL | configuration | It differs in every environment — measured variation. |
MAX_PAGE_SIZE = 100 | convention | One number the team chose; nobody has ever varied it. |
LOG_LEVEL | configuration | Operators change it during an incident, without a deploy. |
TEMPLATE_DIR = "templates/" | convention | A knob that has never been turned in the project’s life. |
ENABLE_NEW_CHECKOUT | configuration, with an expiry date | A flag is a transition (lesson 0035), not a permanent setting. |
Skill: knob or decision?
A value that never varies across deployments should be:
Database credentials are configuration because:
A config graveyard forms when:
Practice on your own code
List the config settings in your system. For each, answer: does it vary per deployment or per tenant? If it doesn't, convert it to a convention — hardcode the decision and delete the knob. Grep for settings read nowhere and delete those too.
Reveal: a graveyard audit
An app exposed MAX_PAGE_SIZE (never varied), RETRY_DELAY (varied between prod and staging), and FEATURE_X_ENABLED (dead). Audit: MAX_PAGE_SIZE became a constant; RETRY_DELAY stayed config, now validated at startup; the dead flag was deleted. Settings count: 14 → 5, and the survivors all varied.
Your win
You can audit any setting with the measured-variation test, convert non-varying knobs into conventions, and clear the graveyard.
Read and watch deeper
- The Twelve-Factor App — Config: the canonical statement of what belongs in configuration.
- "Convention over Configuration", The Rails Doctrine — the strongest statement of the trade, from the framework that made it a movement.
- Software Architecture in Practice, Bass et al. — configurability as a quality attribute with a price.
- Watch: ArjanCodes YouTube — search "configuration" or "12 factor".
- Next: lesson 0030 — Generality vs Specificity.
- Reference: Trade-offs — configuration vs convention; glossary — config graveyard.
Bring your settings inventory to your agent-teacher and run the variation test on every knob.