The Codex Refactoring Playbook: 20 Prompts for Legacy Code Modernization, Design Pattern Implementation, and Technical Debt Reduction

The Codex Refactoring Playbook: 20 Prompts for Legacy Code Modernization, Design Pattern Implementation, and Technical Debt Reduction

The Codex Refactoring Playbook: 20 Prompts for Legacy Code Modernization, Design Pattern Implementation, and Technical Debt Reduction

This playbook provides twenty high-leverage prompts you can feed to OpenAI Codex (or a code-focused successor) to systematically refactor legacy codebases. The goal is to make modernization repeatable, measurable, and safe. Each prompt card includes: when to use it, what inputs to supply, a fully specified prompt template, example code, iterative refinement steps, and acceptance criteria. The prompts are grouped into five domains—identifying code smells, implementing design patterns, modernizing deprecated APIs, reducing technical debt metrics, and migration strategies for framework upgrades—so you can compose them into coherent refactoring waves across your organization.

To ensure durable outcomes, the playbook emphasizes context-setting and iterative refinement. Codex performs best when you provide a compact repository summary, clear constraints, test scaffolds, and a crisp success definition. Combine these prompts with disciplined code review, static analysis, and CI gates to minimize regression risk and steadily reduce debt while unlocking modern platform capabilities. For cross-team adoption enablement, see also organizational guidance under the later “Operationalizing the Playbook” section and align with platform engineering standards. For related topics on structured prompt patterns and evaluation, consider

For a deeper exploration of building production REST APIs with Codex prompts, our comprehensive guide on The Codex API Development Playbook provides detailed strategies, practical examples, and implementation patterns that complement the techniques discussed in this article.

and quality measurement approaches related to

For a deeper exploration of product management prompts for roadmap planning and PRDs, our comprehensive guide on 30 ChatGPT-5.5 Prompts for Product Managers provides detailed strategies, practical examples, and implementation patterns that complement the techniques discussed in this article.

. Code health can be reinforced with policies discussed in

For a deeper exploration of automated code review with Codex agents, our comprehensive guide on How to Use OpenAI Codex for Automated Code Review provides detailed strategies, practical examples, and implementation patterns that complement the techniques discussed in this article.

.

Table of Contents

  1. Overview and Operating Model
  2. Context-Setting Techniques for Codex
  3. Iterative Refinement Patterns
  4. The 20 Refactoring Prompts
    1. A. Identifying Code Smells (4 prompts)
    2. B. Implementing Design Patterns (4 prompts)
    3. C. Modernizing Deprecated APIs (4 prompts)
    4. D. Reducing Technical Debt Metrics (4 prompts)
    5. E. Migration Strategies for Framework Upgrades (4 prompts)
  5. Operationalizing the Playbook
  6. Governance, Risk, and Compliance
  7. Reference Checklists and Templates

Overview and Operating Model

This playbook assumes a multi-repo environment with CI/CD, unit/integration tests, static analysis (e.g., ESLint, Pylint, Checkstyle, SpotBugs), and coverage tooling (e.g., nyc/istanbul, JaCoCo, Coverage.py). Refactors occur in “waves,” each wave aiming at specific categories of improvements with measurable exit criteria. Codex is used interactively within IDE extensions or via API to propose patches, with engineers curating outputs via PRs, tests, and staged rollouts.

  • Wave unit: A 1–2 week sprint focusing on a bounded subsystem (e.g., payment service, admin UI).
  • Gate criteria: No increased defect rate, test coverage maintained or improved, complexity and duplication budgets reduced, performance non-regressive per SLOs.
  • Artifacts: Prompt packets (context + constraints + seed examples), Codex diffs, PR descriptions, changelogs, and post-wave metrics.

Context-Setting Techniques for Codex

Codex quality is largely a function of the context you provide. For refactoring, context-setting should compress repo essentials and codify non-negotiables. Supply:

  • Repo map: High-level module layout, programming languages, frameworks, build systems, runtime versions, and third-party dependencies.
  • Constraints: Style guides, lint rules, security standards (e.g., OWASP ASVS), performance constraints, supported platforms.
  • Golden tests: Minimal unit/integration tests that must remain green (or expand them as part of prompts).
  • Definition of done: Specific metric targets (complexity ≤ N, duplication ≤ M%), deprecation eliminations, pattern adoption counts.

Codex prompt structure pattern:

  1. Objective: A concise imperative describing the refactor outcome.
  2. Scope: Specific files/functions and change boundaries.
  3. Rules: Coding standards, safety constraints, and explicit “do not change” areas.
  4. Examples: Before/after or unit tests serving as behavioral oracles.
  5. Deliverables: Code diffs, migration notes, and test updates.

Iterative Refinement Patterns

Refactoring with Codex works best as an iterative dialogue with tight acceptance loops. Use small batches and structured follow-ups:

  • Plan: Start with diagnosis prompts (smells, metrics), then choose patterns, then modernize APIs, and finally migrate frameworks.
  • Probe: Ask for a proposed plan and quick spike diffs, review, then request production-ready changes.
  • Constrain: Add failing tests, assertions, or property-based tests to force correctness.
  • Verify: Run linters, tests, and performance checks. If failures occur, feed failures back into the next prompt.
  • Lock in: Document rationale and codify checkers to prevent regressions.

