Refactor Without Breaking It — Turn "It Looks Cleaner" into "It Still Behaves Exactly the Same"
An AI-assisted workflow that bounds every refactoring with an explicit behavioral contract, sequences it into small verifiable steps, and proves equivalence with tests before anything ships.
Executive Brief
Your Challenge
Your code has gotten harder to change — functions have grown, business rules are duplicated, dependencies are created inline, and names no longer mean what they say. You know it needs restructuring, but every time you touch legacy code you risk silently altering a return value, an exception type, an execution order, or a transaction boundary that a caller quietly depends on. The structure is the problem; the fear of breaking observable behavior is what keeps it that way.
Common Obstacles
AI can accelerate the analysis and transformation, but pointed at code without guardrails it does the wrong thing confidently: it redesigns instead of refactors, invents abstractions that add layers without value, removes code it believes is unused, and changes exception or transaction semantics while producing something that merely looks cleaner. Large uncontrolled rewrites disguised as refactoring create more risk than they remove — and 'it passed the tests' means nothing when the tests only execute the code without asserting its behavior.
The ABME Approach
This workflow does it in the right order: define the specific maintainability problem, inventory the behavior that must remain stable, assess risk by impact rather than code size, then use the prompt pack to generate an incremental change plan and revised code that preserves the public contract. Characterization and differential tests establish a baseline so intentional changes are distinguishable from accidental ones, and a 28-point validation checklist gates acceptance. The goal is not fashionable code — it is future change that is safer, faster, and easier, with no unintended behavior slipping through.
Insight Summary
Successful refactoring is not measured by how different the code looks. It is measured by whether the software becomes easier to change while continuing to behave correctly.
A refactoring without a specific problem statement and a measurable outcome is churn. Define the problem before you change the code.
Risk should be based on potential impact, not code size. A three-line change to an authorization check is critical-risk; a hundred-line rename is not.
Behavioral equivalence is a claim you cannot make without executing tests. 'Not referenced in this repository' does not mean unused, and 'looks equivalent' is not equivalence.
A rewrite may be justified — but it should never be disguised as a refactoring. The testing surface, migration cost, and operational risk are categorically different.
Coverage is not proof. Tests can execute a path without verifying its behavior, which is exactly how a refactoring passes CI and still breaks a caller.
Moving a side effect across a conditional or transaction boundary changes behavior significantly — even when the code around it looks unchanged.
The Journey
Three phases; each lists the tools you'll use there.
Define the Contract and the Risk
- State the specific maintainability problem and measurable outcome
- Inventory inputs, outputs, side effects, ordering, and failure behavior
- Document the public contract for every affected interface
- Classify the refactoring by risk using the four-level model
- Distinguish refactoring from bug fix, feature change, and rewrite
Refactor Incrementally with AI
- Run the primary prompt with the code, contract, and constraints
- Generate characterization and golden-master baselines first
- Apply follow-up prompts for extraction, duplication, error handling, and DI
- Split the work into a small, independently releasable pull-request sequence
- Verify behavioral equivalence with a differential comparison
Validate, Deploy, and Roll Back Safely
- Run the 28-point validation checklist
- Execute characterization, unit, integration, contract, regression, differential, performance, and security tests
- Compare behavior and performance against representative workloads
- Deploy incrementally with feature flags, canary, and rollback thresholds
What's Inside the Execution Layer
Numbered deliverables grouped by phase. Membership unlocks every tool.
Prerequisites Checklist
- Assemble the code, contracts, and constraints before prompting
- Surface coverage gaps and rollback constraints early
- Trigger characterization tests when behavior is poorly understood
Before using this workflow, gather:
Safe Refactoring Prompt Pack
- Produce an incremental change plan bounded by a behavioral contract
- Generate characterization, differential, and equivalence tests
- Split the work into small, reviewable pull requests
Primary Prompt
Start here with the code, its contract, and all available constraints.You are a senior software engineer performing a behavior-preserving refactoring. I will provide some or all of the following: • Refactoring objective • Source code • Business requirements • Acceptance criteria • Existing tests • Public interfaces • Data models • Database behavior • External dependencies • Performance expectations • Security requirements • Logging and audit requirements • Runtime and framework versions • Coding standards • Known defects • Deployment constraints Your task is to design and, where requested, produce a safe incremental refactoring. Do not begin by rewriting the code. First: 1. Summarize what the code currently does. 2. Identify the specific maintainability or design problem. 3. Determine whether the requested work is: • Refactoring • Bug fix • Feature change • Architectural redesign • Rewrite • A combination 4. Separate: • Behavior that must remain unchanged • Behavior intentionally changing • Behavior that is ambiguous • Behavior that appears accidental • Behavior that may be relied upon by callers 5. Inventory: • Inputs • Outputs • Side effects • Exceptions • Status codes • Logging • Audit events • Persistence • Network calls • File operations • Shared state • Concurrency • Ordering • Configuration • Public interfaces 6. Identify dependencies and ownership boundaries. 7. Identify current tests. 8. Identify unprotected behavior. 9. Identify architectural and operational constraints. 10. Assess refactoring risk. Then create an incremental refactoring plan. For each step include: • Step ID • Objective • Files or components affected • Exact structural change • Behavior that must remain stable • Required tests before the step • Required tests after the step • Risk • Rollback point • Review notes Requirements: • Prefer small, independently verifiable steps. • Do not combine unrelated cleanup. • Do not change public behavior silently. • Do not remove code unless usage is verified. • Do not change exception behavior without documenting it. • Do not change transaction boundaries unintentionally. • Do not change authorization behavior. • Do not change logging or audit behavior without review. • Preserve output schema and ordering where required. • Preserve configuration compatibility where required. • Preserve supported runtime compatibility. • Avoid unnecessary abstractions. • Avoid speculative design patterns. • Keep the code understandable to the current team. • Follow the existing project conventions where reasonable. • Clearly label intentional behavior changes. • Clearly label placeholders and unverified assumptions. • Do not claim behavioral equivalence unless tests were executed. When generating revised code: • Preserve the public contract. • Preserve meaningful side effects. • Preserve failure semantics. • Use clear names. • Keep functions focused. • Isolate dependencies where useful. • Reduce duplication only when the logic represents the same concept. • Avoid broad framework migrations. • Add comments only where intent is not obvious. • Include example tests. • Identify any code that should remain unchanged. After the refactoring, provide: 1. Executive summary. 2. Current-state assessment. 3. Behavior contract. 4. Risk assessment. 5. Incremental refactoring plan. 6. Revised code. 7. Before-and-after comparison. 8. Required tests. 9. Compatibility assessment. 10. Performance considerations. 11. Security considerations. 12. Deployment and rollback guidance. 13. Remaining technical debt. 14. Validation checklist.
Create Characterization Tests
Before any structural change, to lock in current observable behavior.Create characterization tests for the current implementation before refactoring. The tests should capture observable behavior, including: • Valid inputs • Invalid inputs • Boundary values • Exceptions • Side effects • Output ordering • Logging or audit behavior where contractual • Data persistence • Dependency interactions • Known quirks Do not “correct” existing behavior in the tests unless the behavior is explicitly being changed.
Extract a Large Function
When a function mixes multiple responsibilities.Refactor this large function into smaller cohesive functions. Before changing it: • Identify each responsibility • Identify shared state • Identify execution order • Identify side effects • Identify variables crossing boundaries • Identify error-handling behavior Then propose the smallest safe extraction sequence. Preserve observable behavior.
Remove Duplication
When repeated code blocks may or may not represent the same concept.Analyze these repeated code blocks. Determine whether they represent: • The same business rule • Similar syntax with different meaning • Shared infrastructure behavior • Accidental duplication • Intentional independence Consolidate only when the code should change for the same reason.
Simplify Conditional Logic
When nested or unclear conditionals obscure the flow.Refactor the conditional logic for clarity. Use: • Guard clauses • Named predicates • Explicit state handling • Reduced nesting • Clear defaults Preserve evaluation order, side effects, short-circuit behavior, and exception behavior.
Improve Testability
When hidden dependencies make the code hard to test.Refactor the code to improve testability. Focus on: • Hidden dependencies • Static state • Global state • Direct system clock use • Direct randomness • Direct file access • Direct network access • Hardcoded configuration • Mixed business and infrastructure logic Recommend the smallest changes that create reliable test seams.
Separate Business Logic from Infrastructure
When business decisions are entangled with framework or I/O concerns.Separate business decisions from database, network, framework, and file-system concerns. Identify: • Pure logic • State-changing operations • External dependencies • Transaction ownership • Error mapping • Logging and audit responsibilities Preserve the current public contract and transaction semantics.
Refactor Error Handling
When errors are swallowed, wrapped inconsistently, or lose context.Refactor the error handling. Assess: • Exception types • Error wrapping • Lost context • Duplicate logging • Swallowed errors • Retry behavior • Caller-visible behavior • Sensitive information exposure • Cleanup Preserve established public error contracts unless an intentional change is documented.
Replace Global State
When global or static state creates hidden dependencies or test coupling.Create an incremental plan to replace the global or static state. Document: • Current readers • Current writers • Initialization • Lifetime • Concurrency • Test dependencies • Configuration dependencies • Backward compatibility Do not replace it with a complex abstraction unless justified.
Refactor for Dependency Injection
When dependencies are constructed inline and should be explicit.Identify dependencies that should be explicit. Introduce dependency injection only where it improves: • Testability • Ownership • Lifecycle control • Configuration • Substitution • Reliability Avoid passing every utility through multiple layers without a clear benefit.
Refactor a Legacy Module
When modernizing a poorly understood legacy module incrementally.Create an incremental refactoring plan for this legacy module. Prioritize: 1. Characterization tests 2. Public-contract documentation 3. Isolation of high-risk side effects 4. Reduction of duplication 5. Improved naming 6. Smaller responsibility boundaries 7. Safer error handling 8. Removal of verified dead code Do not propose a full rewrite unless the evidence supports it.
Verify Behavioral Equivalence
After the refactoring, to compare old and new behavior.Compare the original and refactored implementations. Create a behavioral equivalence matrix covering: • Inputs • Outputs • Exceptions • Status codes • Side effects • Persistence • Ordering • Logging • Audit events • Performance • Concurrency • Configuration • Compatibility Identify every confirmed, likely, or uncertain behavior difference.
Create a Pull-Request Sequence
When splitting a large refactoring into reviewable increments.Split the refactoring into small pull requests. For each pull request include: • Objective • Scope • Files affected • Tests • Risk • Reviewer focus • Rollback point • Dependencies on earlier changes Keep each pull request independently understandable and releasable where practical.
Before-and-After Comparison
- Document real improvements rather than reduced line count
- Confirm the public contract and failure semantics are unchanged
- Give reviewers a concise structural diff
| Dimension | Before | After |
|---|---|---|
| Function responsibilities | Five mixed concerns | Coordinated focused functions |
| Time dependency | Direct system clock | Injected clock |
| Calculation testability | Embedded | Pure function |
| Public contract | Existing result object | Unchanged |
| Persistence order | Before email and audit | Unchanged |
| Failure semantics | Exceptions after persistence possible | Unchanged |
| Duplication | Repeated result construction | Central helper |
| Complexity | Nested and sequential | Clear top-level workflow |
Test Matrix
- Classify each behavior as equivalent or changed
- Drive differential testing with concrete scenarios
- Evidence behavioral equivalence for review sign-off
| Test Area | Before | After | Expected |
|---|---|---|---|
| Null request | Throws ArgumentNullException | Same | Equivalent |
| Missing customer | Failure result | Same | Equivalent |
| Empty items | Failure result | Same | Equivalent |
| Payment decline | Logs warning and returns failure | Same | Equivalent |
| Successful order | Saves, emails, audits | Same | Equivalent |
| Email failure | Order already saved; exception propagates | Same | Equivalent |
| Audit failure | Order and email completed; exception propagates | Same | Equivalent |
| Timestamp | Current UTC | Injected UTC equivalent | Equivalent |
| Total calculation | Item sum | Extracted item sum | Equivalent |
Validation Checklist
- Accept or reject the AI's refactoring
- Confirm behavioral equivalence and compatibility
- Verify tests were executed and human review completed
Before accepting the refactoring:
Refactoring Test Plan
- Cover behavior at every layer before accepting changes
- Run differential comparisons against the original implementation
- Verify security and performance are not silently regressed
Apply the appropriate test types for the refactoring:
Characterization Tests
Unit Tests
Integration Tests
Contract Tests
Regression Tests
Differential Tests
Performance Tests
Security Tests
🔒 The full execution layer — every checklist, matrix, and the prompt pack — is included with ABME membership.
Unlock Full BlueprintFull Playbook
Overviewpublic
Refactoring improves the internal structure of software without intentionally changing its external behavior.
It may involve:
- Breaking large functions into smaller units
- Improving naming
- Removing duplication
- Simplifying conditionals
- Separating responsibilities
- Isolating dependencies
- Replacing hidden global state
- Improving error handling
- Clarifying interfaces
- Reducing coupling
- Improving testability
- Consolidating repeated business rules
- Removing obsolete code
Refactoring is valuable because software becomes more difficult to change as complexity, duplication, coupling, and unclear ownership accumulate.
However, refactoring also creates risk.
A change that appears structural may unintentionally alter:
- Return values
- Error behavior
- Side effects
- Timing
- Execution order
- Database transactions
- Logging
- Authorization
- Public interfaces
- Serialization
- Resource usage
- Compatibility
- Deployment behavior
This workflow uses AI to analyze existing code, define the behavior that must remain stable, identify safe refactoring boundaries, generate an incremental change plan, propose revised code, and design tests that verify behavioral equivalence.
The goal is not to make code look more fashionable. The goal is to make future change safer, faster, and easier without introducing unintended behavior.
Business Problempublic
Code often becomes difficult to maintain because:
- Features are added incrementally.
- Deadlines favor local fixes.
- Business logic is duplicated.
- Functions grow too large.
- Dependencies are created directly inside methods.
- Naming no longer reflects current behavior.
- Error handling is inconsistent.
- Configuration is hardcoded.
- Tests depend on internal implementation.
- Multiple responsibilities are mixed together.
- Teams are afraid to change legacy code.
- Temporary workarounds become permanent.
- Framework patterns change over time.
- Ownership changes.
- Documentation becomes stale.
Poorly structured code can lead to:
- Slow delivery
- High defect rates
- Expensive onboarding
- Fragile releases
- Inconsistent behavior
- Duplicate fixes
- Security weaknesses
- Incomplete tests
- Difficult debugging
- Vendor or framework lock-in
- Growing operational cost
Refactoring can reduce these problems, but large uncontrolled rewrites often create more risk than they remove.
AI can accelerate code analysis and transformation, but only when the refactoring is bounded by explicit behavioral contracts and validated incrementally.
Typical Use Casespublic
Use this workflow when:
- Simplifying a large function
- Removing duplication
- Improving naming and readability
- Separating business logic from infrastructure
- Preparing code for testing
- Isolating external dependencies
- Replacing global or static state
- Improving error handling
- Reducing coupling
- Extracting reusable components
- Consolidating repeated validation
- Preparing for a framework upgrade
- Modernizing legacy code incrementally
- Improving module boundaries
- Reducing technical debt
- Preparing code for new features
- Correcting architectural drift
- Improving maintainability before handoff
Do NOT Use This Workflow Whenpublic
This workflow is not intended to:
- Rewrite an entire application without a migration strategy
- Mix refactoring with unrelated feature development
- Change public behavior without documenting it
- Replace code review
- Replace automated testing
- Replace architecture review
- Introduce fashionable patterns without measurable benefit
- Add abstractions solely to reduce line count
- Split functions mechanically without improving responsibility boundaries
- Remove code solely because AI believes it is unused
- Replace stable code without a clear objective
- Change security behavior unintentionally
- Change database transactions without validation
- Change API contracts silently
- Perform broad production changes without rollback planning
- Guarantee behavioral equivalence without execution and testing
A refactoring should have a specific problem statement and a measurable outcome.
Expected Outcomepublic
After completing this workflow, you should have:
- A clear refactoring objective
- An inventory of behavior that must remain unchanged
- A public-contract assessment
- A side-effect inventory
- A dependency map
- A risk assessment
- A test coverage assessment
- A behavioral characterization plan
- A step-by-step refactoring sequence
- Revised code or pseudocode
- Regression tests
- Compatibility checks
- Performance checks
- Deployment considerations
- Rollback guidance
- A before-and-after comparison
- Remaining technical debt
🔒 The complete playbook — reference models, worked examples, and operational guidance — is included with ABME membership.
Unlock Full BlueprintRefactoring Objectivesprotected
A complete refactoring plan should answer:
- What problem is the refactoring solving?
- Which behavior must remain unchanged?
- Which behavior is intentionally changing?
- What public interfaces are affected?
- What hidden side effects exist?
- What dependencies are involved?
- What tests currently protect the code?
- Which behaviors are undocumented?
- What is the smallest safe sequence of changes?
- How will each step be validated?
- What risks are introduced?
- What should not be changed?
- How will performance be compared?
- How will compatibility be verified?
- How will the change be rolled back?
- What future changes become easier afterward?
Refactoring Versus Rewritingprotected
Refactoring
- Preserves intended external behavior
- Proceeds incrementally
- Uses tests to verify equivalence
- Produces small reviewable changes
- Improves structure
- Reduces risk through controlled steps
Rewriting
- Replaces a substantial implementation
- May change behavior
- May change architecture
- Often requires migration
- Usually has a larger testing surface
- Carries greater delivery and operational risk
Refactoring Categoriesprotected
Extract Method or Function
- Performs one identifiable task
- Has a meaningful name
- Can be tested independently
- Obscures the main flow
- Is repeated
Extract Class or Component
Rename for Clarity
- Purpose
- Domain meaning
- Units
- Scope
- Mutability
- Ownership
- Side effects
Remove Duplication
- The logic represents the same business rule
- The code changes for the same reason
- Differences are configuration rather than behavior
Simplify Conditional Logic
- Using guard clauses
- Naming complex expressions
- Separating validation
- Replacing duplicated branches
- Clarifying state transitions
- Removing impossible branches
Separate Pure Logic from Side Effects
- Calculation from persistence
- Validation from network calls
- Mapping from database access
- Decision logic from logging
- Business rules from framework concerns
Introduce Dependency Injection
- Database clients
- HTTP clients
- File-system objects
- Clocks
- Random generators
- Cloud SDK clients
- Notification services
Replace Global or Static State
- Hidden dependencies
- Test-order dependency
- Concurrency risk
- Difficult lifecycle management
- Configuration ambiguity
Consolidate Error Handling
- Swallowed
- Converted inconsistently
- Logged repeatedly
- Leaking sensitive details
- Returned as success
- Losing original context
- Retried unsafely
Clarify Data Models
- One object serves unrelated purposes
- Persistence objects are exposed publicly
- Mutable objects cross boundaries
- Optional fields are ambiguous
- Units or formats are unclear
Improve Module Boundaries
- Internal implementation is exposed
- Imports are cyclic
- Shared utilities become dumping grounds
- Business logic crosses ownership boundaries
- Modules cannot be tested independently
Behavioral Equivalenceprotected
Behavioral Equivalence
- Return values
- Exceptions
- Error codes
- Status codes
- Output schema
- Ordering
- Side effects
- Persistence
- Logging and audit events
- Timing guarantees
- Idempotency
- Retry behavior
- Security decisions
- Configuration behavior
- Resource cleanup
- Public interfaces
- Compatibility
Behavior Inventoryprotected
Inputs
- Parameters
- Data types
- Nullability
- Defaults
- Formats
- Ranges
- Environment variables
- Configuration
- Files
- Events
- User identity
Outputs
- Return values
- Response bodies
- Status codes
- Exceptions
- Files
- Events
- Database changes
- Logs
- Metrics
- Notifications
Side Effects
- Database writes
- Network calls
- File operations
- Queue messages
- Cache updates
- Audit events
- Global state
- Resource allocation
- Process execution
- External system changes
Ordering
Failure Behavior
- What fails
- What is rolled back
- What is retried
- What is logged
- What callers receive
- What partial state may remain
Refactoring Risk Modelprotected
Critical Risk
- Authentication
- Authorization
- Payment processing
- Data deletion
- Cross-tenant access
- Cryptography
- Safety-critical behavior
- Irrecoverable operations
- Regulatory calculations
- Security boundaries
High Risk
- Public APIs
- Database transactions
- Data migrations
- Concurrency
- Distributed workflows
- External integrations
- Deployment sequencing
- High-volume processing
- Financial calculations
- Audit behavior
Medium Risk
- Internal business logic
- Error handling
- Caching
- Configuration
- Serialization
- Moderate-volume workflows
- Shared libraries
Low Risk
- Internal naming
- Local extraction
- Comments
- Dead local variables
- Formatting already controlled by tooling
- Small duplicated pure logic
Example Inputprotected
Refactoring Objective
Simplify an order-processing method that validates an order, charges a payment card, saves the order, sends a confirmation email, and writes an audit event.
Existing Code
public async Task<OrderResult> ProcessOrder(
OrderRequest request)
{
if (request == null)
{
throw new ArgumentNullException(
nameof(request)
);
}
if (request.CustomerId == null)
{
return new OrderResult
{
Success = false,
Error = "Customer is required"
};
}
if (request.Items == null ||
request.Items.Count == 0)
{
return new OrderResult
{
Success = false,
Error = "At least one item is required"
};
}
decimal subtotal = 0;
foreach (var item in request.Items)
{
subtotal += item.Price * item.Quantity;
}
var payment = await _paymentClient.ChargeAsync(
request.PaymentToken,
subtotal
);
if (!payment.Success)
{
_logger.LogWarning(
"Payment failed for customer {CustomerId}",
request.CustomerId
);
return new OrderResult
{
Success = false,
Error = "Payment failed"
};
}
var order = new Order
{
CustomerId = request.CustomerId,
Items = request.Items,
Subtotal = subtotal,
PaymentId = payment.PaymentId,
CreatedAt = DateTime.UtcNow
};
await _repository.SaveAsync(order);
await _emailSender.SendOrderConfirmationAsync(
request.CustomerId,
order.Id
);
await _auditWriter.WriteAsync(
"OrderCreated",
request.CustomerId,
order.Id
);
return new OrderResult
{
Success = true,
OrderId = order.Id
};
}Example Current-State Assessmentprotected
The method currently performs five responsibilities:
- Input validation
- Total calculation
- Payment processing
- Persistence
- Post-order notifications and auditing
The method also contains several important behavioral contracts:
- Null request throws ArgumentNullException.
- Missing customer returns an unsuccessful result.
- Empty items return an unsuccessful result.
- Payment failure returns an unsuccessful result and writes a warning.
- Order persistence occurs before email and audit.
- Email failure occurs after payment and persistence.
- Audit failure occurs after email.
- The order timestamp uses the system UTC clock.
- No rollback occurs when email or audit fails.
- The caller may receive an exception after an order has already been persisted.
The last behavior is operationally significant and should not be changed accidentally during refactoring.
Example Refactoring Planprotected
Step RF-001 — Add Characterization Tests
Capture:
- Null request exception
- Validation results
- Total calculation
- Payment failure behavior
- Save-before-email ordering
- Save-before-audit ordering
- Behavior when email fails
- Behavior when audit fails
- UTC timestamp behavior
Step RF-002 — Extract Validation
Move request validation into a private or domain-level function without changing result types or messages.
Step RF-003 — Extract Total Calculation
Move subtotal calculation into a pure function.
Step RF-004 — Inject Clock
Replace direct DateTime.UtcNow use with an explicit clock while preserving UTC values.
Step RF-005 — Extract Order Construction
Move order mapping into a dedicated function.
Step RF-006 — Extract Post-Persistence Actions
Group email and audit operations only if their ordering and failure behavior remain explicit.
Step RF-007 — Review Transaction and Recovery Design Separately
Do not alter persistence, email, or audit transactional behavior as part of the structural refactoring.
Example Refactored Codeprotected
public sealed class OrderService
{
private readonly IPaymentClient _paymentClient;
private readonly IOrderRepository _repository;
private readonly IEmailSender _emailSender;
private readonly IAuditWriter _auditWriter;
private readonly IClock _clock;
private readonly ILogger<OrderService> _logger;
public OrderService(
IPaymentClient paymentClient,
IOrderRepository repository,
IEmailSender emailSender,
IAuditWriter auditWriter,
IClock clock,
ILogger<OrderService> logger)
{
_paymentClient = paymentClient;
_repository = repository;
_emailSender = emailSender;
_auditWriter = auditWriter;
_clock = clock;
_logger = logger;
}
public async Task<OrderResult> ProcessOrder(
OrderRequest request)
{
ArgumentNullException.ThrowIfNull(request);
var validationResult = Validate(request);
if (validationResult is not null)
{
return validationResult;
}
var subtotal = CalculateSubtotal(
request.Items!
);
var payment = await _paymentClient.ChargeAsync(
request.PaymentToken,
subtotal
);
if (!payment.Success)
{
LogPaymentFailure(request.CustomerId!);
return Failure("Payment failed");
}
var order = CreateOrder(
request,
subtotal,
payment.PaymentId
);
await _repository.SaveAsync(order);
await SendPostOrderActions(
request.CustomerId!,
order.Id
);
return new OrderResult
{
Success = true,
OrderId = order.Id
};
}
private static OrderResult? Validate(
OrderRequest request)
{
if (request.CustomerId is null)
{
return Failure(
"Customer is required"
);
}
if (request.Items is null ||
request.Items.Count == 0)
{
return Failure(
"At least one item is required"
);
}
return null;
}
private static decimal CalculateSubtotal(
IEnumerable<OrderItem> items)
{
return items.Sum(
item => item.Price * item.Quantity
);
}
private Order CreateOrder(
OrderRequest request,
decimal subtotal,
string paymentId)
{
return new Order
{
CustomerId = request.CustomerId!,
Items = request.Items!,
Subtotal = subtotal,
PaymentId = paymentId,
CreatedAt = _clock.UtcNow
};
}
private async Task SendPostOrderActions(
string customerId,
string orderId)
{
await _emailSender
.SendOrderConfirmationAsync(
customerId,
orderId
);
await _auditWriter.WriteAsync(
"OrderCreated",
customerId,
orderId
);
}
private void LogPaymentFailure(
string customerId)
{
_logger.LogWarning(
"Payment failed for customer {CustomerId}",
customerId
);
}
private static OrderResult Failure(
string error)
{
return new OrderResult
{
Success = false,
Error = error
};
}
}This refactoring improves readability and testability while preserving the original sequencing.
It does not solve the risk of successful payment and persistence followed by email or audit failure. That issue should be handled as a separate behavioral and architectural change.
Example Characterization Testsprotected
[Fact]
public async Task ThrowsWhenRequestIsNull()
{
var service = CreateService();
await Assert.ThrowsAsync<
ArgumentNullException
>(
() => service.ProcessOrder(null!)
);
}
[Fact]
public async Task ReturnsFailureWhenCustomerIsMissing()
{
var service = CreateService();
var request = new OrderRequest
{
CustomerId = null,
Items = ValidItems()
};
var result = await service.ProcessOrder(
request
);
result.Success.Should().BeFalse();
result.Error.Should().Be(
"Customer is required"
);
}
[Fact]
public async Task SavesOrderBeforeSendingEmail()
{
var sequence = new List<string>();
var repository = Substitute.For<
IOrderRepository
>();
var emailSender = Substitute.For<
IEmailSender
>();
repository
.When(x => x.SaveAsync(
Arg.Any<Order>()
))
.Do(_ => sequence.Add("save"));
emailSender
.When(x => x.SendOrderConfirmationAsync(
Arg.Any<string>(),
Arg.Any<string>()
))
.Do(_ => sequence.Add("email"));
var service = CreateService(
repository: repository,
emailSender: emailSender
);
await service.ProcessOrder(
ValidRequest()
);
sequence.Should().ContainInOrder(
"save",
"email"
);
}
[Fact]
public async Task UsesInjectedUtcTime()
{
var fixedTime = new DateTimeOffset(
2026,
7,
21,
14,
30,
0,
TimeSpan.Zero
);
var clock = Substitute.For<IClock>();
clock.UtcNow.Returns(fixedTime);
Order? savedOrder = null;
var repository = Substitute.For<
IOrderRepository
>();
repository
.When(x => x.SaveAsync(
Arg.Any<Order>()
))
.Do(call =>
{
savedOrder = call.Arg<Order>();
});
var service = CreateService(
clock: clock,
repository: repository
);
await service.ProcessOrder(
ValidRequest()
);
savedOrder!.CreatedAt.Should().Be(
fixedTime
);
}The exact framework and mocking syntax should match the project.
Testing Strategiesprotected
Characterization Testing
Characterization tests document what the code currently does, including behavior that may be surprising.
They are especially useful when:
- Requirements are incomplete
- Code is legacy
- Few tests exist
- Callers are unknown
- Error behavior is unclear
- Side effects are complex
- Refactoring risk is high
Characterization tests do not automatically declare current behavior correct.
They create a baseline so intentional changes can be distinguished from accidental ones.
Golden Master Testing
A golden master test captures existing output for representative inputs and compares future output against it.
It may be useful for:
- Report generation
- Complex formatting
- Serialization
- Legacy calculations
- Transformation pipelines
- Large deterministic outputs
Risks include:
- Preserving incorrect behavior
- Producing unreadable snapshots
- Hiding small but important differences
- Blindly approving updated output
Use golden masters as temporary safety nets, not substitutes for meaningful assertions.
Differential Testing
Differential testing executes the old and new implementations with the same inputs.
Compare:
- Return values
- Exceptions
- Side effects
- Database changes
- Events
- Logs where contractual
- Ordering
- Timing ranges
- Resource usage
Differences must be classified as:
- Intended
- Acceptable
- Defect
- Unknown
This approach is useful for calculations, transformations, parsers, and legacy business rules.
Review Checkpointsprotected
Public Contract Review
Before refactoring a public method, API, library, event, or file format, document:
- Name
- Signature
- Parameters
- Defaults
- Null behavior
- Output schema
- Ordering
- Exceptions
- Status codes
- Side effects
- Versioning
- Deprecation policy
- Known consumers
A seemingly harmless change such as replacing one exception type may break callers.
Side-Effect Review
Refactoring must preserve or intentionally change:
- Database writes
- Commit timing
- File writes
- Notifications
- Queue messages
- Audit events
- Log events
- Cache invalidation
- External calls
- Resource cleanup
Moving a side effect across a conditional or transaction boundary may change behavior significantly.
Transaction Review
When refactoring database code, verify:
- Transaction start
- Transaction completion
- Rollback behavior
- Isolation level
- Lock duration
- Save ordering
- External calls inside transactions
- Retry behavior
- Partial completion
- Idempotency
Do not extend transaction duration accidentally by moving network calls inside the transaction.
Concurrency Review
Refactoring can change timing and expose concurrency defects.
Review:
- Shared state
- Lock scope
- Lock order
- Atomicity
- Async behavior
- Thread safety
- Resource ownership
- Cancellation
- Duplicate requests
- Event ordering
Replacing a sequence with parallel execution is a behavior and performance change, not merely a refactoring.
Error-Behavior Review
Preserve or explicitly document changes to:
- Exception type
- Exception message when contractual
- Error code
- Status code
- Retry classification
- Logging
- Error wrapping
- Stack trace preservation
- Cleanup
- Partial state
Avoid broad exception handling such as:
try:
process()
except Exception:
return NoneThis may simplify control flow while hiding failures and changing the caller contract.
Security Review During Refactoring
Refactoring can accidentally weaken security by:
- Moving authorization after data access
- Sharing previously isolated data
- Returning persistence objects directly
- Logging sensitive inputs
- Removing validation
- Changing identity context
- Reusing privileged clients
- Broadening dependency permissions
- Bypassing tenant filters
- Reordering security checks
Security-sensitive behavior should receive explicit regression tests.
Performance Review
Refactoring may alter:
- Number of database queries
- Number of external calls
- Memory allocation
- Serialization
- Caching
- Loop complexity
- Parallelism
- Lock duration
- Connection usage
- Startup time
Before and after comparisons should use representative workloads.
Do not reject a maintainability improvement over an insignificant microbenchmark difference, but investigate meaningful regressions.
Compatibility Review
Verify compatibility across:
- Runtime versions
- Framework versions
- Operating systems
- Public APIs
- Stored data
- Configuration files
- Environment variables
- Database schemas
- Event consumers
- External integrations
- Package consumers
Internal cleanup can still break compatibility when internal assumptions leak into public behavior.
Dead Code Removal
Before removing code, verify:
- Static references
- Reflection or dynamic loading
- Configuration-based invocation
- Framework conventions
- Scheduled execution
- External callers
- Scripts
- Documentation examples
- Tests
- Deployment hooks
- Feature flags
- Backward compatibility
"Not referenced in this repository" does not always mean unused.
Remove dead code in a separate, clearly reviewed change where practical.
Abstraction Review
An abstraction is useful when it:
- Represents a stable concept
- Hides meaningful complexity
- Supports substitution
- Clarifies ownership
- Reduces duplicated business logic
- Improves testing
- Establishes a boundary
An abstraction is harmful when it:
- Has only one trivial implementation
- Renames existing operations without simplifying them
- Requires many layers to follow one action
- Is created for hypothetical future needs
- Hides important domain behavior
- Makes debugging harder
Prefer the smallest abstraction that solves the current problem.
Naming Improvements
Good names communicate:
- Domain meaning
- Intent
- Unit
- Scope
- Direction
- Ownership
- Mutation
- Side effects
Weak:
data
item
process
handler
manager
util
temp
result2Better names depend on the domain:
eligibleOrders
calculateDiscount
authorizationDecision
paymentCaptureResult
subscriptionExpirationUtcAvoid renaming public symbols without a compatibility plan.
Function Extraction
Extract a function when the resulting function:
- Has one clear responsibility
- Has a meaningful name
- Requires a manageable parameter list
- Improves the parent flow
- Can be tested or reasoned about independently
- Does not obscure important sequencing
Do not extract every few lines mechanically.
Parameter Object Review
A parameter object may help when:
- Many related values travel together
- Parameters are frequently misordered
- Optional values are growing
- The values represent a domain concept
It may hurt when:
- It hides required inputs
- It becomes an unstructured property bag
- It permits invalid combinations
- It is introduced only to satisfy a style rule
Refactoring Sequenceprotected
A safe sequence often looks like:
- Define the objective.
- Document public behavior.
- Add characterization tests.
- Establish a performance baseline.
- Isolate one responsibility.
- Run tests.
- Review the diff.
- Commit the step.
- Repeat for the next responsibility.
- Remove verified duplication.
- Update documentation.
- Run full validation.
- Compare behavior and performance.
- Deploy incrementally.
- Monitor.
Avoid combining every structural improvement into one change.
Pull-Request Strategyprotected
A large refactoring may be divided into:
PR 1 — Tests and Baseline
- Characterization tests
- Performance baseline
- No behavior changes
PR 2 — Naming and Local Extraction
- Internal names
- Small pure functions
- No public changes
PR 3 — Dependency Isolation
- Introduce interfaces or adapters
- Preserve existing construction through compatibility paths
PR 4 — Responsibility Separation
- Move logic into cohesive components
- Preserve public facade
PR 5 — Verified Dead-Code Removal
- Remove code only after usage validation
PR 6 — Intentional Behavior Improvements
- Treat as a separate feature or bug-fix change
- Update contracts and tests explicitly
Deployment Considerationsprotected
Even behavior-preserving refactoring may require careful deployment when it changes:
- Dependency wiring
- Configuration
- Package versions
- Service registration
- Database access patterns
- Resource usage
- Startup behavior
- Logging
- Deployment artifacts
Use:
- Feature flags where appropriate
- Canary deployments
- Side-by-side comparison
- Error-rate monitoring
- Performance monitoring
- Rollback thresholds
- Version compatibility checks
Rollback Planprotected
A rollback plan should identify:
- Previous version
- Reversible configuration
- Database compatibility
- Package compatibility
- Event compatibility
- Deployment order
- Data written by the new version
- Monitoring triggers
- Responsible owner
A code rollback may be insufficient if the refactoring changed persistent data or external contracts.
Refactoring Metricsprotected
Teams may track:
- Change failure rate
- Defect rate after refactoring
- Complexity reduction
- Duplication reduction
- Test coverage by behavior
- Build time
- Test execution time
- Review time
- Deployment frequency
- Lead time for future changes
- Onboarding time
- Number of public-contract changes
- Rollbacks
- Flaky tests
- Technical debt closed
Metrics should reflect engineering outcomes, not reward code churn.
Governance Recommendationsprotected
Define:
- Required characterization testing
- Public-contract review
- Security review thresholds
- Maximum pull-request scope
- Required performance validation
- Required rollback planning
- Dead-code removal standards
- Approved AI platforms
- Source-code handling rules
- Human review requirements
- Refactoring ownership
- Architecture review triggers
- Documentation requirements
- Compatibility policy
- Test expectations
- Post-deployment monitoring
Automation Opportunitiesprotected
- Pull-request preparation
- Technical debt programs
- Legacy modernization
- Static-analysis remediation
- Code-quality initiatives
- Framework upgrades
- Testability improvement
- Dependency replacement
- Architecture remediation
- AI-generated code cleanup
- Engineering enablement
- A mature refactoring workflow could: identify candidate hotspots; retrieve code and tests; analyze complexity and duplication; inventory public behavior; generate characterization tests; propose incremental changes; apply one structural step; run tests and analysis; compare behavior and performance; require human approval before continuing.
Pro Tipsprotected
- Define the problem before changing the code.
- Document behavior before changing structure.
- Add characterization tests for legacy code.
- Separate refactoring from bug fixes and features.
- Preserve public interfaces unless change is intentional.
- Review side effects and operation ordering.
- Protect transaction boundaries.
- Preserve security checks.
- Extract pure logic first where practical.
- Introduce dependency injection only where useful.
- Remove duplication only when the code represents the same concept.
- Avoid speculative abstractions.
- Keep pull requests small.
- Run tests after every structural step.
- Compare old and new behavior.
- Measure performance before and after.
- Verify dead code before removal.
- Document intentional differences.
- Maintain rollback points.
- Require human review of AI-generated refactoring.
Common Mistakesprotected
- Mixing Refactoring and Features — a structural change and new behavior in the same pull request make review and rollback difficult.
- Refactoring Without Tests — without tests, behavioral equivalence becomes an assumption.
- Improving the Code and Fixing Every Quirk — some quirks may be relied upon by callers. Document and separate behavior changes.
- Rewriting Too Much at Once — large changes are difficult to review, test, and reverse.
- Over-Abstracting — more interfaces and classes do not automatically improve design.
- Removing Code Based Only on Search Results — dynamic invocation, configuration, reflection, and external callers may not appear in ordinary reference searches.
- Changing Error Behavior Accidentally — new wrappers, guard clauses, or asynchronous boundaries may alter exception type and timing.
- Changing Execution Order — reordering persistence, logging, notifications, or audit operations may alter observable behavior.
- Ignoring Performance — a structurally cleaner implementation can introduce repeated database or network calls.
- Ignoring Concurrency — moving state or changing asynchronous execution may create races.
- Using Coverage as Proof — tests may execute code without verifying behavior.
- Accepting AI-Generated Architecture Automatically — AI may introduce unnecessary patterns or misunderstand framework conventions.
- Renaming Public Interfaces Without Migration — consumer impact must be evaluated.
- Changing Formatting Across the Entire Repository — large formatting diffs hide meaningful structural changes.
Security Considerationsprotected
- Before providing code to an AI system: remove active credentials.
- Before providing code to an AI system: remove private keys.
- Before providing code to an AI system: remove connection strings containing secrets.
- Before providing code to an AI system: remove production tokens.
- Before providing code to an AI system: sanitize customer data.
- Before providing code to an AI system: remove regulated data.
- Before providing code to an AI system: confirm the AI platform is approved.
- Before providing code to an AI system: confirm code-retention policies.
- Before providing code to an AI system: follow source-code classification requirements.
- During refactoring: preserve authentication order.
- During refactoring: preserve authorization checks.
- During refactoring: preserve tenant filtering.
- During refactoring: preserve validation.
- During refactoring: preserve secure defaults.
- During refactoring: preserve secret-handling behavior.
- During refactoring: preserve audit events.
- During refactoring: avoid logging sensitive values.
- During refactoring: review new dependency boundaries.
- During refactoring: apply least privilege.
Related Blueprints
⚠ Normalization Warnings — 13 for review
- RESTRUCTURE: 'Primary Prompt' and 'Follow-Up Prompts' (two source H1s plus 13 H2 subsections) combined into one prompt_pack tool with 14 prompts; 'when' guidance lines are editorial additions, prompt text verbatim.
- CLASSIFICATION TO CONFIRM: 'Prerequisites' classified as a checklist TOOL (gather-before-start items are completable, matching PS-006 golden precedent). Alternative: body/prose.
- CLASSIFICATION TO CONFIRM: 'Testing Recommendations' classified as a checklist TOOL named 'Refactoring Test Plan' (each of the eight test types with its instruction is a verifiable pass). Display name changed from source heading — confirm. Alternative: body/reference.
- CLASSIFICATION TO CONFIRM: 'Before-and-After Comparison' modeled as a matrix TOOL from the source table (columns Dimension/Before/After). The doc's example rows are the order-processing case; treated as example_rows with skeleton_rows:0. Alternative: body/example. Confirm it is a reusable tool vs. a one-off illustration.
- CLASSIFICATION TO CONFIRM: 'Test Matrix Example' modeled as a matrix TOOL (columns Test Area/Before/After/Expected) since it is a repeatable comparison instrument. Named 'Test Matrix'. Alternative: body/example. No rubric stated in doc; rubric left empty.
- CLASSIFICATION TO CONFIRM: 'Refactoring Categories', 'Behavior Inventory', and 'Refactoring Risk Model' classified as body/reference (consulted taxonomies/models). 'Behavior Inventory' has fill-in intent ('document...') but is a consultation model of what to capture, not a completable artifact — chose reference per the conservative rule; confirm.
- GROUPING: The many single-topic review sections ('Public Contract Review', 'Side-Effect Review', 'Transaction Review', 'Concurrency Review', 'Error-Behavior Review', 'Security Review During Refactoring', 'Performance Review', 'Compatibility Review', 'Dead Code Removal', 'Abstraction Review', 'Naming Improvements', 'Function Extraction', 'Parameter Object Review') grouped under a synthesized 'Review Checkpoints' body/group to avoid a flat 40+ section list. Group title is editorial.
- GROUPING: 'Characterization Testing', 'Golden Master Testing', and 'Differential Testing' grouped under a synthesized 'Testing Strategies' body/group. Group title is editorial. Differential Testing content is distinct from the differential Test Matrix tool.
- PLAYBOOK: 'Common Mistakes' and 'Security Considerations' rendered as playbook flat lists (subsections were short one-line prose, unlike PS-006's code-rich versions). Section-heading context prepended to items where needed for standalone clarity.
- PLAYBOOK: 'Automation Opportunities' — the numbered 'mature refactoring workflow could' sequence was flattened into a single trailing bullet to preserve it within the flat list shape; confirm or split.
- CODE LANGUAGE: Example code is C# (.language: csharp) and Python (error-behavior/naming snippets kept inline within their review prose sections rather than as separate example blocks). Confirm inline code retention.
- STATS: overlay.stats.prompts = 14 (1 primary + 13 follow-ups). deliverables = 6 tools. No Quick Wins or Roadmap sections in source; playbook.quick_wins and roadmap intentionally empty.
- RELATED: 'Refactoring Metrics' and 'Governance Recommendations' kept as body/prose (consultative operational guidance, not completable tools). Confirm metrics list is not intended as a matrix.
SEO Block
- Title tag: Refactor Code Safely with AI | ABME (35 chars)
- Meta: Refactor legacy code safely with AI: define the behavior that must stay stable, sequence small verifiable steps, and prove equivalence before you ship. (151 chars)
- Schema: HowTo · noindex: false
- Related: ai-001, ai-002, ai-003, ai-004, ai-006, ai-007, ai-008, ai-009, ai-010, ps-001, ps-004, ps-005, ps-007, ps-008, ps-009
- Keywords: safe code refactoring, behavior-preserving refactoring, characterization tests, differential testing, ai code refactoring, legacy code refactoring, refactoring vs rewriting, golden master testing, incremental refactoring plan, public contract review, refactoring risk assessment
