Module 7: Modernize Legacy Code
Identifying Tech Debt with Claude Code
Identifying Tech Debt with Claude Code
Capsule description
Before modernizing, you need an inventory: what tech debt does this code have? Claude Code can scan a complete module and list code smells, deprecated patterns, dead code, unused imports, and missing type hints — in minutes. Manually, this audit takes hours.
In this capsule you're going to learn to run systematic tech debt scans with Claude Code, categorize findings by type, and prioritize them with the impact/risk matrix.
The 6 Types of Tech Debt
1. Deprecated Syntax
# Old Python:
name = "Hello, %s" % user_name # %-formatting
items = dict([(k, v) for k, v in data]) # verbose dict comprehension
if type(x) == int: # type() comparison
file = open("data.txt") # no context manager
# Modern Python:
name = f"Hello, {user_name}" # f-strings
items = {k: v for k, v in data} # dict comprehension
if isinstance(x, int): # isinstance()
with open("data.txt") as file: # context manager
2. Missing Type Hints
# Without type hints (ambiguous):
def calculate_total(items, discount, tax_rate):
subtotal = sum(i["price"] * i["qty"] for i in items)
return subtotal * (1 - discount) * (1 + tax_rate)
# With type hints (clear):
def calculate_total(
items: list[dict[str, float]],
discount: float,
tax_rate: float
) -> float:
subtotal = sum(i["price"] * i["qty"] for i in items)
return subtotal * (1 - discount) * (1 + tax_rate)
3. Dead Code
import os # never used
import json # never used
from datetime import timedelta # never used
def old_calculate_tax(amount): # never called
"""Deprecated: use calculate_tax_v2"""
return amount * 0.16
LEGACY_URL = "https://old-api.example.com" # never referenced
4. Deprecated Patterns
# Old pattern: manual error handling
try:
file = open("config.json")
data = json.load(file)
file.close()
except: # bare except (catches everything)
pass # silences errors
# Modern pattern: context manager + specific exception
try:
with open("config.json") as file:
data = json.load(file)
except FileNotFoundError:
data = {}
except json.JSONDecodeError as e:
logger.error(f"Invalid config: {e}")
data = {}
5. Code Duplication
# In user_service.py:
tax = subtotal * 0.16
if region == "EU":
tax = subtotal * 0.21
# In order_service.py (identical):
tax = subtotal * 0.16
if region == "EU":
tax = subtotal * 0.21
# In invoice_service.py (identical):
tax = subtotal * 0.16
if region == "EU":
tax = subtotal * 0.21
6. Deprecated Dependencies
# requirements.txt with deprecated deps:
flask==1.1.4 # EOL, should be 3.x
requests==2.25.0 # old, use httpx or update
python-jose==3.3.0 # unmaintained, use PyJWT
Systematic Scan with Claude Code
The tech debt scan prompt
> "Analyze [file or module] and generate a complete
tech debt inventory. For each item, report:
1. Type (syntax, type hints, dead code, pattern, duplication, dependency)
2. Location (file:line)
3. Severity (high/medium/low)
4. Description (what it is and why it's tech debt)
5. Suggested fix (how to modernize it)
Organize by type and severity."
Expected output
# Tech Debt Inventory: src/services/order_service.py
## Deprecated Syntax (3 items)
| # | Line | Severity | Description | Fix |
|---|-------|-----------|-------------|-----|
| 1 | 23 | Low | %-formatting | f-string |
| 2 | 45 | Low | dict() with a list comprehension | dict comprehension |
| 3 | 67 | Medium | open() without a context manager | with statement |
## Missing Type Hints (5 items)
| # | Line | Severity | Description | Fix |
|---|-------|-----------|-------------|-----|
| 1 | 12 | Medium | create_order() without type hints | Add hints |
| 2 | 34 | Medium | calculate_total() without hints | Add hints |
| ... | ... | ... | ... | ... |
## Dead Code (2 items)
| # | Line | Severity | Description | Fix |
|---|-------|-----------|-------------|-----|
| 1 | 5 | Low | import os (unused) | Remove |
| 2 | 89 | Medium | old_validate() never called | Remove |
## Deprecated Patterns (2 items)
| # | Line | Severity | Description | Fix |
|---|-------|-----------|-------------|-----|
| 1 | 67 | Medium | bare except | Specific exceptions |
| 2 | 78 | High | SQL string concatenation | Parameterized query |
## Total: 12 items (2 high, 5 medium, 5 low)
Prioritization with the Impact/Risk Matrix
The matrix
| High impact | Low impact | |
|---|---|---|
| Low risk | ✅ FIRST | 🔄 When convenient |
| High risk | ⚠️ Plan | ❌ Probably not worth it |
Applying the matrix
✅ FIRST (high impact, low risk):
- Dead code removal
- Import cleanup
- bare except → specific exceptions
🔄 WHEN CONVENIENT (low impact, low risk):
- %-formatting → f-strings
- verbose dict() → dict comprehension
⚠️ PLAN (high impact, high risk):
- SQL concatenation → parameterized (security)
- Missing type hints on public functions
❌ PROBABLY NOT (low impact, high risk):
- Rewriting functions that work "for aesthetics"
Connection with the Project
In the Module Project (capsule 05), the first step is a complete tech debt scan. The prioritization determines the modernization order.
Troubleshooting
Problem 1: Claude Code reports too many items
Solution: Filter by severity. Focus on high and medium. The low ones are "nice to have."
Problem 2: I don't know if something is really tech debt
Solution: Ask: "Would this code cause problems in a 2026 code review?" If yes, it's tech debt. If it works well and is readable, it may be fine.
Problem 3: The team doesn't use type hints
Solution: Don't introduce type hints in a module if the rest of the project doesn't use them. Modernization should be aligned with the team.
Exercises
Exercise 1: Classify tech debt (Easy)
Classify each item by type and severity:
import systhat's never usedexcept:without specifying the exception- A 300-line function with 5 responsibilities
"Hello %s" % nameinstead of an f-string- SQL query with string concatenation:
f"SELECT * FROM users WHERE id = {user_id}"
See solution
- Dead code, Low — doesn't affect functionality
- Deprecated pattern, Medium — can hide errors
- Code smell, High — hard to maintain and test
- Deprecated syntax, Low — functional, only cosmetic
- Security vulnerability, VERY HIGH — SQL injection possible
Exercise 2: Write a scan prompt (Medium)
Write the prompt for Claude Code to scan all of src/services/ looking only for deprecated patterns and dead code.
See solution
> "Scan all the files in src/services/ looking for:
1. Dead code: functions never called, unused imports,
unreferenced variables
2. Deprecated patterns: bare except, open() without with,
manual file.close(), %-formatting, type() comparison
For each finding, report: file, line, type,
and suggested fix. Ignore type hints and code style."
Common Errors in Tech Debt Scans
Error 1: Confusing "I don't like it" with "tech debt"
Symptom: Your list includes "this name is ugly" or "I'd write it differently". The team rejects the PR.
Why it happens: Aesthetic preferences sneak in as "tech debt". But tech debt is objective cost: future bugs, maintenance difficulty, security debt — not personal style.
How to fix: For each item, ask yourself: "Would this code cause problems in a professional code review, or does it just bother me?". If it just bothers you, it's not debt — it's preference.
Error 2: Uniform severity ("everything is important")
Symptom: 30 items, 30 marked as medium-high severity. Prioritization is impossible.
Why it happens: Each item feels important at the moment. But prioritizing requires differentiating — and that means marking most as low.
How to fix: A typical healthy distribution: 10% high, 30% medium, 60% low. If everything is high, nothing is high. SQL injection is high. F-string vs %-formatting is low, not medium.
Error 3: Marking "dead code" without dynamic verification
Symptom: You removed 3 "dead" functions. In production, one is called from a cron job. Incident.
Why it happens: Blind trust in grep. Python has dynamic callbacks, decorators with strings, getattr, plugins, entry points in setup.py.
How to fix: Before marking as dead, verify:
grep -r "function_name"in the WHOLE repo (not just the module)- Search in strings:
grep -r '"function_name"' - Look in config files (YAML, TOML, JSON)
- If logs are accessible, verify it doesn't appear in the last 30 days
- If you still doubt, mark it as "candidate for removal" — not removed yet
Error 4: Looking for tech debt without understanding the project context
Symptom: You marked "doesn't use async" as tech debt in a sync-first project. The team laughs.
Why it happens: You applied a "modern" pattern without checking whether it fits the project. Not every project needs to be async, type-hinted, or dataclass-based.
How to fix: Before the scan, read CLAUDE.md or the README. What patterns does the project use? Modernization should align with the project, not impose a foreign style.
Error 5: Not including security in the scan
Symptom: Your list has 30 cosmetic items but doesn't detect SQL injection, hardcoded secrets, or missing validation.
Why it happens: The instinct is to look for "old code". But the worst tech debt is security debt — and it looks more subtle than an f-string.
How to fix: Explicitly include in your prompt: "Also identify: SQL string concatenation, hardcoded secrets/passwords/keys, missing validation on external inputs, error handling that leaks sensitive info". Default severity: high or critical.
Summary
- 6 types of tech debt: syntax, type hints, dead code, patterns, duplication, dependencies
- Claude Code scans in minutes what manually takes hours
- Prioritize with impact/risk: dead code first, cosmetic later
- Tech debt isn't negligence — it's natural evolution
- The scan is the input for the modernization (capsules 03-04)
- Tell preference apart from debt — not everything that bothers you is debt
- Dynamically verify dead code before marking it
- Include security explicitly in the scan
Next capsule: Modernizing Deprecated Syntax and Patterns — executing the fixes.
Additional Resources
- pylint - A linter that detects many code smells
- vulture - A dead code finder for Python
- pyupgrade - Automatic syntax modernization
- bandit - A security scanner for Python
- Refactoring Guru - Code Smells - A complete catalog
- SonarQube - A code quality analysis platform
Module 7, Capsule 02 — Refactoring & Legacy Code with Claude Code Guide