The Codex Refactoring Playbook: 20 Prompts for Legacy Code Modernization, Design Pattern Implementation, and Technical Debt Reduction - Section 1

The 20 Refactoring Prompts

Each prompt card below is designed to be copy-paste ready. Adapt the variables (e.g., file paths, target versions) to your environment, and inject your repo context packet at the top of your interaction.

A. Identifying Code Smells (4 prompts)

Prompt 1: Detect Long Methods and Propose Safe Extractions

When to use: Functions exceeding agreed complexity or length budgets, with nested conditionals or mixed responsibilities.

What to supply: Target file(s), complexity budget, unit tests acting as safety net, language/tool versions.

Objective:
Identify long methods (>= 40 lines or cyclomatic complexity >= 10) and propose Extract Method refactors
that keep behavior unchanged. Provide diffs and updated/unit tests when needed.

Repo Context (summary):
- Language: Java 17, Build: Maven, Test: JUnit 5
- Quality gates: Checkstyle, SpotBugs; max cyclomatic complexity per method: 10

Scope:
- Analyze and refactor methods in src/main/java/com/acme/billing/InvoiceService.java

Rules:
- Preserve public API signatures unless absolutely necessary; if changed, update all call sites.
- Maintain logging levels; do not alter INFO/WARN/ERROR semantics.
- Add private helper methods with clear names for each extracted responsibility.
- Ensure tests remain green; add/adjust tests if coverage drops.

Deliverables:
- Unified diff (patch) for refactored file
- List of extracted methods with intent descriptions
- Test changes (if any) and rationale

Now analyze this method and propose refactorings:
// Before (excerpt)
public BigDecimal computeInvoiceTotal(Invoice invoice) {
  BigDecimal total = BigDecimal.ZERO;
  for (LineItem li : invoice.getItems()) {
    BigDecimal price = pricingService.priceOf(li.getSku(), li.getQuantity());
    if (li.getDiscountCode() != null) {
      if (discountService.isValid(li.getDiscountCode())) {
        price = price.subtract(discountService.amountFor(li.getDiscountCode(), price));
      } else {
        logger.warn("Invalid discount {}", li.getDiscountCode());
      }
    }
    if (li.isTaxable()) {
      BigDecimal rate = taxService.rateFor(invoice.getShipTo());
      price = price.add(price.multiply(rate));
    }
    total = total.add(price);
  }
  if (invoice.getCredits() != null && !invoice.getCredits().isEmpty()) {
    for (Credit c : invoice.getCredits()) {
      total = total.subtract(c.getAmount());
    }
  }
  if (total.compareTo(BigDecimal.ZERO) < 0) {
    total = BigDecimal.ZERO;
  }
  metrics.recordInvoiceTotal(invoice.getId(), total);
  return total;
}
// After (sketch)
public BigDecimal computeInvoiceTotal(Invoice invoice) {
  BigDecimal total = invoice.getItems().stream()
    .map(li -> computeLinePrice(invoice, li))
    .reduce(BigDecimal.ZERO, BigDecimal::add);
  total = applyCredits(invoice, total);
  total = clampNonNegative(total);
  metrics.recordInvoiceTotal(invoice.getId(), total);
  return total;
}

private BigDecimal computeLinePrice(Invoice invoice, LineItem li) { ... }
private BigDecimal applyCredits(Invoice invoice, BigDecimal total) { ... }
private BigDecimal clampNonNegative(BigDecimal total) { ... }

Follow-ups:

  • Iterate: “Explain each extracted method’s pre/post-conditions and side effects.”
  • Refine: “Re-run with a complexity target of 7 and ensure no name collisions, then update JUnit tests.”

Acceptance criteria: Complexity ≤ 10, tests pass, logging preserved, no public API changes.

Prompt 2: Identify God Classes and Propose Responsibility Slices

When to use: A single class centralizes diverse responsibilities (e.g., parsing, persistence, and business rules).

What to supply: The oversized class, modules that would host extractions, constraints on package boundaries.

Objective:
Detect God Class symptoms in UserManager.java and propose a slice plan that separates
- domain logic
- persistence
- integration (email, audit)
Provide a stepwise refactor plan with file-level diffs and migration notes.

Scope:
- src/main/java/com/acme/identity/UserManager.java (1500 LOC)

Constraints:
- Package boundaries: com.acme.identity.domain, .repo, .integration
- Do not change external REST API contracts (controllers stay as-is)

Deliverables:
- New class/interface list per slice with brief responsibility statements
- Migrations: constructors/factories, dependency injection wiring
- Risks and rollback plan
// Symptom excerpt
class UserManager {
  boolean createUser(UserDto dto) { ... }       // validation, hashing, repo writes
  boolean sendWelcomeEmail(String id) { ... }   // SMTP details
  boolean deactivate(String id) { ... }         // audit trail + repo
  // 100+ methods spanning DTO mapping, SQL, SMTP, audit, password policy ...
}

Follow-ups:

  • Iterate: “Generate the new constructor signatures and Spring @Configuration updates for DI wiring.”
  • Verify: “List all public methods whose responsibilities move and the new owning classes.”

Acceptance criteria: Clear separation per slice, unchanged REST contracts, updated DI, and passing integration tests.

Prompt 3: Spot Feature Envy and Recommend Move Method/Introduce Delegation

When to use: Methods manipulate data from other objects excessively, suggesting misplaced behavior.

What to supply: Class pair(s) with suspected envy, runtime behavior constraints, tests.

