Module 7: Modernize Legacy Code

Module 7: Modernize Legacy Code

Module 7: Modernize Legacy Code

Capsule description

Phase 2 taught you refactoring techniques (M4), framework migration (M5), and context management (M6). This module applies everything to a concrete and extremely common case: legacy code that needs modernization. Not a framework migration — modernization: updating deprecated syntax, introducing type hints, replacing obsolete patterns, removing dead code.

Almost every team has code that "works but is obsolete." It was written with the best tools and knowledge available at the time. The dependencies got deprecated, the patterns evolved, the language added new features. Modernizing isn't criticizing — it's updating to the present.

Claude Code is extraordinarily effective here: it can detect code smells, deprecated patterns, and dead code in minutes — what manually takes hours of file-by-file review.

By the end of the 5 capsules, you'll be able to identify tech debt systematically, prioritize modernization with impact/risk criteria, modernize syntax and patterns without breaking behavior, and execute incremental modernization with regression tests at each step.


Module Context

Where are we?

Guide #8, Phase 3: Legacy and Project, Module 7 of 8.

Phase 3: Legacy and Project (Modules 7-8)
├── Module 7: Modernize Legacy Code ← YOU ARE HERE
│   → Tech debt detection, syntax modernization, dead code
└── Module 8: Capstone Project
    → Complete migration of a real legacy project

What you already know

From Phase 1 you master onboarding (M1), exploration (M2) and architecture analysis (M3). From Phase 2 you master multi-file refactoring (M4), migration (M5) and context management (M6). This module applies all of that to a specific case: code that evolved organically and accumulated tech debt.

Where are we headed?

Module 8 integrates the 7 previous modules into a complete migration project. The modernization techniques you learn here are executed in that project on a real legacy codebase.


Professional Objective

By the end you'll be able to:

  • ✅ Identify tech debt systematically with Claude Code
  • ✅ Prioritize modernization with the impact/risk matrix
  • ✅ Modernize Python syntax (f-strings, type hints, dataclasses, pathlib)
  • ✅ Modernize patterns (context managers, enums, async/await)
  • ✅ Remove dead code with confidence
  • ✅ Execute incremental modernization with tests at each step
  • ✅ Tell modernization (preserves behavior) apart from rewrite (changes behavior)

A Real Case: Modernizing Without Breaking

To anchor the module, consider a typical case:

Task: Modernize a payments/processor.py module (~600 lines) written in Python 3.6, without type hints, with os.path everywhere, %s string formatting, try/except catching a generic Exception, and 3 functions that seem to be unused.

Approach A: "Big bang modernization"

Day 1, 09:00 → Opens the file, decides "I'm going to clean it all up".

Day 1, 11:00 → Did: type hints + f-strings + pathlib + dataclasses
              + removed "unused" functions + changed exceptions.
              
Day 1, 11:30 → Tests don't exist. Runs the app: seems to work.

Day 1, 14:00 → Push to staging.

Day 1, 16:00 → INCIDENT: one of the "unused" functions was in fact called
              dynamically from a reports module.
              Revert everything. Day lost.

POSTMORTEM:
- Without tests, you don't know what broke or where
- Too many changes together: the diff is illegible
- The "dead" function wasn't dead — it just wasn't visible to grep

Approach B: Incremental modernization (this module)

Day 1, 09:00 → DETECT (Capsule 02)
              "List all the tech debt items in payments/processor.py.
               Categorize by type and severity."
              → Claude Code lists 23 items in 5 minutes.

Day 1, 09:15 → PRIORITIZE (Capsule 02)
              Apply the impact/risk matrix. Decide the order:
              1. Type hints (high value, low risk)
              2. F-strings (medium value, low risk)
              3. pathlib (medium value, low risk)
              4. Specific exceptions (high value, medium risk)
              5. Remove dead code (high value, HIGH RISK — last)

Day 1, 09:30 → SAFETY NET (M4 capsule 05)
              Regression tests that capture the current behavior.
              Verify that they pass against the untouched code.

Day 1, 10:00 → COMMIT 1: type hints
              Only add type hints. Tests pass. Commit.

Day 1, 11:00 → COMMIT 2: f-strings
              Only change % and .format() to f-strings. Tests pass. Commit.

Day 1, 11:30 → COMMIT 3: pathlib
              Only replace os.path with pathlib. Tests pass. Commit.

Day 1, 14:00 → COMMIT 4: specific exceptions
              Replace except Exception with specific exceptions.
              Tests pass. Commit.

