Refactoring Patterns
Safe refactoring techniques for autonomous agents — extract method/class, rename with propagation, dead code removal, simplifying conditionals, reducing duplication, maintaining backward compatibility, and refactoring in small verified steps.
Refactoring Patterns
You are an autonomous agent that refactors code safely and incrementally. You never perform large, sweeping rewrites. Instead, you apply small, well-understood transformations — each one verified before moving to the next. The code works before you start, and it works after every single step.
Philosophy
Refactoring is changing the structure of code without changing its behavior. This distinction is critical. If you change behavior during a refactoring step, you have combined two different kinds of work — restructuring and feature change — making both harder to verify and debug. Keep them separate.
The goal of refactoring is to make code easier to understand, modify, and extend. It is not about making code "look nicer" according to abstract principles. Every refactoring should have a practical motivation: the current structure makes it hard to add a feature, fix a bug, or understand what the code does.
Core Techniques
Extract Method
When a function does too much, or when a block of code has a clear purpose that deserves a name:
- Identify the block to extract. It should be a cohesive unit — not an arbitrary slice of lines.
- Identify all variables the block reads (parameters) and all variables it modifies (return values).
- Create a new function with a descriptive name, taking the reads as parameters and returning the writes.
- Replace the original block with a call to the new function.
- Run the tests to verify behavior is preserved.
When to extract: A comment explaining what the next 10 lines do is a strong signal — the comment is the method name waiting to happen.
Extract Class
When a class has multiple responsibilities or when a group of related fields and methods can stand alone:
- Identify the fields and methods that belong together around a single concept.
- Create a new class containing those fields and methods.
- Replace the original fields with a reference to the new class.
- Update all call sites to go through the new class.
- Run the tests.
Rename with Propagation
When a name does not communicate its purpose:
- Choose a name that accurately describes what the thing is or does. Invest time in this — naming is one of the most impactful refactors.
- Use your IDE or search tools to find every reference across the codebase — including strings, comments, documentation, configuration files, and API contracts.
- Rename all occurrences consistently.
- Pay special attention to serialized forms: JSON field names, database columns, URL paths, and environment variables. These may require migration strategies rather than simple renames.
- Run the tests and check for runtime errors in dynamic languages where some references may not be caught statically.
Dead Code Removal
Code that is never executed is worse than no code — it creates confusion, must be maintained, and may mislead readers:
- Use search tools to verify the code is truly unreachable: no callers, no references, no reflection-based access, no conditional feature flags that might enable it.
- Check version control to understand why the code exists. It may have been disabled temporarily or kept for reference.
- Remove the dead code. Do not comment it out — it lives in version control history if anyone needs it later.
- Remove any supporting code (helper functions, imports, test fixtures) that was only used by the dead code.
- Run the tests.
Simplifying Conditionals
Complex conditional logic is one of the most common sources of bugs:
- Decompose conditional. Extract the condition and each branch into named methods:
if isEligibleForDiscount(customer)is clearer thanif customer.age > 65 or customer.years_active > 10 or customer.tier == 'gold'. - Replace nested conditionals with guard clauses. Handle edge cases and error conditions at the top of the function with early returns, leaving the main logic un-indented.
- Consolidate duplicate conditionals. If the same condition appears in multiple branches, combine them.
- Replace conditional with polymorphism. When a switch statement or chain of if/else dispatches on a type to select behavior, consider using method overriding or a strategy pattern instead.
Reducing Duplication
- Extract shared logic into a function when two or more places contain the same logic. But be cautious: two blocks of code that look similar today may evolve in different directions. Only extract if they represent the same concept, not just the same characters.
- Use the Rule of Three. Do not extract on the first duplication. Wait until you see the same pattern three times. By then, the stable shape is clear.
- Parameterize differences. When two functions differ only in a few values, extract a common function that takes those values as parameters.
Maintaining Backward Compatibility
When refactoring public APIs, exported functions, or database schemas:
- Deprecate before removing. Add the new interface alongside the old one. Mark the old one as deprecated with a clear migration path.
- Use adapter patterns. The old function can call the new one internally, preserving the contract while centralizing logic.
- Version APIs. If a breaking change is necessary, introduce a new version rather than modifying the existing endpoint.
- Migrate data incrementally. Database schema changes should be applied in steps: add new column, backfill data, update code to use new column, then drop old column in a later migration.
Refactoring in Small, Verified Steps
This is the most important technique. Every other technique assumes this discipline:
- Make one small structural change.
- Run the tests. If they pass, continue. If they fail, revert and try a smaller step.
- Commit the passing state so you have a rollback point.
- Repeat.
This approach gives you confidence at every step. If something goes wrong, the last working state is one small step behind you, not an hour of tangled changes away.
Best Practices
- Have tests before refactoring. If the code lacks tests, write characterization tests first — tests that capture the current behavior, right or wrong. These protect you during restructuring.
- Do not mix refactoring with behavior changes. Alternate between refactoring commits and feature/fix commits. Never combine both in one commit.
- Communicate intent through commit messages. "Extract parseAddress from processOrder" tells reviewers this is a pure structural change with no behavior modification.
- Preserve formatting conventions. Match the project's existing style. A refactoring that also reformats the file creates a noisy diff that obscures the structural change.
- Measure twice, cut once. Before starting a large refactoring, understand the full scope. How many files are affected? Are there downstream consumers? Is this a public API?
Anti-Patterns
- Big-bang rewrites. Rewriting an entire module at once without incremental verification. This is the most common way to introduce regressions during refactoring.
- Refactoring without tests. Changing structure without a way to verify behavior is preserved is not refactoring — it is gambling.
- Premature abstraction. Extracting interfaces, base classes, and factories before you have evidence that the abstraction is needed. This adds complexity without benefit.
- Renaming to satisfy convention without adding clarity. Changing
getUsertofetchUserbecause "fetch implies async" is only worthwhile if the distinction matters in the codebase. Do not rename for its own sake. - Refactoring code you do not understand. If you cannot explain what the code does, you cannot safely restructure it. Read and understand first.
- Changing signatures without updating all callers. In dynamic languages or across package boundaries, some callers may not be found by simple text search. Check thoroughly.
- Leaving orphaned code. After extracting or moving code, clean up unused imports, dead variables, and now-empty files. The refactoring is not complete until the residue is gone.
Related Skills
Abstraction Control
Avoiding over-abstraction and unnecessary complexity by choosing the simplest solution that solves the actual problem
Accessibility Implementation
Making web content accessible through ARIA attributes, semantic HTML, keyboard navigation, screen reader support, color contrast, focus management, and WCAG compliance.
API Design Patterns
Designing and implementing clean APIs with proper REST conventions, pagination, versioning, authentication, and backward compatibility.
API Integration
Integrating with external APIs effectively — reading API docs, authentication patterns, error handling, rate limiting, retry with backoff, response validation, SDK vs raw HTTP decisions, and API versioning.
Assumption Validation
Detecting and validating assumptions before acting on them to prevent cascading errors from wrong guesses
Authentication Implementation
Implementing authentication flows correctly including OAuth 2.0/OIDC, JWT handling, session management, password hashing, MFA, token refresh, and CSRF protection.