Objective:
Identify methods in OrderService that exhibit Feature Envy towards Order/Customer and recommend:
- Move Method targets with new signatures
- Delegation or domain services where appropriate
- Minimal diffs that preserve behavior and logging

Scope: src/main/java/com/acme/orders/OrderService.java

Rules:
- Public service API stable; if moving methods internally, keep facades delegating
- Preserve transactional boundaries (@Transactional)
- Update unit tests and mocks as needed
// Before
class OrderService {
  Money calculateLoyaltyDiscount(Order order, Customer customer) {
    int points = customer.getLoyaltyPoints();
    Tier tier = customer.getTier();
    if (tier == Tier.PLATINUM && points > 5000) return order.total().multiply(0.1);
    if (tier == Tier.GOLD && points > 2000) return order.total().multiply(0.05);
    return Money.zero();
  }
}
// After (move logic to Customer or a LoyaltyPolicy)
class Customer {
  Money discountFor(Order order) { ... } // encapsulate tier/points rules
}

class OrderService {
  Money calculateLoyaltyDiscount(Order order, Customer customer) {
    return customer.discountFor(order); // delegation
  }
}

Follow-ups:

  • Iterate: “Provide an exhaustive test matrix for tiers/points and generate parameterized JUnit tests.”
  • Refine: “Ensure transactional annotations remain on service-layer boundary methods only.”

Acceptance criteria: Reduced direct data peeking, tests validating delegation, unchanged service API.

Prompt 4: Find Cyclic Dependencies and Propose Modular Breaks

When to use: Build systems or static analyzers flag cycles across packages/modules that hinder maintainability.

What to supply: Dependency graph (can be pasted), affected modules, constraints on public APIs.

Objective:
Analyze module/package dependency cycles and propose refactors to break them using:
- interface extraction
- inversion (Dependency Inversion Principle)
- eventing or domain events
Return a plan with PR-ready diffs for the highest-value cycle.

Inputs:
- Graph excerpt (edges are dependencies):
  A -> B, B -> C, C -> A (cycle)
  A -> D
  C -> E

Rules:
- Do not create new runtime deps to forbidden layers (UI -> Domain)
- Prefer extracting interfaces into a neutral module 'spi'
- Provide a migration sequence with green builds at each step

Deliverables:
- New module layout
- Interface definitions
- Call-site updates and adapter implementations

Follow-ups:

  • Iterate: “Generate the minimal ‘spi’ module POM/Gradle file and publish steps.”
  • Validate: “List package-level circular refs resolved and residual risks.”

Acceptance criteria: Build cycles removed, layering preserved, tests green after each incremental step.

B. Implementing Design Patterns (4 prompts)

Prompt 5: Replace Conditional Dispatcher with Strategy

When to use: Large if/else or switch statements select behavior based on a type/code.

What to supply: The dispatcher function, enumeration/types, construction site.

Objective:
Replace conditional dispatch in PricingEngine with Strategy pattern.
Create Strategy interface and concrete strategies per price rule.

Scope: src/main/java/com/acme/pricing/PricingEngine.java

Rules:
- Strategies are stateless and injectable
- Use a registry keyed by rule code
- Unit tests for each strategy and registry lookup

Deliverables:
- Strategy interface
- Strategies per rule
- Registry wiring
- Updated engine calling Strategy
// Before
switch (rule) {
  case "BULK": return bulkPrice(items);
  case "SEASONAL": return seasonalPrice(items, date);
  case "LOYALTY": return loyaltyPrice(items, customer);
  default: throw new IllegalArgumentException("Unknown rule");
}
// After (sketch)
interface PriceRule {
  Money apply(List<Item> items, Context ctx);
}

class PriceRuleRegistry {
  private final Map<String, PriceRule> rules;
  Money apply(String rule, List<Item> items, Context ctx) { return rules.get(rule).apply(items, ctx); }
}

class PricingEngine {
  Money price(String rule, List<Item> items, Context ctx) { return registry.apply(rule, items, ctx); }
}

Follow-ups:

  • Iterate: “Generate concrete classes BulkPriceRule, SeasonalPriceRule, LoyaltyPriceRule with tests.”
  • Refine: “Add guard rails for missing rule and telemetry for rule selection.”

Acceptance criteria: No conditional dispatch in core path, registry-based strategy lookup, green tests.

Prompt 6: Convert Primitive Obsession to Value Object

When to use: Scattered usage of raw strings/ints for concepts like Money, Email, or Distance.

What to supply: Call sites, validation rules, arithmetic/format requirements.

Objective:
Replace primitive types representing Money with a Value Object across the checkout module.

