The Hook (The "Byte-Sized" Intro)
Refactoring legacy code is like renovating a house while people live in it. You can't tear everything down at once. The strangler fig pattern โ build new alongside old, redirect traffic gradually, remove old when empty โ is the safest approach. Git supports this with parallel branches, feature flags, and incremental PRs.
๐ What is the Legacy Refactor Workflow?
A Git workflow designed for safely replacing or overhauling legacy code systems through incremental changes rather than big-bang rewrites.
Conceptual Clarity
Refactoring approaches:
| Approach | Risk | Speed | Best For |
|---|---|---|---|
| Big-bang rewrite | ๐ด Very high | Fast (appears) | Never recommended |
| Strangler fig | ๐ข Low | Gradual | Large systems |
| Branch by abstraction | ๐ข Low | Medium | Internal refactors |
| Parallel implementation | ๐ก Medium | Medium | API replacements |
Strangler fig steps:
| Phase | Git Action |
|---|---|
| 1. Create abstraction layer | Small PR, merge to main |
| 2. Build new implementation | PRs behind feature flag |
| 3. Route traffic to new | Gradual flag rollout |
| 4. Verify new works | Monitor and compare |
| 5. Remove old code | Clean-up PR |
Real-Life Analogy
The strangler fig tree grows around an existing tree, gradually replacing it. The old tree supports the new one until the new tree can stand alone. Then the old tree is safely removed. No clear-cutting. No risk of collapse.
Visual Architecture
Why It Matters
- Safety: Production is never broken by half-finished refactors.
- Reversibility: Feature flags allow instant rollback to old code.
- Incremental progress: Each PR is small, reviewable, and deployable.
- Proof: New code is proven in production before old code is removed.
Code
# โโโ Phase 1: Create abstraction (interface) โโโ
git checkout -b refactor/payment-interface
# Create PaymentProcessor interface that old code implements
# Small PR, merge to main
# โโโ Phase 2: Build new implementation โโโ
git checkout -b refactor/new-payment-processor
# Implement new PaymentProcessor behind feature flag
# if (flags.newPaymentProcessor) { useNew() } else { useOld() }
# PR 1: New processor class
# PR 2: Integration with existing system
# PR 3: Migration utilities
# โโโ Phase 3: Gradual rollout โโโ
# Enable flag for 5% โ 25% โ 50% โ 100%
# Monitor error rates, latency, correctness
# โโโ Phase 4: Remove old code โโโ
git checkout -b chore/remove-legacy-payment
# Remove old implementation, feature flag, and abstraction
# One clean PR
# โโโ Each PR should be small โโโ
# Bad: 1 PR with 5,000 lines
# Good: 10 PRs with 200-500 lines eachKey Takeaways
- Never do a big-bang rewrite โ use the strangler fig pattern.
- Build new code behind feature flags while old code runs in production.
- Gradual rollout proves the new code works before removing the old.
- Each step is a small, reviewable PR โ never one massive change.
Interview Prep
-
Q: How do you safely refactor a critical legacy system? A: Use the strangler fig pattern: create an abstraction layer, build the new implementation behind a feature flag, gradually route traffic to the new code, verify it works, then remove the old code. Each step is a separate, small PR.
-
Q: Why are big-bang rewrites risky? A: They're all-or-nothing โ if the new system has issues, there's no fallback. They also take months, during which the old system still needs maintenance. The team effectively maintains two systems with no production validation of the new one.
-
Q: What is "branch by abstraction"? A: A technique where you introduce an abstraction (interface) that both the old and new implementations satisfy. You swap implementations behind the abstraction without changing consumers. It enables incremental replacement.