Confident Refactoring: Using prepareChange Before Breaking Things
By CKB Team
Refactoring is scary. You want to improve the code, but you're not sure what depends on it. So you make a change, run the tests, hope for the best, and deal with the fallout in production.
There's a better way. CKB's prepareChange tells you exactly what breaks before you touch anything.
The Fear of Refactoring
Every developer knows this moment:
"I should refactor this. It's messy, hard to test, and everyone complains about it. But... what if I break something? What if there's some edge case I don't know about? Maybe I'll just leave it."
The code stays messy. Technical debt accumulates. Eventually, someone has to change it, and then it's a firefight.
This fear is rational. In a codebase of any size, you genuinely don't know all the dependencies. Tests help, but they don't cover everything. Code review helps, but reviewers don't know every consumer either.
Enter prepareChange
prepareChange answers the question: "What happens if I change this?"
User: "What's the impact of changing the AuthMiddleware?"
AI: [calls prepareChange for AuthMiddleware]
"Impact analysis for AuthMiddleware (internal/middleware/auth.go):
Direct dependencies: 14 files
- All route handlers in internal/api/handlers/
- Integration test setup in tests/setup.go
- Mock in tests/mocks/auth_mock.go
Transitive impact: 8 additional files
- Controllers that depend on authenticated routes
- E2E tests that use authenticated endpoints
Tests covering this code: 23
- Unit: auth_middleware_test.go (12 tests)
- Integration: api_test.go (8 tests)
- E2E: auth_e2e_test.go (3 tests)
Risk score: 58 (Medium)
- High fan-out (14 direct callers)
- Good test coverage mitigates risk
- Recent stability (no changes in 45 days)
Suggested reviewers: @alice (author), @bob (recent modifier)"
Now you know:
- Exactly what depends on this code
- What tests cover it
- How risky the change is
- Who should review it
How prepareChange Works
prepareChange aggregates multiple analyses:
- Dependency tracing - Static analysis of what references the target
- Transitive impact - What depends on the dependents
- Test mapping - Which tests exercise this code path
- Risk calculation - Based on fan-out, churn, and coverage
- Ownership lookup - Who knows this code best
All of this runs against CKB's pre-built semantic index, so it's fast—typically under a second even for large codebases.
Change Types
prepareChange understands different types of changes:
Modify
{ "target": "AuthMiddleware", "changeType": "modify" }
Changing implementation without changing the interface. Impact is limited to:
- Code that depends on specific behavior
- Tests that verify that behavior
Rename
{ "target": "getUserById", "changeType": "rename" }
Renaming a function or type. Impact includes:
- All call sites that need updating
- String references (logging, errors)
- Documentation mentioning the name
Delete
{ "target": "legacyHandler", "changeType": "delete" }
Removing code entirely. Impact is:
- Any remaining callers (should be zero for safe deletion)
- Tests that will break
- Documentation to update
Extract
{ "target": "handleRequest", "changeType": "extract" }
Extracting part of a function into a new function. Impact analysis helps you:
- Identify what can be safely extracted
- See what variables would need to be passed
- Find similar code that could use the extraction
Workflow: Refactoring with Confidence
Step 1: Assess the Target
User: "I want to simplify the PaymentProcessor. What's the risk?"
AI: [calls prepareChange]
"PaymentProcessor analysis:
Complexity: 42 (cyclomatic) - very high
Lines: 580
Methods: 23
This is a refactoring hotspot:
- Modified 34 times in past 6 months
- 5 different authors
- 3 open bugs reference this file
Risk score: 78 (High)
- High complexity
- Many dependencies (31 files)
- Core business logic
Recommendation: Break into smaller changes.
Consider extracting PaymentValidator first (lower risk)."
Step 2: Plan the Approach
User: "What if I extract the validation logic first?"
AI: [calls prepareChange for validation methods]
"Extracting validation logic from PaymentProcessor:
Affected methods:
- validateCard() - 12 references
- validateAmount() - 8 references
- validateMerchant() - 6 references
If extracted to PaymentValidator:
- 26 reference updates needed
- 15 tests need updating
- Risk score drops to 34 (Low)
This is a good first step. Validation logic has:
- Clear boundaries
- High cohesion
- Good test coverage
Would you like me to show the extraction plan?"
Step 3: Verify Test Coverage
User: "Are there any gaps in test coverage for this refactoring?"
AI: [calls getAffectedTests with coverage data]
"Test coverage for PaymentProcessor validation:
Covered paths:
✓ Valid card → success
✓ Invalid card number → error
✓ Expired card → error
✓ Valid amount → success
✓ Negative amount → error
Uncovered paths:
⚠ Amount exactly at limit (boundary)
⚠ Card with future expiry > 10 years
⚠ Merchant with special characters in name
Recommendation: Add boundary tests before refactoring.
3 tests would close the gaps."
Step 4: Execute with Confidence
Now you can refactor knowing:
- Exactly what will be affected
- That tests cover the critical paths
- Who should review the change
- The risk is acceptable
User: "Generate the PR description for extracting PaymentValidator"
AI: "## Extract PaymentValidator from PaymentProcessor
### Summary
Extracts validation logic into dedicated PaymentValidator class,
reducing PaymentProcessor complexity from 42 to 28.
### Changes
- New: internal/payment/validator.go (PaymentValidator)
- Modified: internal/payment/processor.go (uses PaymentValidator)
- Modified: 26 files updating import paths
- Modified: 15 test files
### Impact Analysis (via CKB)
- Risk score: 34 (Low)
- Test coverage: 94% of affected paths
- Zero transitive impact on public APIs
### Testing
- All existing tests pass
- Added 3 boundary tests for edge cases
- Manual verification of payment flow in staging"
Real-World Example
A fintech team needed to refactor their transaction processing. Without CKB:
"We spent 2 days manually tracing dependencies. Made the change. Broke 3 things in production that we missed. Spent another day fixing. Total: 4 days."
With CKB:
"prepareChange showed us 47 dependents we'd have missed. Identified 2 untested code paths. The whole refactor took 6 hours including review."
When to Use prepareChange
Always use it before:
- Renaming public APIs
- Modifying interfaces or base classes
- Deleting code you think is unused
- Refactoring core business logic
- Changing data structures
You can skip it for:
- Internal implementation details with no callers
- Adding new code (nothing depends on it yet)
- Documentation changes
Best Practices
1. Check Before You Code
Run prepareChange before writing any refactoring code. It might reveal the change is riskier than expected.
2. Use Risk Scores
- Low (0-30): Proceed with normal review
- Medium (31-60): Extra testing recommended
- High (61-80): Consider breaking into smaller changes
- Critical (81-100): Get sign-off from code owners first
3. Address Coverage Gaps First
If prepareChange shows uncovered paths, add tests before refactoring. This catches regressions early.
4. Document the Analysis
Include CKB's impact analysis in your PR description. Reviewers appreciate knowing you've done due diligence.
The Confidence Payoff
Refactoring without fear changes how you work:
- You actually refactor instead of avoiding it
- PRs include impact analysis, making review easier
- Production incidents from refactoring drop dramatically
- Technical debt gets paid down consistently
Stop guessing. Know your impact. Refactor with confidence.
Links: