Skip to content

Code vs. Scripts vs. Docs

Problem & Solution

Code, scripts, and docs are three mediums with different execution guarantees. Code runs automatically as part of the system. Scripts run when a human (or cron, or CI) invokes them. Docs never run — they get read, sometimes.

Putting something in the wrong medium silently weakens it. A rule documented in a README is a suggestion; a rule in code is an invariant. A command explained in prose is something a user has to retype correctly; the same command wrapped in a script is one word to invoke. Rationale baked into code via clever naming evaporates when the code is refactored; rationale written in docs survives.

The principle: route each piece of content to the medium that matches the guarantee you actually need.

Pattern

The Three Homes

  • Code for rules & behaviors — anything that must always hold or always happen. The machine enforces it. Examples: an access filter, a timeout, a retry policy, a schema validation, a privilege drop.
  • Scripts for repeated operations — anything a person would do more than once. The script encapsulates the steps so they don't have to be re-derived. Examples: install, smoke-test, deploy, log-tail, database seed.
  • Docs for rationale — anything a reader needs to understand why the system is the way it is. Things the code can't express: trade-offs, history, intent, cross-references to other systems. Examples: threat model, why this architecture was chosen over alternatives, which external systems this depends on.

How to Apply

You're staring at something — a new requirement, a manual process, a piece of context. Ask what is this? and route it:

If it's a…Put it in…
Rule that must hold (or behavior that must always run)Code
Repeated operation you'd otherwise do by handScript
Reason, trade-off, or context someone needs to reason aboutDocs

Smells That Should Trigger the Rule

Each one says you're routing to the wrong medium.

  • Writing "remember to…" or "be sure to…" in a doc. The fact that you need to remind a future reader means the system isn't enforcing it. → Move it to code.
  • Writing a sequence of commands in a README for the user to paste. They'll mistype or drift. → Wrap them in a script and point the doc at the script.
  • Explaining in code comments why the code makes a trade-off that only makes sense if you know about an external constraint (a past incident, a vendor quirk, a compliance rule). Code comments rot; a design doc doesn't. → Move the rationale to docs, keep the comment short and pointing at the doc.
  • Docs describing what the code does step by step. If the code is the authority, the doc will go stale the first time someone edits without updating both. → Delete the narrative, let the code speak, keep only the why in docs.
  • Scripts that encode policy ("skip this host if it's staging"). Policy is a rule, not an operation. → Move the decision into code, keep the script dumb.

When Docs Are the Right Home

Not everything belongs in code. Docs are the right home when:

  • The content is about trade-offs or alternatives considered. Code shows the choice made, not the choices rejected.
  • The content is rationale that future maintainers need to decide whether to change something. "We picked X because of constraint Y" — if Y ever goes away, they can revisit.
  • The content is a pointer to external systems the code depends on ("DNS is served by CoreDNS on the Mac Studio; see macos-config/304-remote-access.md"). Code can't express the dependency; docs and cross-links can.
  • The content is onboarding context: how the system fits together, what a newcomer needs to read first.

If the content answers "what does this do?" — it should be obvious from the code or named in a script. If it answers "why is it like this?" — it's docs.

Examples

Example 1: Tailnet-only access filter

A server should only accept traffic from loopback and Tailscale. The naive instinct is to document this in the README: "the dashboard is tailnet-only; do not expose to the public internet."

That's a suggestion, not an enforcement. The README can't reject a request.

Right home: code. An Express middleware calls an isTailnetAddr() predicate on every request, returning 403 for anything else. A smoke-test script verifies this matches reality on every interface. The README explains why — the threat model, the fact that there's no auth, the decision not to expose via tailscale funnel — but the rule itself lives in access.js.

Example 2: Smoke-test the daemon

Checking that a service is healthy involves several steps: probe loopback, probe each network interface, look at process ownership. Documenting those steps in a README puts the burden on the user to run them correctly, in order, and to interpret the results.

Right home: script. npm run service:check encapsulates the entire sequence and exits non-zero if any check fails. The README points at the script; the script carries the knowledge.

Example 3: Why port 80 requires root

macOS requires admin privileges to bind ports below 1024. The dashboard binds 80 by running as a LaunchDaemon that starts as root, then immediately drops privileges to the repo owner. This is subtle — a future reader looking at the server code won't know why it starts as root unless they hit setuid and wonder.

Right home: docs (plus a short comment pointing there). The dashboard's README has a section titled "What happens if the server is ever compromised" that explains the trade-off in plain language. The code has a brief comment near the setuid call naming the mechanism. Neither duplicates the other.

Anti-patterns

"Living documentation" instead of tests

Saying "the README describes how the system works" as a substitute for tests or types. Docs drift; tests don't. If the correctness claim matters, encode it.

Config that's really a rule

A config flag like ENFORCE_TAILNET_ONLY=true defaults to off "so people can opt in." If the invariant is real, don't give the user the option to break it — delete the flag and hardcode the rule.

Scripts that are really docs

A bash script full of echo "Now edit your ~/.ssh/config to add…" with no actual action. The script isn't executing anything; it's just narrating. Either execute the edits (it's a real script) or delete the file and put the instructions in a README (where the form matches the function).

Docs that are really scripts

A README with a 12-step setup procedure that every new engineer follows manually. The first time two engineers do the same steps, it should become a setup script. The README should then be three lines: "run ./setup.sh; see below for what it does."

Why this matters

Each misrouting costs something:

  • Rule in docs → someone will break the invariant because they didn't read, didn't remember, or disagreed.
  • Operation in docs → someone will mistype, skip a step, or run the steps in the wrong order.
  • Rationale in code (only) → the "why" disappears when the code is refactored or rewritten.
  • Docs rewriting what the code already says → drift. The doc lies the moment the code changes.

Routing correctly means each piece of information lives where it can do its job: rules enforced, operations one-command-invocable, rationale preserved.