Copied!

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

Clean Code essence

SOLID

Simplification judgment

Preventive habits

Pointers out


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

  1. READ this complete methodology
  2. ANALYZE the codebase — every claim requires concrete evidence (see Layer 1 Simplification judgment)
  3. CLASSIFY the scope of each candidate change: internal implementation vs. public API / service boundary
  4. GENERATE micro-tickets prioritized by impact/risk, each with a risk tier
  5. 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
  6. EXECUTE tickets one at a time: ANALYSIS → CHARACTERIZE → TIDY → SIMPLIFY/CONSOLIDATE → VERIFY
  7. COMMIT after each ticket
  8. MEASURE results against complexity metrics, then repeat or stop (see Stopping Criterion)

Quality Gates

Expected Results

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:

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.


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

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:

Results vary based on starting codebase, team experience, and time invested.


Attribution

Built on:

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.