Files
stanp-opencode/src/anti-overengineering.md
2026-10-10 13:54:07 +02:00

9.1 KiB

name, description
name description
anti-overengineering Keep any plan, change, or review to the smallest correct solution. Use whenever work may add abstractions, dependencies, configuration, generalized frameworks, speculative flexibility, broad rewrites, compatibility for unshipped code, or unrelated cleanup. Also use when the user asks for simple, minimal, surgical, KISS, YAGNI, or the least code.

Anti-overengineering

Build the smallest correct solution at the right layer. Minimize concepts, moving parts, and maintenance burden—not investigation, correctness, or raw line count.

Core invariant

First understand, then reduce.

A tiny patch at the wrong layer is not minimal. A short implementation that omits required behavior is not simple. A reusable design with no current reuse is inventory.

Correctness floor

Never simplify by weakening or skipping:

  • explicit user requirements or observable behavior;
  • root-cause analysis and affected-caller discovery;
  • trust-boundary validation, authentication, authorization, privacy, or security;
  • data integrity, error handling that prevents corruption or silent failure, or required concurrency guarantees;
  • accessibility, compatibility promises, migrations, or public contracts;
  • measured performance requirements or operationally necessary observability;
  • the narrowest meaningful verification of the changed behavior.

Preserve an existing abstraction until you understand why it exists. Fewer lines are useful only when the result is easier to understand and equally correct.

Before changing anything: establish the scope contract

Capture these facts internally before planning, editing, or reviewing. State them to the user only when they affect the chosen path.

  1. Required outcome: one sentence describing the current behavior the request needs.
  2. Current path: the relevant implementation, callers, local conventions, and root cause for a bug.
  3. Invariants: behavior and boundaries that must remain unchanged.
  4. In scope: the smallest set of responsibilities that must change—not a guessed file count.
  5. Out of scope: cleanup, modernization, and future capabilities not needed for the outcome.
  6. Acceptance signal: the specific runtime scenario, diagnostic, or test that proves the outcome.

Every changed line must trace to the required outcome, a necessary invariant, or its verification. If it cannot, leave it untouched and mention it separately only when useful.

Use the least-power ladder

Walk the ladder in order. Stop at the first option that completely satisfies the current requirement.

  1. No new code: existing behavior, deletion, configuration, documentation, or a workflow change already solves it.
  2. Reuse local behavior: use the repository's existing helper, type, service path, error semantics, and test pattern.
  3. Use a standard primitive: prefer the language standard library, framework primitive, database/query feature, browser API, or platform capability.
  4. Use an installed dependency: do not add a second mechanism the repository already has.
  5. Write direct concrete code: implement the current case in the owning module with the fewest new concepts.
  6. Introduce an abstraction: only after it passes the evidence gate below.

A shorter expression does not outrank readability. A new dependency must remove more owned complexity than it adds and must solve a current need unavailable from earlier rungs.

Make complexity prove its value

Interfaces, factories, strategies, adapters, registries, plugin systems, generic frameworks, feature flags, configuration knobs, wrappers, and new layers are presumed unnecessary until current evidence justifies them.

An abstraction is earned when at least one condition is true:

  • multiple current consumers or implementations share the same semantics and the abstraction reduces total concepts now;
  • a real framework, process, trust, persistence, or public-API boundary requires it;
  • one authoritative implementation prevents a present consistency, security, or data-integrity risk;
  • measured performance or test isolation requires the seam and a direct alternative is inadequate.

Even then, it must have a clear owner, a narrow contract, and less branching than the concrete code it replaces. "We may need it later" is not evidence.

Apply these existence tests:

  • Every parameter has a current caller that supplies meaningful variation.
  • Every option has a current use case.
  • Every branch handles a reachable state.
  • Every wrapper adds domain semantics, policy, or isolation—not only forwarding.
  • Every stored or passed value cannot be cheaply derived from values already in scope.
  • Every compatibility path protects code that has actually shipped or an explicit external contract.

Prefer two clear copies over a guessed shared abstraction. Extract after the pattern and reason to change are stable—often at the third real occurrence. Textually similar code that changes for different reasons is not duplication. If a shared helper accumulates flags and conditional paths for callers, inline it and let the real boundaries reappear.

Implement one present-tense path

  • Fix bugs at the cause, not with repeated caller fallbacks or symptom suppression.
  • Match established repository structure and style; never introduce a second convention.
  • Keep a concrete implementation concrete until variation exists.
  • Do not add unused hooks, optionality, extension points, scaffolding, placeholders, aliases, or TODO paths for imagined follow-up work.
  • Do not preserve compatibility for earlier, unshipped iterations. Update all callers and remove the obsolete path.
  • Do not mix unrelated cleanup, renames, reformatting, import churn, comment rewrites, type tightening, logging, or dependency updates into the change.
  • Separate optional structural cleanup from behavioral work. Perform a structural change in the same patch only when it is required for the correct fix.
  • Prefer obvious control flow and data structures over clever compression. Comments explain non-obvious reasons and constraints, not the conversation that produced the code.
  • Write for a maintainer who did not see the prompt, plan, or patch history.

When the user explicitly requests a broad refactor or new extensibility, that is current scope—not speculation. Still implement only the requested variants and contracts.

Reject common rationalizations

Rationalization Required response
"We'll need this later." Add it when the need and constraints exist.
"This makes it reusable." Name the second current use; otherwise keep it concrete.
"This should be configurable." Name current variation; otherwise keep the value local.
"A clean architecture needs another layer." Name the boundary or policy it owns; otherwise follow the existing direct path.
"A wrapper keeps things consistent." Put real policy in the wrapper or call the underlying API directly.
"Defensive code is safer." Prove the state is reachable; retain checks at real trust and integrity boundaries.
"A local fallback is a smaller bug fix." Trace the shared path and fix the root cause once.
"We should clean this up while here." Leave it untouched unless correctness requires it.
"Keeping the old path is safer." Keep it only for shipped behavior or an explicit compatibility contract.
"More tests are always safer." Add only tests that defend a changed observable contract or plausible regression.

Final subtraction pass

After the behavior works, inspect only the changed surface and ask:

  1. Does each changed line trace to the scope contract?
  2. Did the change add a file, layer, dependency, option, parameter, state field, or public symbol that direct code can avoid?
  3. Is there now more than one path for the same behavior?
  4. Can any one-caller helper, forwarding wrapper, dead compatibility path, speculative branch, or explanatory comment disappear?
  5. Did a refactor leak beyond what the behavioral change requires?
  6. Does the result make sense without the conversation?
  7. Is the narrow acceptance signal proven?

Delete generated scaffolding and obsolete paths. Do not simplify untouched code as a drive-by. Do not remove an existing construct whose purpose remains unknown.

Escalation

Proceed with the smaller correct path by default. Ask one precise question only when simplification would materially alter explicit behavior, a public API, persisted data, a security boundary, compatibility, or another difficult-to-reverse decision.

Treat an unexpectedly broad change as a diagnostic, not an automatic failure: explain why each responsibility must move, then look again for a narrower correct boundary. Do not use arbitrary line or file limits; optimize reviewer cognitive load and reversibility.

Reporting

Keep implementation reports compact:

Minimal path: [the direct solution and owning layer]
Avoided: [notable abstraction, dependency, configuration, or drive-by work omitted]
Verified: [specific behavior exercised]

Omit Avoided when nothing notable was rejected. For reviews, rank only actionable reductions by impact and include the safety caveat that preserves behavior. If no meaningful complexity can be removed, say so rather than inventing findings.