Skip to main content

simple-code

Reduce incidental complexity in code and design. Use when writing, reviewing, refactoring, or structuring code and when deciding API shape, module boundaries, layering, naming, error handling, testing strategy, or architecture. Prefer concrete, local, reversible solutions over speculative abstraction. Do not use as sole guidance when security, compliance, formal verification, advanced type-level design, or hot-path optimization is the dominant concern.

Source facts

Repository
MylesMCook/simple-code
Last source activity
March 15, 2026 at 17:56
Detected SKILL.md language
English
Stars
0
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
simple-code
description
Reduce incidental complexity in code and design. Use when writing, reviewing, refactoring, or structuring code and when deciding API shape, module boundaries, layering, naming, error handling, testing strategy, or architecture. Prefer concrete, local, reversible solutions over speculative abstraction. Do not use as sole guidance when security, compliance, formal verification, advanced type-level design, or hot-path optimization is the dominant concern.
# Simple Code Cross-language software design guidance for code that needs to stay understandable under change. It is grounded primarily in four works: *The Grug Brained Developer* (Carson Gross), *Worse Is Better* (Richard Gabriel), *A Philosophy of Software Design* (John Ousterhout), and *The Laws of Simplicity* (John Maeda), plus a small set of explicitly named supporting heuristics in `references/provenance.md`. Central claim: most maintainability problems come from incidental complexity introduced by premature abstraction, needless indirection, speculative architecture, and interfaces that leak internal problems to callers. ## Scope Use this skill for mainstream application, service, library, and tooling work when the question is mainly about structure, simplicity, maintainability, or tradeoffs. Typical triggers: - shaping APIs or module boundaries - choosing between a concrete solution and a reusable abstraction - reviewing wrappers, layers, configuration, or service splits - planning safe refactors or incremental migrations - deciding what to test and at what boundary - writing code that should stay easy to understand Do not let this skill dominate when another concern is primary: security, privacy, accessibility, compliance, formal verification, low-level performance work, memory layout, lock-free or highly concurrent algorithms, or advanced type-level design. ## Defaults, Not Laws These are strong defaults, not universal bans. Use them to remove incidental complexity. Do not apply them mechanically when the domain genuinely benefits from stronger typing, added structure, explicit protocols, or specialized architecture. ## Precedence Do not simplify away correctness, security, privacy, accessibility, data integrity, legal or regulatory requirements, or explicit user requirements. When complexity is necessary, isolate it, explain why it exists, and keep it from spreading. ## When Principles Conflict 1. Preserve correctness, security, data integrity, and explicit requirements first. 2. For public APIs and long-lived boundaries, prefer interface simplicity. 3. For first internal implementations and exploratory work, prefer implementation simplicity and reversibility. 4. Keep behavior local until a stable cut point emerges; then hide complexity behind a deep interface. ## Operating Procedure When this skill is active: 1. Identify the explicit requirements, constraints, dominant risks, and the concern that should lead the decision. 2. Recommend the simplest design that satisfies those requirements now. 3. Prefer local, concrete, reversible changes over speculative abstractions or large rewrites. 4. Keep the rationale proportional. Use the philosophy to improve the answer, not to lecture. 5. If complexity is required, say where it belongs and why it is necessary. 6. If another discipline dominates, say so clearly and defer to it. 7. Load only the reference file or files that directly deepen the current task. ## Response Pattern Favor this shape when giving advice: - **Recommendation:** the simplest viable approach. - **Why:** what complexity it removes or contains. - **Necessary complexity:** what still must remain and why. - **Next step:** the smallest reversible action that moves the work forward. ## Prime Directive When facing a design decision, ask: does this reduce incidental complexity or move it somewhere worse? If the added complexity is speculative, do not add it yet. If the added complexity is real and justified, trap it behind the simplest boundary you can. ## Core Decision Rules ### 1. Say No First The cheapest complexity is the complexity you never add. Default to “no” or “not yet” for new features, abstractions, parameters, configuration knobs, architectural layers, and dependencies unless the need is concrete. When the requirement is real, build the smallest version that delivers most of the value. The long tail is where maintenance cost hides. ### 2. Ship the Simple Thing That Works For internal implementations, prefer the approach with the simpler implementation when it still satisfies the real requirement. A good common-case solution now usually beats a theoretically complete one built for imagined edge cases. Do not buy local implementation convenience by forcing every caller to absorb complexity. For public APIs and stable boundaries, simpler interfaces usually matter more than simpler internals. ### 3. Build Deep Modules, Not Shallow Ones A module earns its existence when its interface is materially simpler than the implementation it hides. Small interfaces that hide meaningful work are good. Thin wrappers, pass-through layers, and tiny abstractions that expose nearly all the underlying complexity are not. If inlining a function or removing a layer would make the system easier to understand, the boundary has not earned itself. ### 4. Do Not Abstract Until You Must Write concrete code first. Let repetition reveal the real seam. A wrong abstraction is usually worse than a small amount of duplication because it hardens the wrong relationship. Two similar call sites are usually not enough. Wait for repeated, genuinely similar pressure, then extract the smallest stable abstraction that relieves it. ### 5. Prefer Local and Direct Over Indirect and Distributed Keep behavior near the place it matters. Local reasoning beats scavenger hunts across files, wrappers, and services. Prefer: - a self-contained function over a chain of trivial helpers - a small local duplication over a shared utility that couples unrelated modules - configuration close to the code it configures - an in-process boundary over a network boundary unless a real split is justified ### 6. Name Things Precisely Names are compressed design. A good name makes the code easier to read without opening the implementation. A vague name pushes explanation back onto the reader. Break dense logic into clearly named intermediates when that improves comprehension and debugging. ```javascript if (!contact) return; const isInactive = !contact.isActive(); const isFamilyOrFriend = contact.inGroup(FAMILY) || contact.inGroup(FRIENDS); if (isInactive && isFamilyOrFriend) { sendReminder(contact); } ``` If a function, type, or variable refuses to accept a precise name, treat that as a design problem. ### 7. Pull Complexity Downward When complexity must exist, push it into the implementation rather than the interface. Callers should not need to learn your internals to use a module safely. Favor sensible defaults over knobs. Favor closed sets over free-form strings when the domain is small and stable. Favor APIs that make misuse harder instead of APIs that document many footguns. ### 8. Write the Module Promise Before the Implementation Before building a module, write a short note describing what it does, what it hides, and what it guarantees. If you cannot explain that clearly in a paragraph, the boundary is probably wrong or premature. The goal is not heavy design documentation. The goal is enough clarity that the implementation has a stable target. ### 9. Eliminate Errors Before Handling Them The best error handling removes entire classes of errors. Broaden input acceptance when safe. Use types, defaults, and control flow that make invalid states harder to represent. When errors cannot be designed away, handle them at the smallest number of sensible boundaries. Avoid smearing error plumbing through many layers just because every layer can technically see it. ### 10. Test at Stable Boundaries Test behavior at boundaries that should survive refactoring. Avoid tests that merely mirror internal implementation structure. In many codebases, integration tests are the default sweet spot: high enough to verify real behavior, low enough to localize failures. Use unit, property, contract, fuzz, or end-to-end tests where they match the risk better. Mock cautiously and mostly at system boundaries. ### 11. Log Usefully, Optimize with Evidence Log enough to reconstruct important decisions, failures, state transitions, and request flow. Prefer structured, correlated logs where the environment supports them. Do not log secrets, sensitive data, or high-volume branch noise. Do not optimize on instinct. Measure first, then optimize the actual bottleneck. ## Red Flags Pause when you see these: - a module interface as complex as the implementation it hides - a wrapper or layer with the same abstraction as the thing underneath - an abstraction created to unify only two call sites - a refactor that requires many files to change before any value appears - features or code paths being built for hypothetical future cases - generic or type-level machinery that is harder to understand than the concrete use case - service boundaries introduced before module boundaries are proven - eventing or workflow machinery proposed mainly to avoid direct calls in one product flow - tests that break mostly because names, files, or helpers moved - difficulty giving a precise name to the thing you are building ## What This Skill Does Not Mean - It does not mean sloppy code. Simple code is precise, well named, and deliberate. - It does not mean never abstract. It means abstract later, after the seam is real. - It does not mean never use indirection. Use it when it hides real complexity instead of moving code around. - It does not mean never use advanced types, concurrency, or specialized architecture. It means demand a real payoff before paying their cost. - It does not mean ignore performance. It means optimize with measurements, not anxiety. - It does not mean every domain can be made simple. Some complexity is inherent. Accept it, isolate it, and stop pretending it is avoidable. - It does not replace security, database, accessibility, performance, compliance, language, framework, or domain-specific guidance. It should compose with those concerns, not overrule them. ## Reference Materials Load only what is relevant: | Topic | Reference | When to Load | |---|---|---| | Source notes and provenance | `references/provenance.md` | When checking where a principle comes from, how strong it is, or whether it is a primary influence versus an added heuristic | | API and interface design | `references/api-design.md` | Designing public interfaces, libraries, functions, classes, or module boundaries | | Architecture decisions | `references/architecture.md` | Choosing deployment boundaries, layering, dependencies, state, concurrency, or service and event topology | | Refactoring and evolution | `references/refactoring.md` | Refactoring existing code, shrinking systems, planning incremental migration, or touching legacy code |
View on GitHub