Merciless Simplification
For AI Agents: Standards for writing and refactoring clean code, plus an optional campaign flow for whole-codebase passes
Foundations: XP + TDD + Clean Code + Refactoring + Tidy First + SOLID
Goal: Measurable complexity reduction with zero change to externally observable behavior (as observed by callers and the tests) — always backward compatible, implementation-only changes
Quick Start
Copy-paste this prompt to any AI agent:
Apply merciless simplification methodology from https://mfenderov.github.io/merciless-simplification to systematically eliminate complexity in this codebase.
Layer 1 — Standards (use continuously, on every change)
No tickets, no batches, no approval flow. Process (planning, execution, verification) belongs to the host workflow.
Standing principles
- Merciless in ambition, incremental in execution: always strive for the simplest possible long-term solution — reached one small change at a time. Leave code simpler than you found it.
- Small bits only: never batch refactorings. A single small, verified, committed change is the unit of work.
- Risk asymmetry: a wrong deletion surfaces as a production bug weeks later (expensive); leaving mildly complex code is cheap and reversible. When in doubt on anything touching external behavior, do not change it — record it for human review instead.
- Scope classification: internal implementation changes are the normal path. Public API / service boundary changes are out of scope by default and require explicit human approval.
- Write inherently low-CRAP code: CRAP(m) = comp(m)² × (1 − cov/100)³ + comp(m). Every new or rewritten function should score low by construction — cyclomatic complexity ≤5 through guard clauses and one behavior per function, covered by tests that assert behavior. Measure CRAP to shape new code, never as a pass/fail gate.
Clean Code essence
- Names reveal intent: name the what, not the how. Rename on sight when a name confuses.
- Functions do one thing: one behavior per function, complexity ≤5, guard clauses over nesting, small enough to hold in context at once.
- No duplicated knowledge: every concept has a single source of truth. Tolerate duplication once; extract at the third occurrence — never extract a wrong abstraction early.
- Fewest elements: no speculative generality. Delete anything serving no present need.
- Comments explain why, never what: code carries the what through names and structure. No TODO/FIXME left behind.
- Tests assert behavior through public interfaces: one behavior per test, deterministic, no shared mutable state. If tests are hard to write, the production design is bad — fix the design.
SOLID
- SRP — one reason to change: a module serving two masters splits. When a change pulls in two directions, the boundary is wrong.
- OCP — extend, don’t edit: add new behavior through new code (new function, new implementation behind an interface), not by modifying working paths. Branch by abstraction, not by flags scattered through callers.
- LSP — substitutes honor the contract: no strengthened preconditions, no weakened postconditions, no surprising exceptions. If a subtype cannot stand in for its parent everywhere, the hierarchy is wrong — prefer composition.
- ISP — small focused interfaces: 1–3 methods. No client forced to depend on methods it does not use. Split fat interfaces at client boundaries.
- DIP — depend on abstractions: depend on interfaces defined by the consumer (ports), never on concretions. Dependencies point inward: frameworks at the edges, domain at the center. Inner circles know nothing of outer circles.
Simplification judgment
- Evidence over naming: every simplification claim cites concrete evidence (zero references via search, call-site count, identical blocks via diff). Naming a smell is not evidence.
- KEEP when: it solves a real problem, catches real bugs, carries meaning callers rely on (names, API consistency), or enables near-term extension at minimal cost.
- ELIMINATE when: dead (no references), duplicated knowledge, thin wrapper adding no meaning, or built for a hypothetical future. Single call site inlines — unless the name carries meaning callers rely on.
- When uncertain, keep. Record it for human review instead of changing it.
Preventive habits
- Tidy first: make the change easy, then make the easy change. Never mix tidying with behavior changes.
- CHARACTERIZE before restructuring untested code: capture what the code actually does in tests first (success, failure, edge cases as observed) — then judge whether that behavior is desirable.
Pointers out
- Code smells and refactorings follow the standard literature (Fowler, refactoring.com/catalog). Cite refactorings by name and follow catalog mechanics — do not improvise new transformation shapes.
Layer 2 — Campaign (optional whole-codebase passes)
The host workflow owns process. Use this flow only for standalone simplification campaigns run outside feature work. Map its steps onto the host workflow where one exists (plan/execute/verify) instead of running two processes.
AI Agent Instructions
Execute in Order
- READ this complete methodology
- ANALYZE the codebase — every claim requires concrete evidence (see Layer 1 Simplification judgment)
- CLASSIFY the scope of each candidate change: internal implementation vs. public API / service boundary
- GENERATE micro-tickets prioritized by impact/risk, each with a risk tier
- APPROVAL: present the ticket batch; the human approves direction and criteria, not every ticket — high-risk tickets (public API, large deletions, core modules) are gated on explicit approval
- EXECUTE tickets one at a time: ANALYSIS → CHARACTERIZE → TIDY → SIMPLIFY/CONSOLIDATE → VERIFY
- COMMIT after each ticket
- MEASURE results against complexity metrics, then repeat or stop (see Stopping Criterion)
Quality Gates
- All tests pass after each ticket
- Backward compatible: no change to externally observable behavior as observed by callers and the tests (API, error messages, formats, timing, side effects)
- Safety net calibrated to risk: tiny tidies (dead-code removal, renames) rely on smallness and revert; structural changes get CHARACTERIZE tickets first; boundary changes get human approval
- Tickets cite the Fowler refactoring by name and the concrete evidence for the smell — and before executing, read the actual catalog entry, not the summary gloss
- Coverage is a constraint, never a target — tests must assert behavior, verified via mutation testing
- Every ticket exits with: this change makes the next change cheaper — the only economic justification for tidying
Expected Results
- Lower complexity: improved CRAP score (cyclomatic complexity × test coverage) on targeted modules
- Stronger tests: mutation testing shows tests actually assert behavior, not just execute code
- Cleaner architecture: architectural tests (dependency / layering rules) pass or improve
- Zero broken features, zero external behavior change, through TDD discipline
Lines of code and file counts are never a measure of success — adding lines (guard clauses, named intermediate variables, extracted functions) can reduce complexity.
Stopping Criterion
Stop when any of these hold:
- N consecutive tickets find no code smell with evidence (suggested: 3)
- A pre-agreed timebox is reached
Metrics (CRAP score, mutation survival, architecture checks) are diagnostics, not gates — when a measure becomes a target it ceases to be a good measure. Use them to inform the work, never to gate it.
Do not churn. If a ticket’s change is uncertain or tests cannot be made green, revert and record why — then move on.
The Six Micro-Ticket Types
ANALYSIS — Understand Before Changing
ANALYSIS-001: Document dependencies for UserService
Tasks:
- List all imports and dependencies
- Identify code smells
- Document simplification opportunities
Exit: Current state documented, safe path identified
CHARACTERIZE — Capture Behavior (Safety Net)
Safety net for structural changes. TDD designs new behavior; characterization captures existing behavior — write what the code does first, then judge whether that is desirable. Tiny tidies rely on smallness and revert instead.
CHARACTERIZE-001: Capture AuthManager behavior in tests
Tasks:
1. Observe and document current behavior
2. Capture it in tests — success, failure, and edge cases as the code actually behaves
3. Verify all tests pass
Exit: Behavior captured in tests, safe to simplify
TIDY — Prepare for Simplification
Requires existing tests — run a CHARACTERIZE ticket first if the code has no coverage.
TIDY-001: Extract validateEmail before inlining UserValidator
Fowler Refactoring: Extract Function
Steps:
1. Extract email validation to standalone function
2. Run tests (must stay green)
3. Commit
Exit: Structure improved, ready for simplification
SIMPLIFY — Eliminate Complexity
SIMPLIFY-001: Inline formatUserName function
Fowler Refactoring: Inline Function
Code Smell: Speculative Generality
Steps:
1. Verify function used in only one location
2. Copy body to call site
3. Remove function definition
4. Run tests
5. Commit
Exit: Code simpler, all tests pass
CONSOLIDATE — Merge Duplicates
CONSOLIDATE-001: Merge user test files
Fowler Refactoring: Extract Function (shared setup/fixtures), Move Function
Code Smell: Duplicate Code
Steps:
1. Identify duplicate setup/teardown
2. Move tests to primary file
3. Remove empty file
4. Run tests
5. Commit
Exit: Duplication eliminated, all tests pass
VERIFY — Confirm Preservation
VERIFY-001: Verify all changes
Tasks:
1. Run complete test suite
2. Run mutation testing on changed modules — tests must actually assert the behavior
3. Verify no regressions and no external behavior change
Exit: All tests green, ready for next phase
Key Patterns
Pattern: Inline Single-Use Components
When: Component used in only one location
Smell: Speculative Generality
Refactoring: Inline Class, Inline Function
Before:
├── PersonaCard.tsx (156 lines) — only used by PersonaSelector
├── PersonaSelector.tsx
After:
├── PersonaSelector.tsx (card logic inlined)
Decision: Don’t inline if component has independent business logic or inlining makes parent >300 lines.
Pattern: Consolidate Related Test Files
When: Multiple test files testing the same component
Smell: Duplicate Code, Needless Repetition
Refactoring: Move Function, Remove Dead Code
Before:
├── user_basic_test
├── user_test
├── user_nested_test
├── user_errors_test
After:
├── user_test (consolidated)
├── user_advanced_test (complex scenarios)
Decision: Don’t consolidate if files have conflicting setup/teardown or circular dependencies.
Pattern: Eliminate Documentation Redundancy
When: Same information explained in multiple places
Smell: Duplicate Code (in documentation)
Refactoring: Move content to canonical location, delete duplicates
Before:
├── README.md (API + examples)
├── API_REFERENCE.md (API + examples)
├── TUTORIAL.md (API + examples)
After:
├── README.md (overview + getting started)
├── API_REFERENCE.md (complete API docs)
Pattern: Reduce API Surface
When: Too many public functions, some providing minimal value
Smell: Speculative Generality, Dead Code
Refactoring: Inline Function, Remove Dead Code
// KEEP: Callers rely on the indirection
func GetEnv(key string) string {
return os.Getenv(key)
}
// ELIMINATE: No meaning callers rely on over the built-in
func StringLength(s string) int {
return len(s)
}
Scope: internal code only. If these are public functions consumed by other packages, removing them changes external behavior — out of scope unless explicitly approved.
Fowler Refactoring Reference
Standard literature, well known — not listed here. Use the full catalog at refactoring.com/catalog: cite refactorings by name and follow catalog mechanics.
AI Agent Workflow
DO
Cite refactorings by name:
SIMPLIFY-001: Inline getUserName (Inline Function)
Test after every ticket:
Execute → Run tests → Verify green → Commit
Work one ticket at a time:
Complete SIMPLIFY-001 → Commit → Start SIMPLIFY-002
DON’T
Skip tests: If tests fail, revert and break into smaller steps
Change external behavior: Any change to public API, error messages, formats, timing, or side effects is out of scope unless explicitly approved
Batch changes: Commit after each ticket, not after multiple tickets
Limitations
Subjectivity in judgment
Evidence quality varies: dead code and duplicated blocks are easy to agree on; generality, size, and wrapper value admit reasonable disagreement. When uncertain, keep — and let the evidence threshold rise with the blast radius.
When NOT to Use This Methodology
- Codebase is already minimal
- Major architectural rewrite is planned
- Team lacks buy-in
Untested code is not a reason to skip — CHARACTERIZE tickets are the entry point. Capturing behavior is the work.
Honest Expectations
This methodology does not guarantee:
- Any specific reduction percentage
- Improved performance (this is about maintainability)
- Agreement among team members
Results vary based on starting codebase, team experience, and time invested.
Attribution
Built on:
- Kent Beck: XP, TDD, Tidy First
- Robert Martin: Clean Code, SOLID, Clean Architecture
- Martin Fowler: Refactoring (2nd Ed) — refactoring.com/catalog
What this adds: always-on standards (Layer 1) distilling these practices into rules an AI agent applies on every change, plus an optional campaign flow (Layer 2) for whole-codebase passes.
Source: github.com/mfenderov/merciless-simplification
Standards for writing clean code continuously; campaigns for simplifying it periodically.