Scope:
- src/main/java/com/acme/checkout/**

Rules:
- Money is immutable, currency-aware, and scale-safe
- Replace BigDecimal/String pairs with Money
- Provide migration shims for public APIs if needed

Deliverables:
- Money class
- Replaced signatures and call sites
- Conversion utilities
- Updated tests
// Value Object (Java)
public final class Money {
  private final BigDecimal amount;
  private final Currency currency;

  public static Money of(BigDecimal amount, Currency currency) {
    return new Money(amount.setScale(2, RoundingMode.HALF_UP), currency);
  }

  public Money add(Money other) {
    ensureSameCurrency(other);
    return of(this.amount.add(other.amount), currency);
  }

  public Money multiply(double factor) {
    return of(this.amount.multiply(BigDecimal.valueOf(factor)), currency);
  }

  // equals/hashCode/toString/ensureSameCurrency...
}

Follow-ups:

  • Iterate: “Generate a refactoring diff for CheckoutService replacing BigDecimal parameters with Money.”
  • Verify: “List all external APIs that still require primitives and add adapters.”

Acceptance criteria: Value object adopted across internals, consistent rounding, tests validating arithmetic and serialization.

Prompt 7: Introduce Adapter for External API Upgrades

When to use: Third-party APIs change shape or client versions diverge; you want to isolate surface changes.

What to supply: Old vs new client signatures, performance constraints, error semantics.

Objective:
Introduce an Adapter around a new EmailClientV3 to replace EmailClientV1 without touching 50+ call sites.

Scope:
- com.acme.integration.email

Rules:
- Adapter translates old DTOs/errors to new formats
- Keep retry/backoff policy consistent
- Provide deprecation notices and migration plan for removing Adapter later

Deliverables:
- EmailClientAdapter implementing OldEmailClient interface
- Mapping code and error translation
- Updated wiring (DI)
// Adapter sketch (Java)
class EmailClientAdapter implements OldEmailClient {
  private final EmailClientV3 v3;
  public SendResult send(OldEmailRequest req) {
    NewEmailRequest n = map(req);
    try {
      NewSendResult r = v3.send(n);
      return map(r);
    } catch (NewEmailException e) {
      throw translate(e);
    }
  }
}

Follow-ups:

  • Iterate: “Generate mapping functions and unit tests covering error translation.”
  • Refine: “Add circuit breaker integration (Resilience4j) within the adapter.”

Acceptance criteria: All call sites unchanged, adapter thoroughly tested, stable resilience semantics.

Prompt 8: Replace Deep Inheritance with Composition/Decorator

When to use: Fragile, deep class hierarchies with overridden methods and conditional flags.

What to supply: The inheritance chain, hotspots with overridden behavior, performance constraints.

Objective:
Flatten the Renderer inheritance tree by introducing Decorator(s) for concerns: caching, theming, and metrics.

Scope:
- com.acme.ui.render

Rules:
- Prefer composition over inheritance
- Provide a base Renderable and decorators implementing the same interface
- Keep rendering latency within current p95

Deliverables:
- Renderable interface
- BaseRenderer
- CacheRenderer, ThemedRenderer, MetricsRenderer decorators
- Wiring examples
// Decorator example (TypeScript)
export interface Renderable {
  render(ctx: RenderContext): string;
}

export class BaseRenderer implements Renderable {
  render(ctx: RenderContext): string { /* core render */ return "<div>...</div>"; }
}

export class MetricsRenderer implements Renderable {
  constructor(private inner: Renderable, private meter: Meter) {}
  render(ctx: RenderContext): string {
    const start = Date.now();
    const out = this.inner.render(ctx);
    this.meter.record("render.ms", Date.now() - start);
    return out;
  }
}

Follow-ups:

  • Iterate: “Generate a composition graph that replaces subclass X/Y/Z with decorator stacking.”
  • Verify: “Add performance microbenchmarks and keep p95 under 10ms.”

Acceptance criteria: Hierarchy flattened, decorators isolated by concern, stable performance.

C. Modernizing Deprecated APIs (4 prompts)

Prompt 9: Replace Node.js Callbacks with Promises/async-await

When to use: Legacy Node.js code relies on nested callbacks, making control flow and error handling brittle.

What to supply: Target files, Node version, lint rules (e.g., no-floating-promises), test harness.

Objective:
Modernize legacy callback-based filesystem and HTTP code to async/await with Promise-based APIs.

Scope:
- src/server/**/*.js (Node 18+)

Rules:
- Use fs/promises and native fetch
- Preserve error semantics and logging; no swallowed errors
- Update tests to await promises and use async test functions

Deliverables:
- Refactored code
- Updated tests
- ESLint config updates if required
// Before
fs.readFile(path, "utf8", (err, data) => {
  if (err) return next(err);
  request(url, (err2, res, body) => {
    if (err2) return next(err2);
    process(data, body, (err3, out) => {
      if (err3) return next(err3);
      res.send(out);
    });
  });
});

// After
import { readFile } from "fs/promises";

app.get("/route", async (req, res, next) => {
  try {
    const data = await readFile(path, "utf8");
    const resp = await fetch(url);
    const body = await resp.text();
    const out = await process(data, body);
    res.send(out);
  } catch (e) {
    next(e);
  }
});

Follow-ups:

  • Iterate: “Identify all callback-style APIs and propose Promise-based replacements.”
  • Verify: “Add ESLint rules to forbid new callback-style usage and autofix where possible.”

Acceptance criteria: No nested callbacks, consistent async error handling, tests updated.

Prompt 10: Migrate Java Date APIs to java.time

When to use: Code uses java.util.Date/Calendar/SimpleDateFormat; you want java.time for correctness and thread-safety.

What to supply: Target packages, time zone policies, serialization formats.

Objective:
Replace java.util.Date/Calendar and SimpleDateFormat with java.time equivalents.

Scope:
- com.acme.scheduling, com.acme.reports

Rules:
- Use Instant, LocalDate, ZonedDateTime, DateTimeFormatter
- Thread-safe formatters
- Explicit time zones via ZoneId
- Backward compatibility for on-the-wire formats

