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.

Day 17: How To Clean Up Messy Code Without Breaking Anything

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."