Day 17: How To Clean Up Messy Code Without Breaking Anything
Learn to refactor with Claude Code without breaking anything. One golden rule, four refactor types, complete workflow. 400-line component → clean focused files.
Hey, it's G.
Day 17 of the Claude Code series.
Refactoring was the task I avoided the longest.
The code works. Touching it feels risky. Doing it manually is tedious.
Claude Code changed that.
I actually refactor regularly now — because it's fast, it follows rules, and git means I can always revert.
Today I'm sharing how to run a refactor session that improves your codebase without introducing new bugs.
The Problem (Refactoring Used to Feel Too Risky)
Here's why I avoided refactoring:
The code works. It's not pretty, but users don't care about clean code — they care that it works.
Touching working code feels risky. What if I break something?
Doing it manually is tedious. Renaming variables across files. Splitting components. Extracting helpers. All boring, repetitive work.
No immediate benefit. The app doesn't get new features. Just cleaner internals.
So I didn't refactor.
And the codebase got messier:
- 400-line components doing five different things
- Functions with unclear names like
handleSubmit2() - Duplicated logic copy-pasted across files
- Dead code no one remembered existed
Until working in the codebase became slow and error-prone.
The Concept (One Golden Rule Makes It Safe)
Refactoring with Claude Code has one golden rule:
Separate refactoring from feature work.
Never ask Claude Code to refactor AND add new functionality in the same session.
When you mix the two, you lose the ability to tell which change caused a problem.
A refactor session has one job:
Improve the code without changing what it does.
Same inputs. Same outputs. Same behavior.
Just cleaner internals.
There are four types of refactoring worth doing with Claude Code:
1. Readability
Better names, clearer structure, shorter functions
2. Decomposition
Splitting large files or components into smaller focused ones
3. Pattern Consistency
Making code follow the same conventions throughout
4. Dead Code Removal
Finding and deleting code that's no longer used
Each type needs slightly different instructions.
The common thread:
Always tell Claude Code what it cannot change:
- Function signatures
- Behavior
- External APIs
- Exported interfaces
The constraint is what makes the refactor safe.
The /refactor Slash Command
Build this command once, use it forever:
touch ~/.claude/commands/refactor.md
Inside the file:
Refactor the current file for readability and maintainability.
Rules — do not break these:
- Do not change any function signatures
- Do not change any logic or behavior
- Do not add new dependencies
- Do not rename exported functions or components
- Flag anything that looks like a bug — note it, don't fix it
What you should improve:
- Variable and function names that are unclear
- Functions longer than 30 lines — break them up
- Duplicated logic — extract into shared helpers
- Deeply nested conditionals — flatten where possible
- Any code that needs a comment to be understood —
rename it so the comment isn't needed
Type 1: Readability Refactor
When a file is hard to read but not huge:
claude
> view utils/payments.ts
> /refactor
Claude responds:
Refactoring utils/payments.ts for readability...
Changes made:
1. Renamed handlePayment() → processStripePayment()
(more specific about what it does)
2. Renamed data → paymentData
(generic names are harder to track)
3. Split calculateTotal() into two functions:
- calculateSubtotal() (line items only)
- applyDiscountAndTax() (final calculations)
(original was 45 lines doing too many things)
4. Extracted duplicated error handling into
handlePaymentError() helper
5. Flattened nested if statements in validatePayment()
(was 4 levels deep, now 2 levels max)
No behavior changes. All function signatures preserved.
Review the changes:
git diff
Run build to verify nothing broke:
> Run npm run build and npm run typecheck.
> Report any errors.
If clean, commit:
git add utils/payments.ts
git commit -m "refactor: improve readability in payments utils"
Type 2: Decomposition Refactor
When a file is too large and doing too many things:
claude
> /components/billing/BillingPage.tsx is 380 lines and
> doing too many things.
>
> Break it into smaller focused components:
> - BillingPage.tsx → main page, just orchestration
> - BillingSummary.tsx → current plan display
> - BillingUsage.tsx → usage meter
> - UpgradeModal.tsx → upgrade flow
>
> Keep all existing props interfaces and behavior exactly
> the same. Show me your plan before touching anything.
Claude responds with plan:
Plan for decomposing BillingPage.tsx:
Current structure (380 lines):
- Fetching logic (lines 15-45)
- Plan display (lines 50-120)
- Usage meter (lines 125-180)
- Upgrade modal (lines 185-280)
- Error handling (lines 285-320)
- Loading states (lines 325-365)
Proposed structure:
1. BillingPage.tsx (80 lines)
- Main orchestration
- Fetching logic
- Error/loading states
- Renders child components
2. BillingSummary.tsx (40 lines)
- Props: { plan: Plan }
- Displays current plan tier and details
3. BillingUsage.tsx (35 lines)
- Props: { usage: number, limit: number }
- Shows usage meter with percentage
4. UpgradeModal.tsx (60 lines)
- Props: { isOpen: boolean, onClose: () => void }
- Handles upgrade flow
All props interfaces will be exported.
No behavior changes.
Component tree remains the same.
Proceed?
Review the plan. If good, approve:
> Looks good. Go ahead.
Claude refactors. Before reviewing the diff, ask for explanation:
> Walk me through every change you made and why.
> Flag anything you were unsure about.
Claude responds:
Changes walkthrough:
1. Created BillingSummary.tsx
- Extracted lines 50-120 from BillingPage
- Added PlanSummaryProps interface
- No logic changes, pure extraction
2. Created BillingUsage.tsx
- Extracted lines 125-180
- Added UsageMeterProps interface
- Percentage calculation moved here (was inline)
3. Created UpgradeModal.tsx
- Extracted lines 185-280
- Added UpgradeModalProps interface
- No changes to Stripe integration logic
4. Updated BillingPage.tsx
- Removed extracted code
- Added imports for new components
- Props passed down match exactly
- Render structure identical
Uncertain about:
- BillingUsage percentage calculation was inline
I moved it to a local function in the component
Behavior is identical but location changed
Should I extract it to utils instead?
All TypeScript types checked.
All exports preserved.
No new dependencies added.
Review the diff:
git diff
Run build:
> Run npm run build and npm run typecheck.
If clean, commit:
git add .
git commit -m "refactor: split BillingPage into focused components"
One session. 380-line component becomes four clean focused files. Zero behavior changes.
Type 3: Pattern Consistency Refactor
When code across files doesn't follow the same patterns:
claude
> Look through /app/api/ and find all the API routes
> that don't follow the same error handling pattern
> as /app/api/users/route.ts.
>
> Update them to match — same structure, same error
> format, same response shape.
>
> Do not change any business logic.
> Show me what you find before making changes.
Claude responds:
Analyzed all routes in /app/api/:
Pattern from users/route.ts (our standard):
- try/catch wraps all logic
- Errors return NextResponse with status and message
- Success returns NextResponse with data
- Format: { data?, error?, message? }
Routes that don't match:
1. /app/api/subscriptions/route.ts
- No try/catch
- Errors throw without catching
- Inconsistent response format
2. /app/api/billing/route.ts
- Has try/catch but different error format
- Returns raw Error objects instead of shaped response
3. /app/api/webhooks/stripe/route.ts
- Correct pattern (matches users/route.ts)
Proposed changes:
- Update subscriptions/route.ts with try/catch
- Update billing/route.ts error format
- No changes to business logic, only error handling structure
Proceed?
Approve:
> Go ahead.
Review, build, commit.
Type 4: Dead Code Removal
Find code that's no longer used:
claude
> Search the entire project for:
> - Exported functions that are never imported anywhere
> - Components that are never used
> - Variables that are declared but never read
> - Commented-out code blocks
>
> List everything you find before removing anything.
> I'll confirm what's safe to delete.
Claude responds:
Dead code analysis:
Unused exports:
1. utils/dates.ts → formatDateLong()
Exported but never imported anywhere
2. components/ui/Toast.tsx
Component exported but never imported
3. hooks/useDebounce.ts → useDebounce()
Hook defined but never used
Unused variables:
1. app/billing/page.tsx → const maxRetries = 3
Declared but never read
2. lib/stripe.ts → const WEBHOOK_SIGNING_SECRET
Commented out, replaced with env var
Commented-out code:
1. components/profile/ProfileForm.tsx lines 45-67
Old validation logic, replaced 3 commits ago
2. utils/api.ts lines 12-25
Old error handler, no longer used
Confirm deletions?
- formatDateLong: safe to remove
- Toast component: safe to remove
- useDebounce: safe to remove
- maxRetries: safe to remove
- Old commented blocks: safe to remove
Review the list. Confirm:
> All of these look safe. Remove them.
Claude removes dead code. Review, build, commit.
The Safe Refactor Workflow
Here's the complete workflow I use every time:
Step 1: Clean Baseline
Commit everything before you start:
git add .
git commit -m "before refactor"
This is your revert point if something goes wrong.
Step 2: Run the Refactor
claude
> view [file or folder]
> /refactor
Or more specific:
> Split this large component into smaller ones
> Make these routes follow the same error pattern
> Find and remove dead code
Step 3: Ask Claude to Explain
Before reviewing the diff:
> Before I review the diff, walk me through every
> change you made and why. Flag anything you were
> unsure about.
This is the fastest way to catch a refactor that went slightly wrong.
Step 4: Review the Diff
git diff
Look for:
- Did it change behavior?
- Did it rename exports?
- Did it change function signatures?
- Anything that looks wrong?
Step 5: Run Build and Typecheck
> Run npm run build and npm run typecheck.
> Report any errors.
Always do this. Catches type errors the diff doesn't show.
Step 6: Commit or Revert
If clean:
git add .
git commit -m "refactor: [description]"
If broken:
git checkout .
Instant revert. Try again with better constraints.
Real Refactor Session (Complete Example)
Let me walk you through a real refactor I did in Resiboko.
The Problem
/components/dashboard/Dashboard.tsx was 400 lines and doing everything:
- Fetching expense data
- Calculating totals
- Rendering charts
- Handling filters
- Managing modal state
- Error handling
It was hard to read. Hard to test. Hard to change.
Step 1: Baseline
git add .
git commit -m "before dashboard refactor"
Step 2: Refactor
cd ~/projects/resiboko
claude
> /components/dashboard/Dashboard.tsx is 400 lines
> doing too many things.
>
> Break it into focused components:
> - Dashboard.tsx → orchestration only
> - ExpenseChart.tsx → chart rendering
> - ExpenseFilters.tsx → filter controls
> - ExpenseSummary.tsx → totals display
> - AddExpenseModal.tsx → modal for adding
>
> Keep all props interfaces and behavior exactly the same.
> Show me the plan first.
Step 3: Review Plan
Claude shows plan. I approve:
> Looks good. Go ahead.
Step 4: Explain Changes
> Walk me through every change you made and why.
Claude explains:
Created 4 new components by extracting sections:
1. ExpenseChart.tsx (80 lines)
- Extracted chart rendering logic
- Props: { expenses, dateRange }
- Uses recharts (already installed)
2. ExpenseFilters.tsx (45 lines)
- Extracted filter UI and state
- Props: { onFilterChange }
- Callback pattern for parent state
3. ExpenseSummary.tsx (35 lines)
- Extracted totals calculation and display
- Props: { expenses }
- Pure calculation, no side effects
4. AddExpenseModal.tsx (90 lines)
- Extracted modal logic
- Props: { isOpen, onClose, onAdd }
- Same Supabase integration
Updated Dashboard.tsx (150 lines now):
- Imports all new components
- Handles fetching and state
- Orchestrates child components
- Props remain unchanged
All TypeScript types preserved.
No new dependencies.
No behavior changes.
Step 5: Review Diff
git diff
Looks good. All extractions, no behavior changes.
Step 6: Build
> Run npm run build and npm run typecheck.
Claude responds:
Build successful.
TypeScript check passed.
No errors.
Step 7: Commit
git add .
git commit -m "refactor: split Dashboard into focused components
- Created ExpenseChart, ExpenseFilters, ExpenseSummary, AddExpenseModal
- Dashboard.tsx reduced from 400 to 150 lines
- No behavior changes, all props preserved"
Done.
400-line component → 5 focused files
One session. Zero bugs introduced.
Why This Matters
Messy code slows everything down.
Not just you. Claude Code too.
When files are tangled and components are doing five jobs at once, Claude Code makes more mistakes because the context is harder to follow.
Refactoring regularly keeps your codebase in a state where Claude Code can move fast and accurately.
Clean code isn't just good practice.
When you work with an AI agent every day, it's a performance optimization.
Common Refactoring Mistakes
❌ Mistake 1: Mixing Refactor with Feature Work
> Refactor this component AND add a new filter feature
Problem: If something breaks, you don't know which change caused it.
Fix: One session, one job. Refactor OR feature. Not both.
❌ Mistake 2: No Baseline Commit
Starting a refactor without committing first.
Problem: No easy revert if it goes wrong.
Fix: Always git commit -m "before refactor" first.
❌ Mistake 3: Skipping the Explain Step
Reviewing the diff without asking Claude to explain changes first.
Problem: You might miss a subtle behavior change.
Fix: Always ask "Walk me through every change you made" before reviewing diff.
❌ Mistake 4: Not Running Build
Committing refactor without running build/typecheck.
Problem: Type errors you didn't see in the diff.
Fix: Always run npm run build and npm run typecheck before committing.
❌ Mistake 5: Too Many Files at Once
> Refactor everything in /components
Problem: Massive diff, hard to review, hard to revert if needed.
Fix: One file or component at a time. Multiple small refactors, not one giant one.
My Raw Notes (Unfiltered)
The separate refactoring from feature work rule is the one I kept breaking early on.
Mixed a refactor with a small feature addition and spent an hour figuring out which change broke the build. Never again.
The dead code removal prompt is underrated — Claude found three components in Resiboko that hadn't been imported anywhere in months.
The "explain every change before I review the diff" habit is new but it's already saved me from approving a refactor that quietly renamed an exported function.
Always run build and typecheck after — caught two type errors from refactors that looked clean in the diff.
Tomorrow (Day 18 Preview)
Topic: Writing Tests with Claude Code — how to make Claude Code write your tests so you actually have them.
What I'm testing: Can Claude Code write comprehensive tests that actually catch bugs? How do you guide it to write tests that matter?
Following This Series
Phase 3 (Days 15-21): Vibe Coding ⬅️ You are here
So far in Phase 3:
- Day 14: Vibe coding mindset
- Day 15: Feature-first workflow
- Day 16: Debugging with Claude Code
- Day 17: Refactoring with Claude Code (today)
- Day 18: Writing tests with Claude Code (tomorrow)
G
P.S. - One golden rule: never mix refactoring with feature work. One session, one job. When you mix them you can't tell which change broke something.
P.P.S. - Always commit before refactoring: git commit -m "before refactor". Instant revert if something goes wrong.
P.P.P.S. - The explain-before-review step catches bad refactors before you commit them. "Walk me through every change you made and why."