Deliverables:
- Refactored code
- Migration notes for database columns and JSON serialization
- Tests covering DST transitions and time zone conversions
// Before
Date d = new Date();
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
String s = fmt.format(d);

// After
Instant now = Instant.now();
ZonedDateTime zdt = now.atZone(ZoneId.of("UTC"));
DateTimeFormatter fmt = DateTimeFormatter.ISO_INSTANT;
String s = fmt.format(zdt);

Follow-ups:

  • Iterate: “List all SimpleDateFormat usages and provide equivalent DateTimeFormatter code.”
  • Verify: “Generate property-based tests covering random instants across DST boundaries.”

Acceptance criteria: No legacy time APIs, thread-safe formatters, tests validating edge cases.

Prompt 11: Update Python Requests to HTTPX + Async

When to use: Python services use requests synchronously and block event loops or workers; you want async performance and typing.

What to supply: Target modules, retry/circuit policies, typing standards (PEP 484), runtime (Python 3.11+).

Objective:
Migrate from requests to httpx with async support and type hints.

Scope: src/service/**/*.py

Rules:
- Prefer httpx.AsyncClient with context managers
- Preserve timeout/retry semantics
- Add type hints and pydantic models for IO payloads
- Update tests to pytest-asyncio

Deliverables:
- Refactored clients
- Typed models
- Updated tests and fixtures
# Before
import requests

def fetch_user(user_id: str) -> dict:
    r = requests.get(f"{BASE}/users/{user_id}", timeout=5)
    r.raise_for_status()
    return r.json()

# After
import httpx
from typing import Any, Dict

async def fetch_user(user_id: str) -> Dict[str, Any]:
    async with httpx.AsyncClient(timeout=5.0) as client:
        r = await client.get(f"{BASE}/users/{user_id}")
        r.raise_for_status()
        return r.json()

Follow-ups:

  • Iterate: “Generate pytest-asyncio tests and an httpx.MockTransport for offline testing.”
  • Verify: “Produce a mapping of requests parameters to httpx equivalents for our usages.”

Acceptance criteria: Async clients with type hints, mapped retries/timeouts, green async tests.

Prompt 12: Android AsyncTask to Coroutines/WorkManager

When to use: Legacy Android code relies on AsyncTask and manual threading; you want structured concurrency and background constraints.

What to supply: Target tasks, life cycle constraints, background execution requirements.

Objective:
Replace AsyncTask with Kotlin coroutines or WorkManager depending on lifetime and constraints.

Scope: app/src/main/java/com/acme/android/**

Rules:
- UI-bound tasks: use lifecycleScope + Dispatchers.IO
- Long-running/deferrable with constraints: use WorkManager
- Propagate cancellation and handle exceptions

Deliverables:
- Coroutine-based replacements
- WorkManager workers for background jobs
- Updated tests using Turbine/CoroutinesTest
// Before (Java)
new AsyncTask<Void, Void, Result>() {
  protected Result doInBackground(Void... v) { return repo.sync(); }
  protected void onPostExecute(Result r) { render(r); }
}.execute();

// After (Kotlin)
lifecycleScope.launch {
  val r = withContext(Dispatchers.IO) { repo.sync() }
  render(r)
}

Follow-ups:

  • Iterate: “Generate a WorkRequest for periodic sync with network-unmetered constraint.”
  • Verify: “Provide tests with TestCoroutineDispatcher and Robolectric.”

Acceptance criteria: No AsyncTask usage, correct lifecycle handling, background jobs via WorkManager with tests.

D. Reducing Technical Debt Metrics (4 prompts)

Prompt 13: Drive Down Cyclomatic Complexity with Guard Clauses and Extraction

When to use: Hotspots exceed complexity targets and are difficult to reason about.

What to supply: Complexity threshold, list of hotspot files, gating tests.

Objective:
Reduce cyclomatic complexity in specified methods to ≤ 7 using guard clauses, early returns, and Extract Method.

Scope: src/main/**/RiskEngine*.{java,py,ts}

Rules:
- No behavior changes
- Do not alter logging levels or message formats
- Keep public APIs stable

Deliverables:
- Refactored code with guard clauses
- Updated/added tests if new branches introduced
- Complexity report before/after
// Before (TypeScript)
function assessRisk(p: Profile): number {
  let score = 0;
  if (p.active) {
    if (p.country === "US") {
      if (p.balance > 10000) { score += 5; }
      else if (p.balance > 5000) { score += 3; }
      else { score += 1; }
    } else {
      if (p.balance > 8000) { score += 4; }
      else { score += 2; }
    }
  } else {
    score = -1;
  }
  return score;
}

// After (guard clauses + extraction)
function assessRisk(p: Profile): number {
  if (!p.active) return -1;
  return p.country === "US" ? usRisk(p.balance) : intlRisk(p.balance);
}
function usRisk(balance: number): number {
  if (balance > 10000) return 5;
  if (balance > 5000) return 3;
  return 1;
}
function intlRisk(balance: number): number {
  return balance > 8000 ? 4 : 2;
}

Follow-ups:

  • Iterate: “List remaining functions over threshold and propose guard-clauses refactors.”
  • Verify: “Generate a complexity diff report (pre/post) and CI badge update.”

Acceptance criteria: Complexity budgets met, code readability improved, unchanged behavior.

