ποΈ Legacy Code Policy¶
π― Philosophy¶
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β "Leave code better than you found it" β β - Boy Scout Rule β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Legacy code exists. Perfect is the enemy of good. Incremental improvement beats Big Rewrite.
π Rules¶
Touch-Only Policy¶
If modifying legacy file: - β Bring touched code up to standards - β Update tests for modified code - β Document if can't fully modernize
If NOT modifying: - β Don't proactively refactor - β Avoid "style-only" PRs - β Wait for functional change
Example:
// BEFORE: Legacy code
void processAudio(float* buf, int len) {
for (int i = 0; i < len; i++) {
buf[i] = buf[i] * 2.0; // Magic number
}
}
// AFTER: Fixed bug, modernized touched code
void processAudio(float* buffer, size_t length) {
constexpr float GAIN = 2.0f;
for (size_t i = 0; i < length; ++i) {
buffer[i] *= GAIN; // β Fixed: was multiplying by 2.0, should be GAIN
}
}
Gradual Improvement¶
Iteration 1: Fix bug, modernize that function Iteration 2: Add feature, refactor related code Iteration 3: Performance work, restructure module
β After 3 iterations: Much better code
Don't wait for "perfect time" to refactor entire module.
No New Tech Debt¶
Even in legacy code, don't add new problems.
β BAD: Add hack to legacy code
// Legacy code
void legacyFunction() {
// TODO: This is terrible
}
void yourNewCode() {
legacyFunction();
// HACK: Workaround for legacy bug
if (globalState == BROKEN) {
fix_manually();
}
}
β GOOD: Add proper abstraction, even in legacy
// New abstraction
class ProperAbstraction {
void doThingCorrectly() {
// Modern, tested, clean
}
};
void yourNewCode() {
ProperAbstraction proper;
proper.doThingCorrectly();
}
πΊοΈ Migration Strategy¶
For critical legacy modules:
1. Characterization Tests¶
Lock down current behavior before changing anything.
// Test current behavior, even if wrong
TEST(LegacyFFT, CurrentBehavior) {
float input[1024] = {...};
float output[1024];
legacyFFT(input, output, 1024);
// Document current output (even if incorrect)
EXPECT_NEAR(output[0], 0.123456, 1e-6); // Current behavior
}
2. Incremental Refactoring¶
Small, shippable PRs. Each one adds value.
Week 1: Add tests for Module X Week 2: Extract function A, modernize Week 3: Extract function B, modernize Week 4: Refactor Module X internals Week 5: Update API, deprecate old
3. Replace Piece by Piece¶
Don't "stop the world" for rewrite.
// Phase 1: Add new implementation alongside old
#ifdef USE_NEW_FFT
newFFT(input, output, size);
#else
legacyFFT(input, output, size);
#endif
// Phase 2: Ship with flag, test in production
// Phase 3: Remove flag, delete legacy
4. Delete Old Code¶
Celebrate deletions! Ship the removal.
β° Timeline¶
Migration timeline: Months, not days
- Don't rush
- Each iteration ships
- Monitor for regressions
- Communicate with team
π« What NOT to Do¶
β Big Bang Rewrite¶
"Let's rewrite the entire audio engine!"
Why it fails: - Takes months/years - Meanwhile, bugs accumulate in old code - Hard to ship incrementally - Risky to switch over
β Parallel Maintenance¶
"Let's maintain old and new in parallel!"
Why it fails: - Double the work - Bug fixes needed in both - Technical debt in both - Never finish migration
β Style-Only Refactoring¶
"Let's fix all the formatting in legacy code!"
Why it fails: - No functional value - Makes git blame useless - Merge conflicts for everyone - Wastes review time
β What TO Do¶
β Opportunistic Improvement¶
Fix/add feature β Improve that area β Ship
β Strangler Fig Pattern¶
Gradually replace old system by growing new system around it.
[Legacy System]
β
[Legacy System] + [New Feature A (modern)]
β
[Legacy System] + [New Features A,B (modern)]
β
[Small Legacy] + [Modern System]
β
[Modern System]
β Feature Flags¶
Ship new implementation behind flag, validate, remove old.
if (config::useModernAudioEngine()) {
modernEngine.process(buffer);
} else {
legacyEngine.process(buffer); // Remove after validation
}
π Documentation¶
When working with legacy code:
/**
* @brief Process audio (LEGACY)
*
* NOTE: This function is legacy code.
* - Uses C-style arrays (should be std::span)
* - Magic numbers (should be constants)
* - No bounds checking
*
* TODO: Migrate to ModernAudioProcessor (see #123)
*
* @deprecated Use ModernAudioProcessor instead
*/
void processAudioLegacy(float* buffer, int size);
π Resources¶
- Working Effectively with Legacy Code - Michael Feathers
- Refactoring - Martin Fowler
- Strangler Fig Pattern - Martin Fowler
- Ship/Show/Ask - Rouan Wilsenach