6# Code Simplification
7
8> Inspired by the [Claude Code Simplifier plugin](https://github.com/anthropics/claude-plugins-official/blob/main/plugins/code-simplifier/agents/code-simplifier.md). Adapted here as a model-agnostic, process-driven skill for any AI coding agent.
9
10## Overview
11
12Simplify code by reducing complexity while preserving exact behavior. The goal is not fewer lines — it's code that is easier to read, understand, modify, and debug. Every simplification must pass a simple test: "Would a new team member understand this faster than the original?"
13
14## When to Use
15
16- After a feature is working and tests pass, but the implementation feels heavier than it needs to be
17- During code review when readability or complexity issues are flagged
18- When you encounter deeply nested logic, long functions, or unclear names
19- When refactoring code written under time pressure
20- When consolidating related logic scattered across files
21- After merging changes that introduced duplication or inconsistency
22
23**When NOT to use:**
24
25- Code is already clean and readable — don't simplify for the sake of it
26- You don't understand what the code does yet — comprehend before you simplify
27- The code is performance-critical and the "simpler" version would be measurably slower
28- You're about to rewrite the module entirely — simplifying throwaway code wastes effort
29
30## The Five Principles
31
32### 1. Preserve Behavior Exactly
33
34Don't change what the code does — only how it expresses it. All inputs, outputs, side effects, error behavior, and edge cases must remain identical. If you're not sure a simplification preserves behavior, don't make it.
35
36```
37ASK BEFORE EVERY CHANGE:
38→ Does this produce the same output for every input?
39→ Does this maintain the same error behavior?
40→ Does this preserve the same side effects and ordering?
41→ Do all existing tests still pass without modification?
42```
43
44### 2. Follow Project Conventions
45
46Simplification means making code more consistent with the codebase, not imposing external preferences. Before simplifying:
47
48```
491. Read CLAUDE.md / project conventions
502. Study how neighboring code handles similar patterns
513. Match the project's style for:
52 - Import ordering and module system
53 - Function declaration style
54 - Naming conventions
55 - Error handling patterns
56 - Type annotation depth
57```
58
59Simplification that breaks project consistency is not simplification — it's churn.
60
61### 3. Prefer Clarity Over Cleverness
62
63Explicit code is better than compact code when the compact version requires a mental pause to parse.
64
65```typescript
66// UNCLEAR: Dense ternary chain
67const label = isNew ? 'New' : isUpdated ? 'Updated' : isArchived ? 'Archived' : 'Active';
68
69// CLEAR: Readable mapping
70function getStatusLabel(item: Item): string {
71 if (item.isNew) return 'New';
72 if (item.isUpdated) return 'Updated';
73 if (item.isArchived) return 'Archived';
74 return 'Active';
75}
76```
77
78```typescript
79// UNCLEAR: Chained reduces with inline logic
80const result = items.reduce((acc, item) => ({
81 ...acc,
82 [item.id]: { ...acc[item.id], count: (acc[item.id]?.count ?? 0) + 1 }
83}), {});
84
85// CLEAR: Named intermediate step
86const countById = new Map<string, number>();
87for (const item of items) {
88 countById.set(item.id, (countById.get(item.id) ?? 0) + 1);
89}
90```
91
92### 4. Maintain Balance
93
94Simplification has a failure mode: over-simplification. Watch for these traps:
95
96- **Inlining too aggressively** — removing a helper that gave a concept a name makes the call site harder to read
97- **Combining unrelated logic** — two simple functions merged into one complex function is not simpler
98- **Removing "unnecessary" abstraction** — some abstractions exist for extensibility or testability, not complexity
99- **Optimizing for line count** — fewer lines is not the goal; easier comprehension is
100
101### 5. Scope to What Changed
102
103Default to simplifying recently modified code. Avoid drive-by refactors of unrelated code unless explicitly asked to broaden scope. Unscoped simplification creates noise in diffs and risks unintended regressions.
104
105## The Simplification Process
106
107### Step 1: Understand Before Touching (Chesterton's Fence)
108
109Before changing or removing anything, understand why it exists. This is Chesterton's Fence: if you see a fence across a road and don't understand why it's there, don't tear it down. First understand the reason, then decide if the reason still applies.
110
111```
112BEFORE SIMPLIFYING, ANSWER:
113- What is this code's responsibility?
114- What calls it? What does it call?
115- What are the edge cases and error paths?
116- Are there tests that define the expected behavior?
117- Why might it have been written this way? (Performance? Platform constraint? Historical reason?)
118- Check git blame: what was the original context for this code?
119```
120
121If you can't answer these, you're not ready to simplify. Read more context first.
122
123### Step 2: Identify Simplification Opportunities
124
125Scan for these patterns — each one is a concrete signal, not a vague smell:
126
127**Structural complexity:**
128
129| Pattern | Signal | Simplification |
130|---------|--------|----------------|
131| Deep nesting (3+ levels) | Hard to follow control flow | Extract conditions into guard clauses or helper functions |
132| Long functions (50+ lines) | Multiple responsibilities | Split into focused functions with descriptive names |
133| Nested ternaries | Requires mental stack to parse | Replace with if/else chains, switch, or lookup objects |
134| Boolean parameter flags | doThing(true, false, true) | Replace with options objects or separate functions |
135| Repeated conditionals | Same if check in multiple places | Extract to a well-named predicate function |
136
137**Naming and readability:**
138
139| Pattern | Signal | Simplification |
140|---------|--------|----------------|
141| Generic names | data, result, temp, val, item | Rename to describe the content: userProfile, validationErrors |
142| Abbreviated names | usr, cfg, btn, evt | Use full words unless the abbreviation is universal (id, url, api) |
143| Misleading names | Function named get that also mutates state | Rename to reflect actual behavior |
144| Comments explaining "what" | // increment counter above count++ | Delete the comment — the code is clear enough |
145| Comments explaining "why" | // Retry because the API is flaky under load | Keep these — they carry intent the code can't express |
146
147**Redundancy:**
148
149| Pattern | Signal | Simplification |
150|---------|--------|----------------|
151| Duplicated logic | Same 5+ lines in multiple places | Extract to a shared function |
152| Dead code | Unreachable branches, unused variables, commented-out blocks | Remove (after confirming it's truly dead) |
153| Unnecessary abstractions | Wrapper that adds no value | Inline the wrapper, call the underlying function directly |
154| Over-engineered patterns | Factory-for-a-factory, strategy-with-one-strategy | Replace with the simple direct approach |
155| Redundant type assertions | Casting to a type that's already inferred | Remove the assertion |
156
157### Step 3: Apply Changes Incrementally
158
159Make one simplification at a time. Run tests after each change. **Submit refactoring changes separately from feature or bug fix changes.** A PR that refactors and adds a feature is two PRs — split them.
160
161```
162FOR EACH SIMPLIFICATION:
1631. Make the change
1642. Run the test suite
1653. If tests pass → commit (or continue to next simplification)
1664. If tests fail → revert and reconsider
167```
168
169Avoid batching multiple simplifications into a single untested change. If something breaks, you need to know which simplification caused it.
170
171**The Rule of 500:** If a refactoring would touch more than 500 lines, invest in automation (codemods, sed scripts, AST transforms) rather than making the changes by hand. Manual edits at that scale are error-prone and exhausting to review.
172
173### Step 4: Verify the Result
174
175After all simplifications, step back and evaluate the whole:
176
177```
178COMPARE BEFORE AND AFTER:
179- Is the simplified version genuinely easier to understand?
180- Did you introduce any new patterns inconsistent with the codebase?
181- Is the diff clean and reviewable?
182- Would a teammate approve this change?
183```
184
185If the "simplified" version is harder to understand or review, revert. Not every simplification attempt succeeds.
186
187## Language-Specific Guidance
188
189### TypeScript / JavaScript
190
191```typescript
192// SIMPLIFY: Unnecessary async wrapper
193// Before
194async function getUser(id: string): Promise<User> {
195 return await userService.findById(id);
196}
197// After
198function getUser(id: string): Promise<User> {
199 return userService.findById(id);
200}
201
202// SIMPLIFY: Verbose conditional assignment
203// Before
204let displayName: string;
205if (user.nickname) {
206 displayName = user.nickname;
207} else {
208 displayName = user.fullName;
209}
210// After
211const displayName = user.nickname || user.fullName;
212
213// SIMPLIFY: Manual array building
214// Before
215const activeUsers: User[] = [];
216for (const user of users) {
217 if (user.isActive) {
218 activeUsers.push(user);
219 }
220}
221// After
222const activeUsers = users.filter((user) => user.isActive);
223
224// SIMPLIFY: Redundant boolean return
225// Before
226function isValid(input: string): boolean {
227 if (input.length > 0 && input.length < 100) {
228 return true;
229 }
230 return false;
231}
232// After
233function isValid(input: string): boolean {
234 return input.length > 0 && input.length < 100;
235}
236```
237
238### Python
239
240```python
241# SIMPLIFY: Verbose dictionary building
242# Before
243result = {}
244for item in items:
245 result[item.id] = item.name
246# After
247result = {item.id: item.name for item in items}
248
249# SIMPLIFY: Nested conditionals with early return
250# Before
251def process(data):
252 if data is not None:
253 if data.is_valid():
254 if data.has_permission():
255 return do_work(data)
256 else:
257 raise PermissionError("No permission")
258 else:
259 raise ValueError("Invalid data")
260 else:
261 raise TypeError("Data is None")
262# After
263def process(data):
264 if data is None:
265 raise TypeError("Data is None")
266 if not data.is_valid():
267 raise ValueError("Invalid data")
268 if not data.has_permission():
269 raise PermissionError("No permission")
270 return do_work(data)
271```
272
273### React / JSX
274
275```tsx
276// SIMPLIFY: Verbose conditional rendering
277// Before
278function UserBadge({ user }: Props) {
279 if (user.isAdmin) {
280 return <Badge variant="admin">Admin</Badge>;
281 } else {
282 return <Badge variant="default">User</Badge>;
283 }
284}
285// After
286function UserBadge({ user }: Props) {
287 const variant = user.isAdmin ? 'admin' : 'default';
288 const label = user.isAdmin ? 'Admin' : 'User';
289 return <Badge variant={variant}>{label}</Badge>;
290}
291
292// SIMPLIFY: Prop drilling through intermediate components
293// Before — consider whether context or composition solves this better.
294// This is a judgment call — flag it, don't auto-refactor.
295```
296
297## Common Rationalizations
298
299| Rationalization | Reality |
300|---|---|
301| "It's working, no need to touch it" | Working code that's hard to read will be hard to fix when it breaks. Simplifying now saves time on every future change. |
302| "Fewer lines is always simpler" | A 1-line nested ternary is not simpler than a 5-line if/else. Simplicity is about comprehension speed, not line count. |
303| "I'll just quickly simplify this unrelated code too" | Unscoped simplification creates noisy diffs and risks regressions in code you didn't intend to change. Stay focused. |
304| "The types make it self-documenting" | Types document structure, not intent. A well-named function explains *why* better than a type signature explains *what*. |
305| "This abstraction might be useful later" | Don't preserve speculative abstractions. If it's not used now, it's complexity without value. Remove it and re-add when needed. |
306| "The original author must have had a reason" | Maybe. Check git blame — apply Chesterton's Fence. But accumulated complexity often has no reason; it's just the residue of iteration under pressure. |
307| "I'll refactor while adding this feature" | Separate refactoring from feature work. Mixed changes are harder to review, revert, and understand in history. |
308
309## Red Flags
310
311- Simplification that requires modifying tests to pass (you likely changed behavior)
312- "Simplified" code that is longer and harder to follow than the original
313- Renaming things to match your preferences rather than project conventions
314- Removing error handling because "it makes the code cleaner"
315- Simplifying code you don't fully understand
316- Batching many simplifications into one large, hard-to-review commit
317- Refactoring code outside the scope of the current task without being asked
318
319## Verification
320
321After completing a simplification pass:
322
323- [ ] All existing tests pass without modification
324- [ ] Build succeeds with no new warnings
325- [ ] Linter/formatter passes (no style regressions)
326- [ ] Each simplification is a reviewable, incremental change
327- [ ] The diff is clean — no unrelated changes mixed in
328- [ ] Simplified code follows project conventions (checked against CLAUDE.md or equivalent)
329- [ ] No error handling was removed or weakened
330- [ ] No dead code was left behind (unused imports, unreachable branches)
331- [ ] A teammate or review agent would approve the change as a net improvement
332