Prompt 14: Eliminate Duplication with DRY Extraction and Module Reuse

When to use: Copy-pasted logic across services increases maintenance cost and bug surface.

What to supply: Duplicate detectors output (e.g., CPD), examples of duplicates, intended shared module location.

Objective:
Identify near-duplicate code blocks (≥ 30 tokens) across 'orders' and 'invoices' services and extract them into a shared module.

Scope:
- services/orders, services/invoices

Rules:
- Extract shared code into libs/finance-core
- Preserve behavior and validations
- Add versioned changelog for shared module

Deliverables:
- Shared module with functions/classes
- Replaced call sites
- Tests covering shared behavior
// Before (Python - duplicated tax calculation)
def tax(price, region):
    if region == "EU":
        return price * 0.20
    elif region == "US":
        return price * 0.07
    return 0

# After (shared)
# libs/finance_core/tax.py
RATES = {"EU": 0.20, "US": 0.07}
def tax(price: float, region: str) -> float:
    return price * RATES.get(region, 0.0)

Follow-ups:

  • Iterate: “Generate a replacement map of duplicate blocks to shared functions with risk notes.”
  • Verify: “Publish libs/finance-core v1.0.0 and update services to consume it; produce a changelog.”

Acceptance criteria: Duplicates removed, shared library published, cross-service tests green.

Access 40,000+ AI Prompts for ChatGPT, Claude & Codex — Free!

Subscribe to get instant access to our complete Notion Prompt Library — the largest curated collection of prompts for ChatGPT, Claude, OpenAI Codex, and other leading AI models. Optimized for real-world workflows across coding, research, content creation, and business.

Get Free Access Now →

Prompt 15: Improve Null-Safety and Type Rigor

When to use: Null pointer exceptions or TypeErrors appear in logs; types are lax.

What to supply: Language type system capabilities, key paths where NPEs occur, annotations or type checkers.

Objective:
Introduce null-safety/type rigor: Java (Optional/@NonNull), TypeScript (strictNullChecks), Python (mypy).

Scope:
- Authentication and Profile modules

Rules:
- For Java: use Optional & @NonNull/@Nullable annotations; no Optional in fields
- For TS: enable strictNullChecks, add type guards
- For Python: add typing and run mypy with --strict on touched modules

Deliverables:
- Annotated code and Optional usage
- TS config updates and guards
- mypy-compliant Python modules
// Java example
public Optional<Email> findEmail(UserId id) {
  return repo.find(id).map(User::email);
}

// TypeScript guard
function isEmail(x: unknown): x is Email {
  return typeof x === "object" && x !== null && "address" in x;
}

Follow-ups:

  • Iterate: “List high-risk null dereferences and propose guard insertions with rationale.”
  • Verify: “Run mypy/tsc and include the error diff and fixes.”

Acceptance criteria: Annotated types, reduced NPE incidence, strict mode builds passing.

Prompt 16: Lift Test Coverage with Targeted Unit Test Generation

When to use: Coverage gates are failing or refactors lack regression-proofing.

What to supply: Coverage report, modules below target, critical paths to protect.

Objective:
Increase line/branch coverage to ≥ 80% for payment authorization flows by generating targeted unit tests.

Scope:
- services/payments/src/**

Rules:
- Avoid over-mocking; prefer real collaborators or in-memory fakes
- Cover happy path, error path, and edge cases
- Name tests descriptively and document intent

Deliverables:
- New tests with assertions
- Updated coverage report (pre/post)
// Example (Jest - TypeScript)
test("authorizes payment when funds are sufficient", async () => {
  const bank = new FakeBank({ balance: 1000 });
  const svc = new PaymentsService(bank);
  await expect(svc.authorize(200)).resolves.toEqual({ ok: true });
});

test("declines when funds are insufficient", async () => {
  const bank = new FakeBank({ balance: 100 });
  const svc = new PaymentsService(bank);
  await expect(svc.authorize(200)).resolves.toEqual({ ok: false, reason: "INSUFFICIENT_FUNDS" });
});

Follow-ups:

  • Iterate: “List untested branches and generate tests to cover them.”
  • Verify: “Produce the coverage delta report (lcov/html) and commit artifacts.”

Acceptance criteria: Coverage ≥ target, meaningful assertions, stable flake-free tests.

E. Migration Strategies for Framework Upgrades (4 prompts)

Prompt 17: Django 1.x to 4.x Incremental Migration Plan

When to use: Legacy Django projects need security patches and modern features; direct big-bang upgrades are too risky.

What to supply: Current version, third-party apps, Python runtime, database engines, settings modules.

Objective:
Create and execute an incremental plan to migrate Django 1.11 to 4.2 with minimal downtime.

Scope:
- monolith: manage.py, settings, apps/*

Rules:
- Use compatibility shims and feature flags
- Upgrade third-party packages to Django 4.x-compatible versions
- Replace deprecated middleware/URL confs, MIDDLEWARE settings
- Add type hints and mypy config where feasible

Deliverables:
- Stepwise plan with PR boundaries
- Per-step diffs (settings, urls, middleware, apps)
- Data migrations where necessary
- Playbook for running checks (django-upgrade, django-codemod)
# Example step: URLConf migration
# Before (urls.py)
from django.conf.urls import url
urlpatterns = [
  url(r'^admin/', admin.site.urls),
]

# After
from django.urls import path
urlpatterns = [
  path('admin/', admin.site.urls),
]

Follow-ups:

  • Iterate: “List deprecated settings removed and their replacements.”
  • Verify: “Generate a test matrix for supported Python/db combos and CI jobs.”

Acceptance criteria: App boots/runs on Django 4.2, no deprecations at runtime, tests green.

Prompt 18: React Class Components to Functional Components + Hooks

When to use: React 15/16 class components dominate; you want hooks, reduced complexity, and easier side-effect management.

What to supply: Target components, state/side-effect patterns, routing and store integrations.

Objective:
Convert class components to functional components with hooks (useState, useEffect, useMemo, custom hooks for data fetching).

Scope:
- ui/src/components/**

Rules:
- Preserve prop contracts and test snapshots
- Hoist shared logic into custom hooks
- Replace lifecycle methods with effects; clean up subscriptions

Deliverables:
- Refactored components
- New hooks files
- Updated tests (React Testing Library)
// Before
class UserList extends React.Component {
  state = { users: [], loading: true };
  componentDidMount() {
    api.fetchUsers().then(u => this.setState({ users: u, loading: false }));
  }
  render() { /* ... */ }
}

