Rules Make AI Refactoring Convention-Aware
Refactoring without rules: the AI rewrites code to be 'cleaner' but 'cleaner' is subjective. The AI might change your naming convention, switch from your error handling pattern to a different one, or restructure files in a way that does not match your project structure. Refactoring with rules: the AI rewrites code to match the current conventions encoded in the rules. Old patterns become new patterns. Inconsistent code becomes consistent. The refactored code: looks like it was written today, by a developer who knows all the current conventions.
The refactoring advantage of rules: when refactoring legacy code, the rules represent the target state. Code written 2 years ago with old conventions: refactored to match today's conventions automatically. Without rules: the AI might refactor toward a different set of conventions than the team currently uses, creating a third style (old, current, and AI-imagined). AI rule: 'Rules are the specification for what refactored code should look like. The AI transforms old-pattern code into new-pattern code as defined by the rules.'
When to use AI refactoring: updating code to match new conventions after a rule change, modernizing legacy code that is being modified (the boy scout rule), extracting patterns that should be consistent (all error handlers follow the same pattern), and migrating between frameworks or libraries (Express to Fastify, class components to functional).
Safe Refactoring: The Verify-Then-Commit Workflow
The cardinal rule of refactoring: behavior must not change. The code should do exactly the same thing before and after refactoring — just with better structure, naming, or patterns. AI refactoring risk: the AI might subtly change behavior while changing structure. A renamed variable that was actually used as a key in an object. An extracted function that changes the execution order. A pattern change that alters error propagation.
The verify-then-commit workflow: (1) Before refactoring: run all tests. They must pass. If they do not: fix them first. (2) Refactor one small unit at a time (one function, one component, one file). Not the entire codebase at once. (3) After each refactoring unit: run all tests again. They must still pass. If they fail: the refactoring changed behavior. Revert and try again with a more conservative approach. (4) Commit after each verified refactoring unit. Small, verified commits: easy to revert if problems emerge later.
AI rule: 'Refactor in small, testable units. One function at a time, not one module at a time. Run tests after each unit. Commit after each passing test run. Never refactor more code than you can verify in one test run. The smaller the refactoring unit: the easier it is to detect and revert behavioral changes.'
The AI refactors a function: renames a variable, extracts a helper, and changes the error handling pattern. The function looks cleaner. But: the extracted helper changed the execution order. An async operation that used to run after a validation check: now runs before it. No test catches the change because the test only checks the happy path. The bug: appears in production when invalid input triggers the race condition. Rule: run ALL tests after every refactoring unit, not just the tests for the refactored file.
Multi-File Refactoring Techniques
Rename refactoring: renaming a function, variable, or type that is used across multiple files. AI approach: ask the AI to rename with all references. Verify: search the codebase for the old name (it should not exist after refactoring). Risk: the AI might miss references in strings, comments, or dynamic access. AI rule: 'After a rename refactoring: grep the codebase for the old name. If found: the rename is incomplete. Fix the remaining references before committing.'
Extract pattern refactoring: multiple files have the same pattern (duplicated error handling, repeated data fetching logic). AI approach: extract the pattern into a shared function, then update each file to use the shared version. Verify: each file still passes its tests. The shared function: has its own tests. AI rule: 'Extract one pattern at a time. Create the shared function with tests first. Then update each consumer file individually, testing after each update.'
Migration refactoring: changing from one library or pattern to another across the codebase (class components to functional, callbacks to async/await, one ORM to another). AI approach: migrate one file at a time, guided by rules that define the target pattern. AI rule: 'Migration refactoring: update the rules to reflect the target pattern before starting. The AI then refactors each file toward the target. Process: update rules → refactor file → test → commit → repeat. The rules file is the migration specification.'
You want to refactor from callbacks to async/await. If you refactor first and update rules after: the AI generates callback-style code in other files while you are mid-migration (because the rules still say callbacks). The codebase diverges further. If you update the rules first ('Use async/await for all async operations. No callbacks.'): the AI generates async/await in all new code while you refactor existing code. The codebase converges toward the target from both directions — refactored old code AND new AI-generated code.
Common AI Refactoring Pitfalls
Pitfall 1 — Scope creep: you ask the AI to refactor error handling in one function. The AI also: renames variables, reorganizes imports, and adds type annotations. These extra changes: make the diff harder to review, increase the risk of unintended behavioral changes, and mix refactoring types in one commit. Fix: explicitly tell the AI 'only change the error handling pattern. Do not modify naming, imports, or types.' AI rule: 'One refactoring type per pass. Error handling in pass 1. Naming in pass 2. Imports in pass 3. Each pass: independently testable and reviewable.'
Pitfall 2 — Refactoring untested code: if the code has no tests: you cannot verify that refactoring preserved behavior. The AI refactors confidently. The behavior changes subtly. No test catches it. The bug appears in production days later. Fix: write tests for the current behavior before refactoring. Then refactor. Then verify tests still pass. AI rule: 'Never refactor untested code with AI. First: add tests that capture the current behavior (even if the current behavior is imperfect). Then: refactor with confidence that the tests will catch behavioral changes.'
Pitfall 3 — Big-bang refactoring: refactoring an entire module or service in one pass. The diff: 500+ lines across 20 files. Review: impossible to verify that every change preserved behavior. If a bug emerges: nearly impossible to identify which change caused it. Fix: incremental refactoring. One file, one function, one commit at a time. AI rule: 'If the refactoring diff exceeds 100 lines: it is too large. Break it into smaller passes. Each pass: independently testable, reviewable, and revertible.'
Refactoring untested code: like performing surgery without monitoring the patient's vital signs. You make changes. You think the patient is fine. But without monitoring (tests): you do not know if you cut something important. Before refactoring untested code: write tests that capture the current behavior. These tests do not need to be comprehensive — they need to verify the function produces the same output for the same input. Then refactor. If the tests still pass: behavior is preserved.
Refactoring with AI Summary
Summary of the AI-assisted refactoring workflow.
- Rules = target specification: update rules to reflect the target pattern before refactoring
- Verify-then-commit: run tests before, refactor one unit, run tests after, commit. Repeat
- Small units: one function, one component, one file at a time. Never the entire codebase
- Rename: grep for old name after refactoring. Incomplete renames cause runtime errors
- Extract pattern: create shared function with tests first. Update consumers individually
- Migration: update rules → refactor file → test → commit → repeat. Rules are the spec
- Scope creep: one refactoring type per pass. Do not mix naming, error handling, and imports
- Untested code: add behavior tests BEFORE refactoring. Never refactor without test coverage