mcpskills.net
SkillsMCPsAgentsPrompts
mcpskills.net — A curated directory of AI agent Skills and MCP servers
TermsPrivacy
← Back to Skills
Development

Code Simplification

Simplifies code for clarity. Use when refactoring code for clarity without changing behavior.

by Addy OsmaniRepository →Source →

Simplify code by reducing complexity while preserving exact behavior. The goal is code that is easier to read, understand, modify, and debug.

When to Use

  • After a feature is working and tests pass, but the implementation feels heavier than it needs to be
  • During code review when readability or complexity issues are flagged
  • When you encounter deeply nested logic, long functions, or unclear names
  • When refactoring code written under time pressure

The Five Principles

1. Preserve Behavior Exactly

Don't change what the code does — only how it expresses it.

ASK BEFORE EVERY CHANGE:
→ Does this produce the same output for every input?
→ Does this maintain the same error behavior?
→ Does this preserve the same side effects and ordering?
→ Do all existing tests still pass without modification?

2. Follow Project Conventions

Simplification means making code more consistent with the codebase, not imposing external preferences.

3. Prefer Clarity Over Cleverness

// UNCLEAR: Dense ternary chain
const label = isNew ? 'New' : isUpdated ? 'Updated' : isArchived ? 'Archived' : 'Active';

// CLEAR: Readable mapping
function getStatusLabel(item: Item): string {
  if (item.isNew) return 'New';
  if (item.isUpdated) return 'Updated';
  if (item.isArchived) return 'Archived';
  return 'Active';
}

4. Maintain Balance

Watch for over-simplification traps:

  • Inlining too aggressively
  • Combining unrelated logic
  • Removing "unnecessary" abstraction
  • Optimizing for line count

5. Scope to What Changed

Default to simplifying recently modified code. Avoid drive-by refactors of unrelated code.

The Simplification Process

Step 1: Understand Before Touching (Chesterton's Fence)

Before changing anything, understand why it exists:

BEFORE SIMPLIFYING, ANSWER:
- What is this code's responsibility?
- What calls it? What does it call?
- What are the edge cases and error paths?
- Are there tests that define the expected behavior?
- Why might it have been written this way?

Step 2: Identify Simplification Opportunities

Structural complexity:

| Pattern | Signal | Simplification | |---------|--------|----------------| | Deep nesting (3+ levels) | Hard to follow control flow | Extract into guard clauses | | Long functions (50+ lines) | Multiple responsibilities | Split into focused functions | | Nested ternaries | Requires mental stack | Replace with if/else chains | | Boolean parameter flags | doThing(true, false, true) | Replace with options objects | | Repeated conditionals | Same if check in multiple places | Extract to predicate function |

Naming and readability:

| Pattern | Signal | Simplification | |---------|--------|----------------| | Generic names | data, result, temp | Rename to describe content | | Misleading names | Function named get that mutates | Rename to reflect behavior | | Comments explaining "what" | // increment counter | Delete the comment | | Comments explaining "why" | // Retry because API is flaky | Keep these — they carry intent |

Redundancy:

| Pattern | Signal | Simplification | |---------|--------|----------------| | Duplicated logic | Same 5+ lines in multiple places | Extract to shared function | | Dead code | Unreachable branches, unused variables | Remove | | Unnecessary abstractions | Wrapper that adds no value | Inline the wrapper | | Over-engineered patterns | Factory-for-a-factory | Replace with simple approach |

Step 3: Apply Changes Incrementally

Make one simplification at a time. Run tests after each change.

Step 4: Verify the Result

COMPARE BEFORE AND AFTER:
- Is the simplified version genuinely easier to understand?
- Did you introduce any new patterns inconsistent with the codebase?
- Is the diff clean and reviewable?
- Would a teammate approve this change?

Language-Specific Guidance

TypeScript / JavaScript

// SIMPLIFY: Unnecessary async wrapper
// Before
async function getUser(id: string): Promise<User> {
  return await userService.findById(id);
}
// After
function getUser(id: string): Promise<User> {
  return userService.findById(id);
}

// SIMPLIFY: Verbose conditional assignment
// Before
let displayName: string;
if (user.nickname) {
  displayName = user.nickname;
} else {
  displayName = user.fullName;
}
// After
const displayName = user.nickname || user.fullName;

// SIMPLIFY: Manual array building
// Before
const activeUsers: User[] = [];
for (const user of users) {
  if (user.isActive) {
    activeUsers.push(user);
  }
}
// After
const activeUsers = users.filter((user) => user.isActive);

Verification

After completing a simplification pass:

  • [ ] All existing tests pass without modification
  • [ ] Build succeeds with no new warnings
  • [ ] Each simplification is a reviewable, incremental change
  • [ ] Simplified code follows project conventions
  • [ ] No error handling was removed or weakened
  • [ ] No dead code was left behind
  • [ ] A teammate would approve the change as a net improvement