Software Design Reference Master Designer · Complexity Trade-Offs

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.
Field notes · running the variation test
The settingVerdictBecause
DATABASE_URLconfigurationIt differs in every environment — measured variation.
MAX_PAGE_SIZE = 100conventionOne number the team chose; nobody has ever varied it.
LOG_LEVELconfigurationOperators change it during an incident, without a deploy.
TEMPLATE_DIR = "templates/"conventionA knob that has never been turned in the project’s life.
ENABLE_NEW_CHECKOUTconfiguration, with an expiry dateA 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

Bring your settings inventory to your agent-teacher and run the variation test on every knob.