Skip to content

πŸ›οΈ 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.

git log --oneline --all --graph --decorate | grep "Delete legacy"
# Celebrate each deletion

⏰ 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