Module 7: Subagents for Debugging, Regenerate vs Edit
Decision Framework: Regenerate vs Edit
Decision Framework: Regenerate vs Edit
Capsule overview
Capsules 03 and 04 gave you the signals for each side of the spectrum: when to regenerate and when to edit. But reality is rarely black or white. The most interesting cases — and the most frequent — are in the gray zone where the answer isn't obvious.
This capsule integrates everything into a step-by-step decision framework: you assess the damage, estimate times, consider what's lost, and make an informed decision. It's not a magic formula — it's a process that structures your thinking so the decision is fast and justifiable.
And most importantly: 5 realistic scenarios where the answer isn't obvious. Real code with real problems where you need to apply the framework, justify your decision, and defend your choice. Because the difference between a competent developer and a senior developer isn't having the framework — it's knowing how to apply it under pressure with nuance.
The Decision Framework: 5 Steps
Overview
┌─────────────────┐
│ 1. ASSESS │
│ DAMAGE │ → How much of the code needs to change?
└───────┬─────────┘
│
┌───────▼─────────┐
│ 2. ESTIMATE │
│ EDITING │ → How long does it take me to edit?
└───────┬─────────┘
│
┌───────▼─────────┐
│ 3. ESTIMATE │
│ REGENERATION │ → How long does it take me to regenerate?
└───────┬─────────┘
│
┌───────▼─────────┐
│ 4. CONSIDER │
│ LOSSES │ → What do I lose if I regenerate?
└───────┬─────────┘
│
┌───────▼─────────┐
│ 5. DECIDE │
│ │ → Edit | Regenerate function | Regenerate file
└─────────────────┘
Step 1: Assess the damage
Before deciding how to fix, you need to understand how deep the problem is.
Questions to assess:
A. Is the fundamental approach correct?
YES → Probably edit
NO → Probably regenerate
B. How many lines need to change?
1-5 → Edit
5-20 → It depends (go to step 2)
20+ → Probably regenerate
C. Are the changes independent or interconnected?
Independent → Edit (you change them one by one with no risk)
Interconnected → Regenerating has less risk of inconsistency
D. Is the problem a design or an implementation problem?
Design → Regenerate (patching a design creates a Frankenstein)
Implementation → Edit (the design is already correct)
Quick example:
Code: an endpoint that searches for tasks in the DB
Problem: uses a Python loop instead of SQL WHERE
A. Correct approach? NO (should use SQL, not a Python loop)
B. Lines that change? ~15 of 30
C. Interconnected changes? YES (changing the query affects the processing)
D. Design or implementation? DESIGN (the approach is incorrect)
Assessment: regenerate the function
Step 2: Estimate editing time
If step 1 suggests editing, estimate how long it will take:
Approximate formula:
Time = (# of edits × average complexity) + verification
Where:
Simple edit (change an operator, add a line): 1-2 min
Medium edit (add a block, change logic): 3-5 min
Complex edit (restructure a function): 5-10 min
Verification: 3-5 min (always)
Example:
3 simple edits + 1 medium + verification
= (3 × 1.5) + (1 × 4) + 4
= 4.5 + 4 + 4
= 12.5 min
Step 3: Estimate regeneration time
Approximate formula:
Time = prompt + generation + review + customizations + verification
Where:
Detailed prompt: 3-5 min
Generation: 1-2 min
Review of the new code: 3-10 min (depends on complexity)
Re-apply customizations: 0-10 min (depends on how many)
Verification: 3-5 min
Example (simple function without customizations):
= 3 + 1 + 3 + 0 + 3 = 10 min
Example (complex endpoint with logging and audit):
= 5 + 2 + 8 + 8 + 5 = 28 min
Step 4: Consider losses
This step is where many developers go wrong — they forget what they lose by regenerating.
Losses checklist:
☐ Manual customizations I made after the generation
→ Logging, messages in Spanish, specific format
→ How many lines? How long did they take?
☐ Context accumulated in the Claude Code session
→ Does Claude Code understand my style, my preferences?
→ Will losing that context make the new code inconsistent?
☐ Integrations with the rest of the codebase
→ Does the current code integrate with other components?
→ Will the regenerated code maintain those integrations?
☐ Tests that already pass
→ Are there tests that validate the current code?
→ Will the regenerated code pass those tests without changes?
☐ Knowledge I already have of the code
→ "I already know exactly what this function does"
→ "With new code, I'd have to re-read and re-understand"
Step 5: Decide
With the information from steps 1-4, the decision is usually clear:
Decision matrix:
If assess_damage says "incorrect approach" → Regenerate
(regardless of the other factors)
If edit_time < regeneration_time AND losses > 0 → Edit
(faster and you preserve customizations)
If edit_time > regeneration_time AND losses = 0 → Regenerate
(faster and you lose nothing)
If edit_time ≈ regeneration_time → Edit
(in a tie, editing preserves context)
If the edits are interconnected and complex → Regenerate function
(reduce the risk of inconsistency)
The tie-breaker: When the times are similar, editing wins because it preserves context. Only regenerate when there's a clear advantage in time or quality.
The Complete Spectrum of Options
It's not just "edit" or "regenerate." There's a full spectrum:
EDIT REGENERATE
│ │
├── Edit 1 line │
│ "Change > to <" │
│ │
├── Edit several lines │
│ "Add null check, add ownership check" │
│ │
├── Edit a block │
│ "Rewrite the entire try/except" │
│ │
├── Regenerate a function │
│ "Regenerate only calculate_stats(), keep the rest" │
│ │
├── Regenerate several functions │
│ "Regenerate the 3 CRUD functions, keep imports and │
│ configuration" │
│ │
└── Regenerate the entire file ──────────────────────────────┘
"The whole file needs a different approach"
The decision isn't binary. And most of the time, the answer is in the middle: regenerate part, edit the rest.
Common combinations
Scenario 1: "Regenerate function + edit imports"
→ The function has the wrong approach
→ But the file's imports and configuration are fine
→ You regenerate the function, edit the imports if they changed
Scenario 2: "Edit endpoint + regenerate schema"
→ The endpoint is fine but the Pydantic schema has no validations
→ You regenerate the schema with Field constraints and validators
→ You edit the endpoint only if the schema changed its name
Scenario 3: "Regenerate logic + preserve integration"
→ The calculation logic is incorrect
→ But it has logging, an audit trail, and integrated notifications
→ You regenerate the core logic, preserve the integrations
The 5 Scenarios: Practice the Framework
Each scenario presents real code with real problems. Apply the 5-step framework, make your decision, and then compare it with the detailed analysis.
Scenario 1: The Reports Service
Claude Code generated this reports service that has several problems:
from datetime import datetime, timedelta
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from sqlalchemy import func, and_
from pydantic import BaseModel
from app.database import get_db
from app.models import Task, User
from app.dependencies.auth import get_current_user
import logging
logger = logging.getLogger(__name__)
app = FastAPI()
class ReportResponse(BaseModel):
period: str
total_tasks: int
completed_tasks: int
completion_rate: float
avg_completion_days: float
most_active_user: str | None
tasks_by_priority: dict
model_config = {"from_attributes": True}
@app.get("/api/reports/weekly", response_model=ReportResponse)
async def weekly_report(
current_user=Depends(get_current_user),
db: Session = Depends(get_db),
):
logger.info(f"Generando reporte semanal para user {current_user.id}")
one_week_ago = datetime.utcnow() - timedelta(days=7)
all_tasks = (
db.query(Task)
.filter(Task.created_at >= one_week_ago)
.all()
)
total = len(all_tasks)
completed = [t for t in all_tasks if t.status == "completed"]
completion_rate = len(completed) / total if total > 0 else 0
avg_days = 0
for task in completed:
if task.completed_at:
delta = (task.completed_at - task.created_at).days
avg_days += delta
if len(completed) > 0:
avg_days = avg_days / len(completed)
# Find the most active user
user_counts = {}
for task in all_tasks:
uid = task.owner_id
user_counts[uid] = user_counts.get(uid, 0) + 1
most_active_id = max(user_counts, key=user_counts.get) if user_counts else None
most_active_user = None
if most_active_id:
user = db.query(User).filter(User.id == most_active_id).first()
most_active_user = user.name if user else None
# Tasks by priority
priority_counts = {}
for task in all_tasks:
p = task.priority
priority_counts[p] = priority_counts.get(p, 0) + 1
logger.info(f"Reporte generado: {total} tareas, {completion_rate:.1%} completion")
return ReportResponse(
period="weekly",
total_tasks=total,
completed_tasks=len(completed),
completion_rate=round(completion_rate, 4),
avg_completion_days=round(avg_days, 2),
most_active_user=most_active_user,
tasks_by_priority=priority_counts,
)
Identified problems:
- Loads ALL the tasks into memory to do calculations in Python
- Doesn't filter by the current user (shows tasks from all users)
- The task count by priority and the most active user are calculated in Python
avg_completion_daysuses.days(truncates to integers) instead of.total_seconds() / 86400
Apply the framework and decide.
See solution
Step 1: Assess the damage
A. Correct approach? PARTIALLY
- The "search tasks → calculate stats" approach is correct
- BUT the implementation loads everything into memory (incorrect)
- And it's missing the filter by user (security bug)
B. Lines that change? ~30 of 60 (50%)
C. Interconnected? YES
- Changing the queries affects all the calculations
D. Design or implementation? IMPLEMENTATION
- The design (endpoint → calculate stats → response) is correct
- The implementation (Python loops vs SQL) is incorrect
Step 2: Estimate editing
Necessary edits:
1. Add owner_id filter: 2 min (simple)
2. Change total/completed to SQL count: 5 min (medium)
3. Change avg_days to SQL avg: 5 min (medium)
4. Change most_active to SQL subquery: 8 min (complex)
5. Change priority_counts to SQL group by: 5 min (medium)
6. Fix .days → .total_seconds()/86400: 1 min (simple)
7. Verification: 5 min
Total estimated: ~31 min
Step 3: Estimate regeneration
Detailed prompt (include what to preserve): 5 min
Generation: 2 min
Review: 5 min
Re-apply logging (2 lines): 2 min
Verification: 5 min
Total estimated: ~19 min
Step 4: Consider losses
Customizations:
- 2 lines of logging (easy to re-add)
- Log messages in Spanish (include in the prompt)
- ReportResponse schema (include in the prompt)
Losses: Minimal (simple logging, easy to specify in the prompt)
Step 5: Decide
→ REGENERATE the weekly_report function
Justification:
- 50% of the code needs to change
- The edits are interconnected (changing queries affects everything)
- The regeneration time (~19 min) is significantly less than editing (~31 min)
- The losses are minimal (2 lines of logging)
- Regenerating produces more consistent code than editing 5 individual queries
BUT: keep the ReportResponse schema and the imports. Only regenerate the function.
Regeneration prompt:
Regenerate ONLY the weekly_report function. Keep the
ReportResponse schema and the imports exactly as they are.
Problems with the current code that you must fix:
1. Loads all tasks into memory — use SQL aggregations
2. Missing filter by current_user.id — add it
3. avg_completion_days uses .days (truncates) — use total_seconds/86400
4. The most active user and priority count calculations
should be SQL, not Python
Preserve:
- The logging with logger.info (same format and messages in Spanish)
- The same Depends(get_current_user, get_db)
- The response_model=ReportResponse
- The completion_rate calculation with round(_, 4)
Scenario 2: The Rate Limiting Middleware
import time
from collections import defaultdict
from fastapi import FastAPI, Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
app = FastAPI()
class RateLimitMiddleware(BaseHTTPMiddleware):
def __init__(self, app, requests_per_minute: int = 60):
super().__init__(app)
self.requests_per_minute = requests_per_minute
self.requests: dict[str, list[float]] = defaultdict(list)
async def dispatch(self, request: Request, call_next):
client_ip = request.client.host
now = time.time()
self.requests[client_ip] = [
ts for ts in self.requests[client_ip]
if now - ts < 60
]
if len(self.requests[client_ip]) >= self.requests_per_minute:
return JSONResponse(
status_code=429,
content={"detail": "Rate limit exceeded. Try again later."},
headers={
"Retry-After": "60",
"X-RateLimit-Limit": str(self.requests_per_minute),
"X-RateLimit-Remaining": "0",
},
)
self.requests[client_ip].append(now)
response = await call_next(request)
remaining = self.requests_per_minute - len(self.requests[client_ip])
response.headers["X-RateLimit-Limit"] = str(self.requests_per_minute)
response.headers["X-RateLimit-Remaining"] = str(max(0, remaining))
return response
app.add_middleware(RateLimitMiddleware, requests_per_minute=100)
Problems:
- Stores requests in memory (lost on restart, doesn't work with multiple workers)
request.client.hostcan beNoneif there's a proxy (needsX-Forwarded-For)- The cleanup of old timestamps happens on every request (O(n) per request)
- The
self.requestsdict grows without limit (doesn't clean up inactive IPs)
Should the whole middleware be moved to Redis? Or can you patch the problems?
See solution
Step 1: Assess
A. Correct approach? IT DEPENDS ON THE CONTEXT
- For development/single worker: the in-memory approach is acceptable
- For production/multi worker: it needs Redis
B. Lines that change?
- If it stays in memory: ~10 of 40 (25%) → edit
- If it migrates to Redis: ~35 of 40 (87%) → regenerate
C. Interconnected? If it migrates to Redis, yes
D. Design or implementation?
- If it stays in memory: implementation (patch the problems)
- If it migrates to Redis: design (architectural change)
The key question: Is this middleware going to production with multiple workers?
If NOT (development or single worker):
→ EDIT problems 2, 3, and 4. Keep it in-memory.
async def dispatch(self, request: Request, call_next):
client_ip = request.headers.get("X-Forwarded-For", "").split(",")[0].strip()
if not client_ip:
client_ip = request.client.host if request.client else "unknown"
now = time.time()
window_start = now - 60
self.requests[client_ip] = [
ts for ts in self.requests[client_ip] if ts > window_start
]
# Clean up inactive IPs periodically
if len(self.requests) > 10000:
inactive = [
ip for ip, timestamps in self.requests.items()
if not timestamps or timestamps[-1] < window_start
]
for ip in inactive:
del self.requests[ip]
# ... rest the same
3 targeted edits (~12 min). The in-memory approach is valid for the context.
If YES (production with multiple workers):
→ REGENERATE the entire middleware with Redis.
The in-memory approach is fundamentally incorrect for this context. You can't "edit" an in-memory dict to make it shared between workers — you need to change the architecture to Redis.
The lesson: The same question ("do I edit or regenerate?") has different answers depending on the context. The framework makes you consider the context before deciding.
Scenario 3: The Permission System
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from app.database import get_db
from app.models import Task, User
from app.dependencies.auth import get_current_user
app = FastAPI()
@app.get("/tasks/{task_id}")
async def get_task(
task_id: int,
current_user=Depends(get_current_user),
db: Session = Depends(get_db),
):
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(404, "Task not found")
if task.owner_id != current_user.id:
raise HTTPException(403, "Not authorized")
return task
@app.patch("/tasks/{task_id}")
async def update_task(
task_id: int,
title: str | None = None,
current_user=Depends(get_current_user),
db: Session = Depends(get_db),
):
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(404, "Task not found")
if task.owner_id != current_user.id:
raise HTTPException(403, "Not authorized")
if title:
task.title = title
db.commit()
return task
@app.delete("/tasks/{task_id}")
async def delete_task(
task_id: int,
current_user=Depends(get_current_user),
db: Session = Depends(get_db),
):
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(404, "Task not found")
if task.owner_id != current_user.id:
raise HTTPException(403, "Not authorized")
db.delete(task)
db.commit()
return {"detail": "Deleted"}
Problems:
- The permission check is repeated in 3 endpoints (DRY violation)
update_taskdoesn't use a Pydantic model for the bodydelete_taskdoesn't let admins delete other users' tasks- No endpoint has a
response_model update_taskdoesn't useexclude_unsetfor partial updates
That's 5 problems. Do you edit or regenerate?
See solution
Step 1: Assess
A. Correct approach? YES
- CRUD endpoints with auth and ownership — correct
- The endpoint → query → verify → act structure is correct
B. Lines that change?
- Problem 1 (DRY): create a dependency, change 3 functions (~15 lines)
- Problem 2: create a model, change params (~8 lines)
- Problem 3: change 1 condition (~3 lines)
- Problem 4: add 3 response_models (~3 lines)
- Problem 5: change the update logic (~5 lines)
Total: ~34 lines of ~55 = 62%
C. Interconnected? PARTIALLY
- Problem 1 (DRY) affects all 3 endpoints
- The others are independent
D. Design or implementation? IMPLEMENTATION
- The CRUD design is correct
- The implementation has quality problems
Step 2: Estimate editing
1. Create a dependency (new function + change 3 endpoints): 10 min
2. Create a Pydantic model + change update: 5 min
3. Add admin check to delete: 2 min
4. Add response_model × 3: 2 min
5. Add exclude_unset: 3 min
Verification: 5 min
Total: ~27 min
Step 3: Estimate regeneration
Prompt (specify DRY, schemas, admin rule): 5 min
Generation: 2 min
Review: 5 min
Customizations: 0 min (there are none)
Verification: 5 min
Total: ~17 min
Step 4: Consider losses
Customizations: none visible
Integrations: none special
Context: low (simple code, no accumulated state)
Losses: minimal
Step 5: Decide
→ COMBINATION: Regenerate the file + create the new dependency
Justification:
- 62% of the code needs to change
- Regenerating (~17 min) is faster than editing (~27 min)
- There are no customizations to lose
- The changes are partially interconnected (the DRY fix affects everything)
The mixed approach is:
- Create the
get_task_or_404dependency first (new function, doesn't require regenerating) - Regenerate the 3 endpoints with an instruction to use the dependency
- Create the
TaskUpdatePydantic model (new, doesn't require regenerating)
This produces more consistent code than editing 5 problems one by one.
Scenario 4: The Export Endpoint
import csv
import io
from fastapi import FastAPI, Depends, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session, joinedload
from app.database import get_db
from app.models import Task, Category, Tag
from app.dependencies.auth import get_current_user
import logging
logger = logging.getLogger(__name__)
app = FastAPI()
@app.get("/api/tasks/export")
async def export_tasks(
format: str = Query("csv", regex="^(csv|json)$"),
status: str | None = Query(None),
current_user=Depends(get_current_user),
db: Session = Depends(get_db),
):
logger.info(
f"Exportando tareas en formato {format}",
extra={"user_id": current_user.id, "format": format},
)
query = (
db.query(Task)
.options(joinedload(Task.category), joinedload(Task.tags))
.filter(Task.owner_id == current_user.id)
)
if status:
query = query.filter(Task.status == status)
tasks = query.order_by(Task.created_at.desc()).all()
if format == "csv":
output = io.StringIO()
writer = csv.writer(output)
writer.writerow([
"ID", "Título", "Descripción", "Estado",
"Prioridad", "Categoría", "Tags", "Creado", "Actualizado",
])
for task in tasks:
writer.writerow([
task.id,
task.title,
task.description or "",
task.status,
task.priority,
task.category.name if task.category else "",
", ".join(t.name for t in task.tags) if task.tags else "",
task.created_at.isoformat(),
task.updated_at.isoformat() if task.updated_at else "",
])
output.seek(0)
logger.info(f"Exportación CSV completada: {len(tasks)} tareas")
return StreamingResponse(
iter([output.getvalue()]),
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=tasks.csv"},
)
else:
task_list = []
for task in tasks:
task_list.append({
"id": task.id,
"title": task.title,
"description": task.description,
"status": task.status,
"priority": task.priority,
"category": task.category.name if task.category else None,
"tags": [t.name for t in task.tags] if task.tags else [],
"created_at": task.created_at.isoformat(),
"updated_at": task.updated_at.isoformat() if task.updated_at else None,
})
logger.info(f"Exportación JSON completada: {len(tasks)} tareas")
return task_list
Problems:
StreamingResponse(iter([output.getvalue()]))— loads everything into memory, it's not real streaming. With 100,000 tasks, it consumes a lot of RAM.- The manual JSON serialization duplicates the CSV logic (it should be a schema or a shared function).
- There's no export limit — a user with 1M tasks crashes the server.
Do you edit or regenerate?
See solution
Step 1: Assess
A. Correct approach? YES (mostly)
- Export endpoint with filters and auth: correct
- Eager loading of relationships: correct
- CSV and JSON support: correct
- The problem is the streaming implementation and the limits
B. Lines that change?
- Problem 1 (real streaming): ~15 lines of the CSV section
- Problem 2 (DRY): refactor the serialization (~10 lines)
- Problem 3 (limit): add ~5 lines
Total: ~30 of ~70 = 43%
C. Interconnected? PARTIALLY
- The streaming and the serialization are independent
- The limit is independent of everything
D. Design or implementation? IMPLEMENTATION
- The endpoint design is correct
- The streaming implementation needs improvement
Step 2: Estimate editing: ~22 min Step 3: Estimate regeneration: ~25 min (high because of the customizations)
Step 4: Consider losses
SIGNIFICANT customizations:
- ✅ Structured logging with extra fields (4 lines)
- ✅ CSV headers in Spanish
- ✅ Eager loading of category and tags
- ✅ Date format with isoformat()
- ✅ Null handling in category and tags
Losing these customizations would take ~8 min to re-apply
Step 5: Decide
→ EDIT
Justification:
- The approach is correct (~43% of changes, near the threshold)
- The customizations are significant (~8 min to re-apply)
- The 3 problems are independent (I can edit them one by one)
- Editing (~22 min) is faster than regenerating (~25 min + risk of losing customizations)
Editing plan:
-
Add a limit (independent, 5 min):
MAX_EXPORT = 10000 total = query.count() if total > MAX_EXPORT: raise HTTPException(400, f"Máximo {MAX_EXPORT} tareas por exportación") -
Implement real streaming for CSV (10 min): Change from loading everything to a generator that yields rows
-
Extract a serialization function (7 min): Create
serialize_task(task)and use it in both formats
Scenario 5: The Dashboard Metrics Calculation
from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
from app.database import get_db
from app.models import Task
from app.dependencies.auth import get_current_user
app = FastAPI()
@app.get("/api/dashboard/metrics")
async def dashboard_metrics(
current_user=Depends(get_current_user),
db: Session = Depends(get_db),
):
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
week_start = today_start - timedelta(days=today_start.weekday())
month_start = today_start.replace(day=1)
# Today's tasks
today_tasks = (
db.query(Task)
.filter(Task.owner_id == current_user.id, Task.created_at >= today_start)
.all()
)
today_created = len(today_tasks)
today_completed = len([t for t in today_tasks if t.status == "completed"])
# This week's tasks
week_tasks = (
db.query(Task)
.filter(Task.owner_id == current_user.id, Task.created_at >= week_start)
.all()
)
week_created = len(week_tasks)
week_completed = len([t for t in week_tasks if t.status == "completed"])
# This month's tasks
month_tasks = (
db.query(Task)
.filter(Task.owner_id == current_user.id, Task.created_at >= month_start)
.all()
)
month_created = len(month_tasks)
month_completed = len([t for t in month_tasks if t.status == "completed"])
# Streak: consecutive days with at least 1 completed task
streak = 0
check_date = today_start
while True:
day_end = check_date + timedelta(days=1)
day_completed = (
db.query(Task)
.filter(
Task.owner_id == current_user.id,
Task.status == "completed",
Task.completed_at >= check_date,
Task.completed_at < day_end,
)
.count()
)
if day_completed > 0:
streak += 1
check_date -= timedelta(days=1)
else:
break
# Grand total
all_tasks = (
db.query(Task)
.filter(Task.owner_id == current_user.id)
.all()
)
total = len(all_tasks)
total_completed = len([t for t in all_tasks if t.status == "completed"])
return {
"today": {"created": today_created, "completed": today_completed},
"week": {"created": week_created, "completed": week_completed},
"month": {"created": month_created, "completed": month_completed},
"streak": streak,
"total": {"all": total, "completed": total_completed},
"completion_rate": round(total_completed / total, 4) if total > 0 else 0,
}
Problems:
- Runs 4 queries that load all the tasks into memory (today, week, month, all) + 1 query per day for the streak
- The 3 period queries (today, week, month) are redundant —
today_tasksis a subset ofweek_taskswhich is a subset ofmonth_tasks - The streak loop runs one query per day — if the user has a 365-day streak, that's 365 queries
- No response_model
datetime.utcnow()is deprecated in Python 3.12+ (usedatetime.now(timezone.utc))
Do you edit, regenerate, or do a combination?
See solution
Step 1: Assess
A. Correct approach? INCORRECT
- 4 redundant queries that load everything into memory
- The streak loop is N queries where N = streak length
- This is an incorrect algorithmic approach (Signal 1 of regenerating)
B. Lines that change? ~50 of 65 (77%)
C. Interconnected? YES
- Changing from individual queries to SQL aggregations
changes the entire structure of the function
D. Design or implementation? DESIGN of the query strategy
- The metrics design (today/week/month/streak) is correct
- The way of calculating them is fundamentally incorrect
Step 2: Estimate editing: ~40 min (rewrite 5 queries + refactor redundancies + streak) Step 3: Estimate regeneration: ~18 min (there are no customizations)
Step 4: Consider losses
Customizations: NONE
- No logging
- No messages in Spanish
- No special integrations
- The code is generated without modifications
Losses: Zero
Step 5: Decide
→ REGENERATE the entire function
Justification:
- 77% of the code needs to change
- The query approach is fundamentally incorrect
- There are no customizations to lose
- Regenerating (~18 min) is much faster than editing (~40 min)
- The edits would be so interconnected that you'd essentially be rewriting — better to do it with a clean prompt
Regeneration prompt:
Regenerate the dashboard_metrics function with these corrections:
CURRENT PROBLEMS:
1. 4 queries that load all the tasks in Python —
use SQL COUNT/aggregations
2. Redundant queries (today ⊂ week ⊂ month) —
do 1 query with CASE WHEN or 1 query per period with COUNT
3. The streak loop runs N queries — use a window function or
a single query with GROUP BY date
4. Missing response_model
5. Use datetime.now(timezone.utc) instead of datetime.utcnow()
REQUIREMENTS:
- A maximum of 3-4 total SQL queries (not one per period)
- The streak must be calculated with at most 1 query
- Pydantic response model with nested models
- Keep the same response structure:
today: {created, completed}
week: {created, completed}
month: {created, completed}
streak: int
total: {all, completed}
completion_rate: float
CONTEXT:
- SQLAlchemy with PostgreSQL
- from sqlalchemy import func, case, and_
- The Task model has: id, owner_id, status, created_at,
completed_at, priority
Note: the prompt includes what was wrong, the exact requirements, and the project context — the 3 rules of effective regeneration from capsule 03.
Framework Summary
The process in 60 seconds:
1. Is the approach correct?
NO → Regenerate (function or file)
YES → Continue
2. How many edits do I need?
1-3 simple → Edit
4+ or complex → Continue
3. Are there customizations to lose?
Many → Edit (preserve the investment)
Few/None → Continue
4. What's faster?
Edit < Regenerate → Edit
Regenerate < Edit → Regenerate
5. Can I do a mix?
"Regenerate this function, edit the rest"
→ Often the best option
Quick rules for when you don't have time to analyze:
✅ Change 1 character (> to <) → Edit
✅ Add 2-3 lines (null check) → Edit
✅ Incorrect algorithmic approach → Regenerate function
✅ Incorrect architecture → Regenerate file
✅ >50% needs to change without customizations → Regenerate
✅ <30% needs to change with customizations → Edit
✅ You don't know which to choose → Edit (preserves context, lower risk)
Connection to the Project
In the capstone project (Module 8)
The framework in this capsule is exactly what you'll apply in the project. For each problem you find:
- Apply the 5-step framework
- Document your decision and justification
- Execute (edit, regenerate, or combination)
- Verify the result
Documenting decisions is part of the evaluation:
Documentation format:
## Problem #3: Inefficient task search
**Assessment:** Incorrect algorithmic approach (Python loop
instead of SQL WHERE). 90% of the code needs to change.
**Decision:** Regenerate the search_tasks function
**Justification:**
- Signal 1: fundamentally incorrect approach
- 0 customizations to lose
- Editing would take ~25 min, regenerating ~12 min
**Prompt used:** [include prompt]
**Verification:**
- ✅ The SQL query works correctly
- ✅ The existing filters are maintained
- ✅ Performance: from 3s to 50ms with 10,000 records
Troubleshooting
Problem 1: "I applied the framework and made the decision, but the result wasn't good"
Cause: The framework guides the decision but doesn't guarantee the execution. If you decide to regenerate but the prompt is bad, the result will be bad. Solution: The framework has two parts: (1) deciding what to do, (2) doing it well. If you decided to regenerate, apply the techniques from capsule 03. If you decided to edit, apply the techniques from capsule 04. The correct decision with incorrect execution still gives a bad result.
Problem 2: "I spend too much time analyzing and little time executing"
Cause: Analysis paralysis. The framework is a guide, not an academic analysis. Solution: With practice, the framework becomes intuitive. You don't need to calculate exact minutes — the complete analysis should take 1-2 minutes maximum. If after 2 minutes you don't have a clear decision, edit (it's the lower-risk option) and reconsider if the result isn't satisfactory.
Problem 3: "I don't have the experience to estimate editing vs regeneration times"
Cause: Estimates improve with practice. Solution: The first few times, note down how long each option actually took. After 10-20 decisions, your estimates will be much more accurate. Tip: most developers underestimate the regeneration time (they forget the review and the customizations) and overestimate the editing time.
Problem 4: "Every time I regenerate, the new code has different problems"
Cause: An insufficient prompt or a lack of post-regeneration review. Solution: (1) Include in the prompt everything that was right in the previous code. (2) After regenerating, do a complete code review comparing old vs new — as if it were a colleague's PR. (3) If the third regeneration attempt still has problems, edit the best of the attempts.
Exercises
Exercise 6: Create your own scenario (Hard)
Write a code scenario (30-50 lines) where:
- The correct decision is NOT obvious
- There are valid arguments both for editing and for regenerating
- Include at least 2 customizations that would be lost by regenerating
- Include at least 1 approach problem (not just targeted bugs)
Then, apply the 5-step framework to your own scenario and make a decision.
See solution (example)
Proposed scenario:
import logging
from fastapi import FastAPI, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from sqlalchemy import func
from app.database import get_db
from app.models import Task, TaskHistory
from app.dependencies.auth import get_current_user
logger = logging.getLogger(__name__)
app = FastAPI()
@app.patch("/tasks/{task_id}/status")
async def change_status(
task_id: int,
new_status: str,
current_user=Depends(get_current_user),
db: Session = Depends(get_db),
):
logger.info(
"Cambio de status solicitado",
extra={"task_id": task_id, "new_status": new_status,
"user_id": current_user.id},
)
task = db.query(Task).filter(Task.id == task_id).first()
if not task:
raise HTTPException(404, "Tarea no encontrada")
if task.owner_id != current_user.id:
raise HTTPException(403, "No autorizado")
# Validate state transitions (incorrect approach:
# should be a state machine, not if/elif/elif)
if new_status == "in_progress":
if task.status != "pending":
raise HTTPException(400, "Solo se puede iniciar una tarea pendiente")
elif new_status == "completed":
if task.status != "in_progress":
raise HTTPException(400, "Solo se puede completar una tarea en progreso")
elif new_status == "cancelled":
if task.status == "completed":
raise HTTPException(400, "No se puede cancelar una tarea completada")
else:
raise HTTPException(400, f"Estado inválido: {new_status}")
old_status = task.status
task.status = new_status
# Customization: change history
history = TaskHistory(
task_id=task.id,
field="status",
old_value=old_status,
new_value=new_status,
changed_by=current_user.id,
)
db.add(history)
db.commit()
db.refresh(task)
logger.info(
"Status cambiado exitosamente",
extra={"task_id": task_id, "old": old_status, "new": new_status},
)
return task
Analysis with the framework:
- The state transitions approach (if/elif) is problematic (it becomes unmanageable with more states) — a signal to regenerate
- BUT there are valuable customizations: logging with
extra, change history withTaskHistory, messages in Spanish - The ownership validation and null check are correct
- The approach of saving the transition in history is fine
Decision: EDIT — Refactor only the transitions to a dict/state machine, keep everything else:
VALID_TRANSITIONS = {
"pending": {"in_progress", "cancelled"},
"in_progress": {"completed", "cancelled"},
"completed": set(),
"cancelled": {"pending"},
}
if new_status not in VALID_TRANSITIONS.get(task.status, set()):
raise HTTPException(
400,
f"No se puede cambiar de '{task.status}' a '{new_status}'"
)
4 lines replace the 10 lines of if/elif. All the context is preserved.
Summary
- The 5-step framework structures the decision: assess damage → estimate editing → estimate regeneration → consider losses → decide
- The decision is not binary — there's a spectrum from editing one line to regenerating the entire file
- Combinations are frequently the best option: regenerate one function, edit the rest
- In case of a tie, editing wins because it preserves context and has lower risk
- The 5 scenarios demonstrate that context changes the answer — there's no universal rule
- The framework is a starting point you refine with experience — after 20+ decisions, it becomes intuitive
- Document your decisions: the justification is as important as the result
Additional Resources
- Martin Fowler — When to Rewrite - The Strangler Fig pattern for migrating gradually vs rewriting
- Joel Spolsky — Things You Should Never Do - A classic perspective on the dangers of rewriting
- Refactoring Guru — Refactoring Techniques - A catalog of incremental editing techniques
- Anthropic — Claude Code Best Practices - Optimize prompts for code generation and editing
- The Pragmatic Programmer — Software Entropy - How to prevent the gradual degradation of code
- Working Effectively with Legacy Code — Michael Feathers - Techniques for making safe changes in existing code
Next module: Capstone Project — apply everything you've learned in a complete codebase with real problems.
Debugging & Code Review with Claude Code — Module 7, Capsule 05 Claude Code Agentic Development Path — Guide #6 of 11