// After
function useUsers() {
  const [users, setUsers] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  React.useEffect(() => {
    let mounted = true;
    api.fetchUsers().then(u => { if (mounted) { setUsers(u); setLoading(false); } });
    return () => { mounted = false; };
  }, []);
  return { users, loading };
}
function UserList() {
  const { users, loading } = useUsers();
  return /* ... */;
}

Follow-ups:

  • Iterate: “Generate test cases for effects cleanup and prop change reactions.”
  • Verify: “List components with unsafe lifecycles and prioritize them.”

Acceptance criteria: Hook-based components, equivalent rendering behavior, tests updated.

Prompt 19: Spring Boot 1.5 to 3.x with Jakarta Namespace Changes

When to use: Older Spring Boot apps predate Spring 6/Jakarta EE changes and need modern security, metrics, and baseline JDKs.

What to supply: Current Boot version, Java version, major starters, custom filters/interceptors, JPA/Hibernate configs.

Objective:
Upgrade Spring Boot 1.5.x to 3.x, including jakarta.* namespace migration and Spring Security updates.

Scope:
- api-service/**

Rules:
- Migrate javax.* to jakarta.*
- Update Spring Security (WebSecurityConfigurerAdapter removal)
- Update Actuator endpoints and Micrometer metrics
- Ensure Java 17 baseline

Deliverables:
- Build file updates
- Package imports/diffs
- New SecurityFilterChain configuration
- Migration notes for JPA and validation (jakarta.validation)
// Security config example (Java)
// Before
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); }
}

// After
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
  http.csrf(csrf -> csrf.disable());
  return http.build();
}

Follow-ups:

  • Iterate: “List javax imports and produce jakarta replacements with diffs.”
  • Verify: “Generate actuator endpoint diffs and Micrometer mappings.”

Acceptance criteria: App boots on Boot 3.x, no javax imports, updated security config, passing tests.

Prompt 20: Ruby on Rails 4.x to 7.x with Zeitwerk Autoloading

When to use: Rails apps use classic autoloader, older gems, and outdated defaults; you want security and modern conventions.

What to supply: Current Rails/Ruby versions, gem constraints, initializers, environments.

Objective:
Upgrade Rails 4.2 to 7.x incrementally with Zeitwerk, credentials, and new defaults.

Scope:
- monolith Rails app

Rules:
- Switch to Zeitwerk autoloader
- Replace secrets.yml with Rails credentials
- Update ActiveStorage (if applicable)
- Run rails app:update and review diffs carefully

Deliverables:
- Gemfile/Bundler updates
- Autoloading refactors (module/class nesting)
- Initializers migration
- Test suite updates
# Zeitwerk compatibility example
# Before: models in non-standard paths
app/models/domain/user.rb  # class User < ActiveRecord::Base; end

# After: module nesting matches directories or add explicit module
module Domain
  class User < ApplicationRecord
  end
end

Follow-ups:

  • Iterate: “List constant autoloading issues and propose renames/nesting to satisfy Zeitwerk.”
  • Verify: “Generate credentials migration steps and CI secrets handling.”

Acceptance criteria: Boots under Rails 7 defaults, Zeitwerk clean, tests green, secure credentials in place.

Operationalizing the Playbook

The Codex Refactoring Playbook: 20 Prompts for Legacy Code Modernization, Design Pattern Implementation, and Technical Debt Reduction - Section 2

Codex Orchestration Workflow

To scale these prompts beyond one-off use, orchestrate them as pipelines with automated context assembly and post-run validations:

  1. Context builder: Script that composes a prompt packet from repo metadata, static analysis output, and test oracles.
  2. Prompt runner: CLI or CI job that sends the prompt to Codex, captures diffs, and opens draft PRs.
  3. Policy checks: Linters, formatters, security scanners (SAST/Secrets/IaC), and test suites run on the draft PR.
  4. Human-in-the-loop: Senior reviewer vets architectural changes and approves or requests iterations via prompt follow-ups.
  5. Metrics reporter: Computes complexity, duplication, and coverage deltas for the PR and comments the results.

Prompt Packet Template

=== PROJECT CONTEXT ===
Languages/Frameworks:
- Java 17 (Spring Boot 3.2), Node 18, Python 3.11
Build/CI:
- Maven, Gradle, npm/yarn; GitHub Actions
Quality Gates:
- Checkstyle/SpotBugs, ESLint, mypy; coverage >= 80%
Constraints:
- Public API stability
- Performance p95 unaffected
Tests:
- JUnit5, Jest, pytest; key suites listed below...

=== OBJECTIVE ===
<Concise imperative; e.g., "Replace conditional dispatch with Strategy" / "Migrate java.util.Date to java.time">

=== SCOPE ===
<Files, modules, and explicit non-goals>

=== RULES ===
<Safety and style constraints>

=== EXAMPLES/ORACLES ===
<Before/after sketches OR test cases that must pass>

=== DELIVERABLES ===
<Diffs, tests, notes>

CI Integration Pattern

  • Pre-generate context packets nightly for active repos and store as artifacts.
  • Codex job runs with a maximum diff size per PR to keep reviews manageable (e.g., 400 LOC).
  • PR templates include checklists: behavioral invariants, metrics deltas, migration notes, and rollback steps.
  • Auto-assign reviewers based on CODEOWNERS and past commit history to the affected modules.

Measuring Outcomes

  • Debt metrics: Track cyclomatic complexity distributions, duplication %, lint warnings, and deprecation counts per release.
  • Reliability: Track change failure rate and mean time to recovery (MTTR) post-refactor.
  • Throughput: Track lead time for changes and PR size reductions as refactors modularize code.
  • Security: Monitor SAST/DAST/secret scan findings pre/post modernization waves.

Governance, Risk, and Compliance

Risk Controls for Automated Refactors

  • Progressive delivery: Canary deploy refactor builds and compare telemetry to baseline.
  • Kill switch: Feature flags around new adapters or patterns allow instant rollback.
  • Compatibility gates: For API migrations, maintain adapters and versioned shims until clients complete their upgrades.
  • Artifact capture: Preserve Codex prompt and response artifacts for audit trails and root-cause analysis.

Security Considerations

  • Input validation: Ensure new Value Objects enforce constraints and fail fast on invalid data.
  • Secret handling: During framework upgrades, migrate to modern secret stores/credential mechanisms aligned with platform standards.
  • Dependency hygiene: As you modernize, update dependencies and pin versions; generate SBOMs and scan them.

Quality Gates and Checklists

  • Behavior unchanged: Golden tests pass; user-visible behavior is stable unless explicitly intended.
  • Performance non-regressive: p95 latency within tolerance; memory/CPU not materially worse.
  • Observability: Metrics/logs/traces remain intact; if patterns add decorators, ensure they’re instrumented.
  • Documentation: Update READMEs, ADRs (Architecture Decision Records), and deprecation schedules.

Reference Checklists and Templates

Design Pattern Adoption Checklist

  • Pattern goal stated (e.g., variability isolation, API insulation).
  • Boundary impact assessed (module/API boundaries unchanged or planned).
  • Tests ensure behavior and failure modes consistent.
  • Telemetry captured to confirm runtime parity.

API Modernization Checklist

  • Deprecated APIs enumerated with replacements.
  • Error semantics mapped and preserved.
  • Concurrency model updates validated (async/await vs callbacks/threads).
  • Docs and runbooks updated for operators.

Framework Migration Runbook Skeleton

Phase 0: Inventory & Readiness
- Enumerate frameworks, versions, and critical dependencies
- Freeze new feature changes in affected modules
- Capture baseline metrics (perf, coverage, errors)

Phase 1: Shims & Compatibility
- Introduce adapters and dual-stack code paths
- Add feature flags and toggles
- Build minimal diffs that pass all tests

Phase 2: Core Upgrade
- Upgrade framework/runtime versions
- Replace deprecated constructs and configs
- Run full test and performance suites

Phase 3: Cleanup & De-shimming
- Remove adapters/shims
- Update docs and training
- Lock gates to prevent regressions

Engineer-in-the-Loop Review Template

Summary:
- What did Codex change and why?
- Which patterns/APIs were introduced or removed?

Behavior:
- Tests added/updated
- Edge cases covered
- Observability preserved

Risk:
- Rollback plan
- Feature flags/toggles
- Backward compatibility notes

Metrics:
- Complexity/duplication deltas
- Coverage delta
- Performance change

Putting It All Together

Start each refactor wave with “identifying code smells” prompts to surface hot spots. Apply pattern prompts to isolate variability and make behaviors explicit. Modernize deprecated APIs to unlock platform improvements and reduce maintenance risk. Drive down technical debt metrics to create sustained engineering leverage. Then execute framework migrations in incremental, reversible steps. Use the context-setting and iterative refinement tactics throughout—Codex responds best to precise objectives, crisp rules, and test-grounded oracles. By instrumenting your process with metrics and governance, you transform refactoring from ad-hoc efforts into a reliable, value-creating capability woven into your delivery pipeline.

Adopt a rolling cadence: one or two prompts per subsystem per sprint, each producing a small, safe PR. As confidence grows, expand scope with robust safety nets and automated validations. The result is a legacy codebase steadily modernized, pattern-aligned, and debt-reduced, with minimal disruption and maximum transparency for engineering stakeholders and operators alike.

Get Free Access to 40,000+ AI Prompts for ChatGPT, Claude & Codex

Subscribe for instant access to the largest curated Notion Prompt Library for AI workflows.

More on this