Day 1, 15:30 → COMMIT 5: dead code
              BEFORE: use Explore (M2) to verify the functions
              really aren't called (including dynamic dispatch).
              Finds: 2 are dead, 1 IS called dynamically.
              Remove only the 2 confirmed ones. Tests pass. Commit.

Day 1, 16:00 → 5 clean commits. Tests green in each one. Code review
              easy because each commit does ONE thing.
              Zero incidents in staging.

Same work. Same final result. Radically different risk.

The key difference: one type of change per commit, tests green at each step. If something fails, you know exactly what broke it. If you need to revert, revert one, not all.


Module Progression

Module Map

CapsuleTopicWhat you'll learn
02Identifying Tech Debt with Claude CodeSystematic detection of code smells, deprecated patterns, dead code
03Modernizing Deprecated Syntax and PatternsFrom old Python to modern Python: f-strings, type hints, dataclasses, pathlib
04Incremental vs Big Bang RefactoringWhy incremental wins and how to execute it
05Project: Modernizing a Legacy ModuleModernize a module with 5+ tech debt items

Learning flow

You start by learning to detect tech debt systematically (capsule 02) — without this, modernizing is subjective. Then you learn to modernize specific syntax and patterns (capsule 03) with concrete before/after examples. Capsule 04 gives you the methodology (incremental vs big bang) that applies to any modernization. And the project integrates everything into a real module with 5+ tech debt items.


Key Concepts You'll See

A quick preview of the module's central concepts so you arrive with the vocabulary:

Tech Debt

The future cost of maintaining something that was done sub-optimally in the past. It's not the same as "bad code": tech debt is information about the cost, not a judgment. The impact/risk matrix (capsule 04) helps you decide what to pay off and what to leave.

Code Smells

Observable symptoms of tech debt: long functions, inconsistent naming, duplication, parameters with Any types. Capsule 02 gives you the catalog and how Claude Code detects them systematically.

Deprecated Patterns

Patterns the language or ecosystem has already moved past. Examples in Python:

  • %s formatting → f-strings (3.6+)
  • os.path → pathlib (3.4+)
  • Dictionaries without guaranteed order → ordered dicts by default (3.7+)
  • Optional type hints → recommended with mypy/pyright

Dead Code

Code that doesn't run. Careful: "not found by grep" ≠ "dead". Python has dynamic dispatch (callbacks, getattr, decorators with strings). Capsule 02 gives you the dynamic verification protocol.

Modernization vs Migration vs Rewrite

Three distinct levels of change:

ActionPreserves behaviorChanges frameworkChanges features
Modernization (this module)✅ Yes❌ No❌ No
Migration (M5)✅ Yes✅ Yes❌ No
Rewrite (not in this guide)❌ NoPossible✅ Yes

Mixing the three is the #1 cause of failed migration projects.

Impact/Risk Matrix

A prioritization framework: each tech debt item is evaluated on two axes:

  • Impact — how much value resolving it generates (maintainability, performance, security)
  • Risk — how likely it is to break something when changing it

Execution order: high impact + low risk first, high risk last with additional verification. Capsule 04 develops the framework.

Incremental Refactoring

One type of change per commit. Tests green at each step. If something fails, you know exactly what broke it. The opposite: big bang — everything together, high risk. Capsule 04 shows how to execute incremental with Claude Code.


The Core Principle

Incremental always wins. One change at a time, with tests. It's apparently slower but infinitely safer. Modernize f-strings in one commit. Type hints in another. Dead code removal in another. Never all together.

Three practical consequences:

  1. One type of change per commit. If the commit says "modernization", it's wrong. It should say "type hints in payments/processor.py".
  2. Tests green at each step. You don't move on to the next change if the previous one's tests don't pass.
  3. Dynamic verification before removing. "Unused" according to grep ≠ "dead". Use Explore to verify dynamic dispatch.

Connection with the Project

In the Module Project (capsule 05), you modernize a Python module with at least 5 tech debt items. Each change has a regression test. The deliverable is the modernized module + a change log that documents each commit.

In Module 8 (Capstone Project), the modernization techniques are one of the steps of the full migration cycle. The Module 8 assessment identifies the tech debt; this module gives you the methodology to resolve it incrementally and safely.


Traps to Avoid While Taking This Module

Five predictable misunderstandings. Anticipate them before you start.

1. "If Claude Code lists tech debt, I fix it all at once"

No. The tech debt list is input for prioritizing, not a to-do to execute all in a single PR. Capsule 04 gives you the impact/risk matrix: high impact + low risk goes first, high impact + high risk (like dead code) goes last with additional verification.

2. "Modernizing is the same as rewrite"

