Legacy Modernizer - Safe Legacy Code Evolution
🎯 When to Use This Skill
Use when dealing with:
-
Code written 5+ years ago
-
No tests or documentation
-
"Don't touch - it works" code
-
Deprecated dependencies
-
Security vulnerabilities in old code
-
Performance issues in legacy systems
⚡ The Golden Rule of Legacy Code
NEVER refactor without tests! First make it testable, then make it better.
📋 The Safe Modernization Process
Phase 1: UNDERSTAND (Don't Touch Yet!)
WITH MCP Tools:
"Analyze the architecture and dependencies of [legacy module]" "Document how this legacy code works"
WITHOUT MCP:
- Map the Territory:
Find all files related to the feature
grep -r "LegacyClass" --include=".js" --include=".py" --include="*.java"
Trace execution flow
echo "=== LEGACY CODE MAP ===" > legacy_analysis.md echo "Entry points:" >> legacy_analysis.md grep -r "main|init|start" --include="*.js" >> legacy_analysis.md
Find dependencies
echo "Dependencies:" >> legacy_analysis.md grep -r "require|import|include" legacy_module/ >> legacy_analysis.md
- Document Current Behavior:
// Add temporary logging to understand flow function legacyFunction(data) { console.log('[LEGACY TRACE] Input:', JSON.stringify(data)); // ... existing code ... console.log('[LEGACY TRACE] Output:', result); return result; }
Phase 2: PROTECT (Add Safety Net)
WITH MCP (Test Generator):
"Generate characterization tests for this legacy code"
WITHOUT MCP:
Characterization Tests (Capture Current Behavior):
// Not testing if it's "right", just what it currently does describe('Legacy System - Current Behavior', () => { it('should handle normal input as currently implemented', () => { const result = legacyFunction({ id: 1, name: 'test' });
// Capture EXACT current output
expect(result).toEqual({
status: 'OK',
data: 'TEST', // Even if this seems wrong!
timestamp: expect.any(Number),
});
});
it('should handle null input as currently implemented', () => { // Even if it crashes, document it! expect(() => legacyFunction(null)).toThrow('Cannot read property'); }); });
Golden Master Testing:
Capture current outputs
for input in test_inputs/*; do ./legacy_app < "$input" > "golden_masters/$(basename $input).output" done
After changes, verify outputs match
for input in test_inputs/*; do ./modernized_app < "$input" > temp.output diff "golden_masters/$(basename $input).output" temp.output done
Phase 3: ISOLATE (Strangler Fig Pattern)
Wrap Legacy Code:
// Step 1: Create wrapper (Facade) class ModernInterface { constructor() { this.legacy = new LegacySystem(); }
async processData(input) { // Modern interface const legacyFormat = this.convertToLegacyFormat(input); const result = this.legacy.oldProcessMethod(legacyFormat); return this.convertToModernFormat(result); }
convertToLegacyFormat(modern) { // Transform modern input to legacy format }
convertToModernFormat(legacy) { // Transform legacy output to modern format } }
Phase 4: MODERNIZE (Incremental Updates)
Safe Modernization Patterns:
- Branch by Abstraction:
class DataProcessor { constructor(useModern = false) { this.useModern = useModern; }
process(data) { if (this.useModern) { return this.modernProcess(data); } return this.legacyProcess(data); }
legacyProcess(data) { // Original implementation }
modernProcess(data) { // New implementation } }
- Parallel Run (Verify Compatibility):
async function processWithVerification(data) { const [legacyResult, modernResult] = await Promise.all([ legacyProcess(data), modernProcess(data), ]);
// Compare results if (JSON.stringify(legacyResult) !== JSON.stringify(modernResult)) { console.error('Results mismatch!', { legacyResult, modernResult }); // Use legacy result for safety return legacyResult; }
// Both match, safe to use modern return modernResult; }
🔧 Common Modernization Tasks
- Callback Hell → Promises/Async
Before (Legacy):
getData(function (err, data) { if (err) return handleError(err); processData(data, function (err, result) { if (err) return handleError(err); saveResult(result, function (err) { if (err) return handleError(err); done(); }); }); });
After (Modern):
async function modernFlow() { try { const data = await getData(); const result = await processData(data); await saveResult(result); done(); } catch (err) { handleError(err); } }
- Global Variables → Module Pattern
Before (Legacy):
var globalConfig = {}; var globalState = {};
function doSomething() { globalState.counter++; return globalConfig.prefix + globalState.counter; }
After (Modern):
class AppModule { constructor(config) { this.config = config; this.state = { counter: 0 }; }
doSomething() { this.state.counter++; return this.config.prefix + this.state.counter; } }
export default AppModule;
- SQL Injection → Parameterized Queries
Before (Legacy - DANGEROUS):
const query = SELECT * FROM users WHERE id = ${userId};
db.query(query);
After (Modern - SAFE):
const query = 'SELECT * FROM users WHERE id = ?'; db.query(query, [userId]);
- Old Dependencies → Modern Alternatives
// Mapping old to new const DEPENDENCY_MAP = { request: 'axios', // HTTP client async: 'native-promises', // Flow control underscore: 'lodash', // Utilities bower: 'npm', // Package management grunt: 'webpack', // Build tool jquery: 'vanilla-js', // DOM manipulation };
// Safe migration approach: // 1. Install new alongside old // 2. Migrate one file at a time // 3. Run tests after each migration // 4. Remove old when complete
📊 Modernization Checklist
Security Fixes (Priority 1):
-
SQL injection vulnerabilities
-
XSS vulnerabilities
-
Hardcoded secrets removed
-
Outdated crypto replaced
-
Input validation added
-
HTTPS enforcement
Code Quality (Priority 2):
-
Global variables eliminated
-
Callbacks → Promises/Async
-
Var → Let/Const
-
== → ===
-
Error handling improved
-
Dead code removed
Performance (Priority 3):
-
Database queries optimized
-
Caching implemented
-
Lazy loading added
-
Bundle size reduced
-
Memory leaks fixed
Developer Experience (Priority 4):
-
Tests added (>60% coverage)
-
Documentation written
-
Linting configured
-
Types added (TypeScript/JSDoc)
-
CI/CD pipeline setup
🚨 Red Flags in Legacy Code
Watch for these danger signs:
// 🚨 Date handling new Date('2024-01-01'); // Timezone issues!
// 🚨 Type coercion if (value == true) // Use === instead
// 🚨 Eval usage eval(userInput); // Security nightmare!
// 🚨 Synchronous I/O const data = fs.readFileSync(); // Blocks everything!
// 🚨 Magic numbers if (status === 2) // What does 2 mean?
// 🚨 No error handling JSON.parse(data); // Will crash on bad input
💡 Migration Strategies by Language
JavaScript → Modern JS:
Use automated tools first
npx lebab --transform arrow,let,template legacy.js -o modern.js npx eslint --fix legacy.js
TypeScript migration
npx tsc --init mv legacy.js legacy.ts npx tsc --allowJs --checkJs
Python 2 → Python 3:
Automated conversion
2to3 -w legacy.py
Common fixes
print "Hello" → print("Hello") xrange() → range() unicode() → str()
jQuery → Vanilla JS:
// jQuery $('#element').hide(); $('.class').on('click', handler);
// Vanilla document.getElementById('element').style.display = 'none'; document.querySelectorAll('.class').forEach(el => { el.addEventListener('click', handler); });
📈 Success Metrics
Track modernization progress:
Legacy Modernization Scorecard
Week 1 | Week 4 | Week 8
----------|---------|-------- Coverage: 0% | 45% | 78% Security Issues: 12 | 6 | 1 Tech Debt (hours): 200 | 120 | 40 Build Time: 5min | 3min | 1min Bundle Size: 2.4MB | 1.2MB | 600KB Load Time: 4s | 2.5s | 1.2s
🔄 Rollback Strategy
Always have an escape plan:
// Feature flags for safe rollout if (featureFlags.useModernSystem) { return modernImplementation(); } else { return legacyImplementation(); }
// Database migrations with rollback // up.sql ALTER TABLE users ADD COLUMN email_verified BOOLEAN DEFAULT false;
// down.sql ALTER TABLE users DROP COLUMN email_verified;
Remember: Legacy code is not bad code - it's proven code that needs careful evolution! 🏛️→🏢