No. Modernizing preserves behavior while updating syntax/patterns. Rewrite starts from scratch and can change behavior. If you find yourself "improving the business logic while modernizing", you stopped modernizing and started doing something else — separate the changes.

3. "Dead code = code that grep doesn't find"

No. Python has dynamic dispatch (getattr, __getattr__, decorators with strings, plugins). A function may be called dynamically without grep finding it. Before removing, use Explore (M2) and check production logs. Capsule 02 develops this point.

4. "Tech debt is debt you have to pay off immediately"

No. Tech debt is information about the future cost of maintaining something. Sometimes the cost of paying it off is greater than the cost of maintaining it. The impact/risk matrix (capsule 04) helps you decide what to pay off and what to leave — modernizing everything isn't always the right answer.

5. "Type hints are cosmetic"

Underestimating type hints is an expensive mistake. Type hints catch bugs before runtime, improve the readability of the code, and enable more aggressive refactorings because the linter warns you of breakage. Capsule 03 treats them as a high priority, not as an aesthetic detail.


Diagnosis: What's Your Starting Point?

Five questions to calibrate before you start.

Question 1: The last time you "cleaned up" old code, how many types of change did you make in a single commit?

If you said "several": trap #1 applies to you. Capsule 04 teaches you why incremental wins.

If you said "just one": you're on the right track. Capsule 04 formalizes that habit as a reusable methodology.

Question 2: Do you know how to tell tech debt apart from bugs?

If yes: capsule 02 formalizes tech debt detection and helps you prioritize.

If no: simple — bug = doesn't work as it should; tech debt = works but the cost of maintaining it is high. Capsule 02 develops the taxonomy.

Question 3: Have you removed "unused" code and later discovered it was used?

If yes: trap #3 applies to you. Capsule 02 gives you the verification protocol.

If no: it's going to happen to you sooner or later. Capsule 02 prevents it.

Question 4: Do you have type hints in your new code? And do you add them to existing code when you touch it?

If "yes" to both: you're on the right track. Capsule 03 gives you the systematic pattern.

If "no" to either: capsule 03 shows the ROI: type hints add 10% of time and eliminate ~30% of the typical runtime bugs.

Question 5: How do you decide what tech debt is worth paying off?

If you have a process: capsule 04 formalizes it as a reusable matrix.

If you say "intuition": capsule 04 gives you the axes (impact, risk, frequency of change) to make reproducible decisions.

If you hesitated on 3 or more: this module gives you the missing methodology to modernize with confidence. If you answered them all with clear criteria, use it focused on capsule 02 (systematic detection) which scales a lot with Claude Code.


Evidence of Success

Before moving on to Module 8 (Capstone Project), you should be able to:

  • ✅ Scan a module with Claude Code and list 5+ tech debt items in under 10 minutes
  • ✅ Prioritize the items with the impact/risk matrix, not at random
  • ✅ Modernize syntax (f-strings, type hints, pathlib, dataclasses) without breaking tests
  • ✅ Dynamically verify that the code to be removed really isn't used
  • ✅ Produce one commit per type of modernization (not "general cleanup")
  • ✅ Tell modernization (preserves behavior) apart from rewrite (changes behavior)
  • ✅ Apply the tests-green-at-each-step cycle out of habit

If any one isn't met at the end, go back to the corresponding capsule. Module 8 executes a complete migration that includes modernization — without these techniques, the capstone project becomes a risky "big bang".


Summary

  • Modernizing legacy is the most in-demand work in software teams
  • Claude Code detects tech debt in minutes that manually takes hours
  • Incremental > big bang — one type of change per commit
  • Tech debt isn't negligence — it's the natural evolution of code
  • Regression tests at each step (the Module 4 rule)
  • Dead code requires dynamic verification, not just grep
  • The difference between the "lost day" of Approach A and the "5 clean commits" of Approach B is exactly this module's methodology

Next capsule: 02 — Identifying Tech Debt with Claude Code — systematic detection of code smells, deprecated patterns, and dead code. It's the basis for the prioritization in capsule 04 and for the project.


Additional Resources

  1. Working Effectively with Legacy Code - Michael Feathers - The bible of legacy code
  2. Python What's New - New features of each Python version
  3. Refactoring Guru - Code Smells - A catalog of code smells
  4. pyupgrade - A tool that modernizes Python syntax automatically
  5. vulture - A dead code detector for Python
  6. mypy - A type checker that complements adding type hints
  7. PEP 8 - Python's official style guide (a reference for "modern Python")

Module 7, Capsule 01 — Refactoring & Legacy Code with Claude Code Guide