refactoring-patterns
Use this skill when refactoring code to improve readability, reduce duplication, or simplify complex logic. Triggers on extract method, inline variable, replace conditional with polymorphism, introduce parameter object, decompose conditional, replace magic numbers, pull up/push down method, and any task requiring systematic code transformation without changing behavior.
engineering refactoringpatternscode-qualityclean-codetransformationWhat is refactoring-patterns?
Use this skill when refactoring code to improve readability, reduce duplication, or simplify complex logic. Triggers on extract method, inline variable, replace conditional with polymorphism, introduce parameter object, decompose conditional, replace magic numbers, pull up/push down method, and any task requiring systematic code transformation without changing behavior.
refactoring-patterns
refactoring-patterns is a production-ready AI agent skill for claude-code, gemini-cli, openai-codex. Refactoring code to improve readability, reduce duplication, or simplify complex logic.
Quick Facts
| Field | Value |
|---|---|
| Category | engineering |
| Version | 0.1.0 |
| Platforms | claude-code, gemini-cli, openai-codex |
| License | MIT |
How to Install
- Make sure you have Node.js installed on your machine.
- Run the following command in your terminal:
npx skills add AbsolutelySkilled/AbsolutelySkilled --skill refactoring-patterns- The refactoring-patterns skill is now available in your AI coding agent (Claude Code, Gemini CLI, OpenAI Codex, etc.).
Overview
Refactoring is the discipline of restructuring existing code without changing its observable behavior. The goal is to make code easier to understand, cheaper to modify, and less likely to harbor bugs. Each refactoring move is a named, repeatable transformation - applying them in small, tested steps keeps the codebase safe. This skill gives an agent the vocabulary and judgment to recognize structural problems, choose the right refactoring move, and execute it correctly.
Tags
refactoring patterns code-quality clean-code transformation
Platforms
- claude-code
- gemini-cli
- openai-codex
Related Skills
Pair refactoring-patterns with these complementary skills:
Frequently Asked Questions
What is refactoring-patterns?
Use this skill when refactoring code to improve readability, reduce duplication, or simplify complex logic. Triggers on extract method, inline variable, replace conditional with polymorphism, introduce parameter object, decompose conditional, replace magic numbers, pull up/push down method, and any task requiring systematic code transformation without changing behavior.
How do I install refactoring-patterns?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill refactoring-patterns in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support refactoring-patterns?
This skill works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.
Maintainers
Generated from AbsolutelySkilled
SKILL.md
Refactoring Patterns
Refactoring is the discipline of restructuring existing code without changing its observable behavior. The goal is to make code easier to understand, cheaper to modify, and less likely to harbor bugs. Each refactoring move is a named, repeatable transformation - applying them in small, tested steps keeps the codebase safe. This skill gives an agent the vocabulary and judgment to recognize structural problems, choose the right refactoring move, and execute it correctly.
When to use this skill
Trigger this skill when the user:
- Asks to extract a method, function, or block into a named helper
- Has a long
if/elsechain orswitchthat grows with every new case - Wants to simplify a function with too many parameters
- Asks to replace magic numbers or string literals with named constants
- Wants to break apart a large class that does too many things
- Has complex conditional logic that is hard to read at a glance
- Asks for "systematic" code improvement without changing behavior
- Wants to eliminate duplication across multiple files or classes
Do NOT trigger this skill for:
- Performance optimization - refactoring targets readability, not speed
- Architecture decisions that change system boundaries (use clean-architecture instead)
Key principles
Small steps with tests - Apply one refactoring at a time and verify tests pass after each step. A failing test means the refactoring changed behavior.
Preserve observable behavior - Callers must not notice the change. Return values, side effects, and thrown errors must remain identical.
One refactor at a time - Don't mix Extract Method with Rename Variable in one commit. Each commit should contain exactly one named refactoring move.
Refactor before adding features - Fowler's rule: make the change easy, then make the easy change. Restructure first, add the feature second.
Code smells signal refactoring need - Smells like long functions, duplicated code, and large parameter lists are symptoms pointing to the correct refactoring move. See
references/code-smells.mdfor the full catalog.
Core concepts
Code smells taxonomy
Code smells are categories of structural problems, each suggesting specific moves:
| Smell | Signal | Primary Refactoring |
|---|---|---|
| Long method | Function over 20 lines, section comments | Extract Method |
| Large class | Class does many unrelated things | Extract Class |
| Long parameter list | 4+ parameters | Introduce Parameter Object |
| Duplicated code | Same logic in 2+ places | Extract Method / Pull Up Method |
| Switch statements | switch/if-else grows with each case |
Replace Conditional with Polymorphism |
| Primitive obsession | Strings/numbers standing in for domain concepts | Replace with Value Object |
| Feature envy | Method uses another class's data more than its own | Move Method |
| Temporary field | Instance variable only set in some code paths | Extract Class |
| Data clumps | Same group of variables travel together | Introduce Parameter Object |
| Speculative generality | Abstractions with no second use case | Collapse Hierarchy / Remove |
Refactoring safety net
Never refactor without tests. If tests don't exist, write characterization tests first - tests that capture the current behavior before you change anything. The test suite is the contract that proves the refactoring preserved behavior.
Common tasks
Extract method
Apply when a function contains a section that can be given a meaningful name.
Before:
function printOrderSummary(order: Order): void {
// print header
console.log("=".repeat(40));
console.log(`Order #${order.id} - ${order.customer.name}`);
console.log(`Date: ${order.createdAt.toLocaleDateString()}`);
console.log("=".repeat(40));
// print line items
for (const item of order.items) {
const lineTotal = item.price * item.quantity;
console.log(` ${item.name} x${item.quantity} @ $${item.price} = $${lineTotal}`);
}
// print totals
const subtotal = order.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
const tax = subtotal * 0.08;
console.log(`Subtotal: $${subtotal.toFixed(2)}`);
console.log(`Tax (8%): $${tax.toFixed(2)}`);
console.log(`Total: $${(subtotal + tax).toFixed(2)}`);
}After:
function printOrderSummary(order: Order): void {
printOrderHeader(order);
printLineItems(order.items);
printOrderTotals(order.items);
}
function printOrderHeader(order: Order): void {
console.log("=".repeat(40));
console.log(`Order #${order.id} - ${order.customer.name}`);
console.log(`Date: ${order.createdAt.toLocaleDateString()}`);
console.log("=".repeat(40));
}
function printLineItems(items: OrderItem[]): void {
for (const item of items) {
const lineTotal = item.price * item.quantity;
console.log(` ${item.name} x${item.quantity} @ $${item.price} = $${lineTotal}`);
}
}
function printOrderTotals(items: OrderItem[]): void {
const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
const tax = subtotal * 0.08;
console.log(`Subtotal: $${subtotal.toFixed(2)}`);
console.log(`Tax (8%): $${tax.toFixed(2)}`);
console.log(`Total: $${(subtotal + tax).toFixed(2)}`);
}Replace conditional with polymorphism
Apply when a switch or if/else dispatches behavior by type, and new types keep
getting added. Each new case is a modification to existing code - a violation of OCP.
Before:
function calculateShipping(order: Order): number {
switch (order.shippingMethod) {
case "standard": return order.weight * 0.5;
case "express": return order.weight * 1.5 + 5;
case "overnight": return order.weight * 3.0 + 15;
default: throw new Error(`Unknown shipping method: ${order.shippingMethod}`);
}
}After:
interface ShippingStrategy {
calculate(order: Order): number;
}
class StandardShipping implements ShippingStrategy {
calculate(order: Order): number { return order.weight * 0.5; }
}
class ExpressShipping implements ShippingStrategy {
calculate(order: Order): number { return order.weight * 1.5 + 5; }
}
class OvernightShipping implements ShippingStrategy {
calculate(order: Order): number { return order.weight * 3.0 + 15; }
}
// Adding a new method = new class only, no modification to existing code
function calculateShipping(order: Order, strategy: ShippingStrategy): number {
return strategy.calculate(order);
}Introduce parameter object
Apply when a function receives 4+ related parameters that travel together.
Before:
function createReport(
title: string,
startDate: Date,
endDate: Date,
authorId: string,
format: "pdf" | "csv",
includeCharts: boolean
): Report { ... }After:
interface ReportOptions {
title: string;
dateRange: { start: Date; end: Date };
authorId: string;
format: "pdf" | "csv";
includeCharts: boolean;
}
function createReport(options: ReportOptions): Report { ... }Replace magic numbers with named constants
Apply when numeric or string literals appear in logic without explanation.
Before:
function isEligibleForDiscount(user: User): boolean {
return user.totalPurchases > 500 && user.accountAgeDays > 90;
}
function calculateLateFee(daysLate: number): number {
return daysLate * 2.5;
}After:
const DISCOUNT_PURCHASE_THRESHOLD = 500;
const DISCOUNT_ACCOUNT_AGE_DAYS = 90;
const LATE_FEE_PER_DAY = 2.5;
function isEligibleForDiscount(user: User): boolean {
return (
user.totalPurchases > DISCOUNT_PURCHASE_THRESHOLD &&
user.accountAgeDays > DISCOUNT_ACCOUNT_AGE_DAYS
);
}
function calculateLateFee(daysLate: number): number {
return daysLate * LATE_FEE_PER_DAY;
}Decompose conditional
Apply when a complex boolean expression obscures what condition is actually being tested. Extract each clause into a named predicate.
Before:
if (
user.subscription === "premium" &&
user.accountAgeDays > 30 &&
!user.isSuspended &&
(user.region === "US" || user.region === "CA")
) {
grantEarlyAccess(user);
}After:
function isPremiumUser(user: User): boolean {
return user.subscription === "premium";
}
function isEstablishedAccount(user: User): boolean {
return user.accountAgeDays > 30 && !user.isSuspended;
}
function isEligibleRegion(user: User): boolean {
return user.region === "US" || user.region === "CA";
}
if (isPremiumUser(user) && isEstablishedAccount(user) && isEligibleRegion(user)) {
grantEarlyAccess(user);
}Extract class
Apply when a class has a cluster of fields and methods that form a distinct responsibility. The test: can you describe the class in one sentence without "and"?
Before:
class User {
id: string;
name: string;
email: string;
street: string;
city: string;
state: string;
zip: string;
getFullAddress(): string {
return `${this.street}, ${this.city}, ${this.state} ${this.zip}`;
}
isValidAddress(): boolean {
return Boolean(this.street && this.city && this.state && this.zip);
}
}After:
class Address {
constructor(
public street: string,
public city: string,
public state: string,
public zip: string
) {}
toString(): string {
return `${this.street}, ${this.city}, ${this.state} ${this.zip}`;
}
isValid(): boolean {
return Boolean(this.street && this.city && this.state && this.zip);
}
}
class User {
id: string;
name: string;
email: string;
address: Address;
}Replace temp with query
Apply when a local variable stores a computed value that could be a method call. Eliminates the variable and makes the intent reusable.
Before:
function applyDiscount(order: Order): number {
const basePrice = order.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
const discount = basePrice > 100 ? basePrice * 0.1 : 0;
return basePrice - discount;
}After:
function basePrice(order: Order): number {
return order.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
}
function discount(order: Order): number {
return basePrice(order) > 100 ? basePrice(order) * 0.1 : 0;
}
function applyDiscount(order: Order): number {
return basePrice(order) - discount(order);
}Anti-patterns / common mistakes
| Mistake | Why it's wrong | What to do instead |
|---|---|---|
| Refactoring without tests | No proof that behavior was preserved; bugs introduced invisibly | Write characterization tests before the first change |
| Mixing refactoring with features | Makes diffs unreadable and bugs hard to attribute | Separate commits: one for refactoring, one for the feature |
| Over-extracting tiny functions | Dozens of 2-line functions destroy navigability | Extract when a block has a clear name and independent purpose |
| Applying polymorphism to stable switches | Strategy pattern adds classes for no gain when the switch never grows | Only replace with polymorphism when new cases are expected |
| Renaming everything at once | Mass renames hide structural changes and cause merge conflicts | Rename one thing per commit; use IDE rename-refactor to stay safe |
Gotchas
Refactoring without a characterization test safety net - When tests don't exist, write characterization tests that capture current behavior before touching anything. A refactoring without a test that would catch a behavioral change is a gamble, not a refactoring.
Extract Method applied to code that shares mutable state - Extracting a method that reads or writes shared mutable state (class fields, closures) can introduce subtle bugs when the extracted function is called in a different order. Verify that the extracted method is free of hidden state coupling before extracting.
Replace Conditional with Polymorphism on a switch that never grows - The Strategy pattern adds classes, indirection, and cognitive overhead. Only apply it when new cases are expected. A switch with three stable cases is often clearer than three classes plus a factory.
Mixing refactoring and feature work in one commit - Combined commits make it impossible to bisect a regression to either the refactoring or the feature. Keep them separate. If you discover a needed refactoring while implementing a feature, commit the refactoring first on its own.
Rename refactoring done with find-and-replace instead of IDE tooling - Manual find-and-replace misses dynamic usages, string references, and cross-file cases. Always use the IDE's rename refactoring which understands symbol scope, and review the diff carefully before committing.
References
For detailed content on specific topics, read the relevant file from references/:
references/code-smells.md- Catalog of 15+ smells with detection criteria and recommended refactoring for each
Only load the reference file when the task requires identifying a specific smell or choosing between multiple refactoring moves.
References
code-smells.md
Code Smells Catalog
A code smell is a surface indication of a deeper structural problem. Smells signal that refactoring might improve the design - always apply judgment. Use this catalog to name a smell precisely and select the right refactoring move.
Function-level smells
1. Long Method
- Detection: Function exceeds 20 lines, or requires section comments to divide distinct operations, or mixes multiple levels of abstraction
- Refactoring: Extract Method - give each section a name that states intent
- Signal: "This function first validates, then calculates, then saves..."
2. Too Many Parameters
- Detection: Function takes 4+ arguments; callers pass confusing positional values
- Refactoring: Introduce Parameter Object or Preserve Whole Object
- Threshold: 0 args (ideal), 1 arg (good), 2 args (acceptable), 3 args (justify), 4+ args (refactor)
3. Flag Argument
- Detection: A boolean parameter that causes the function to behave in two fundamentally different ways
- Refactoring: Split into two separate functions with descriptive names
// Smell
render(data, true); // what does true mean?
// Fixed
renderForPrint(data);
renderForScreen(data);4. Dead Code
- Detection: Unreachable branches, unused variables, functions that are never called, commented-out blocks
- Refactoring: Delete it. Version control preserves history.
- Note: IDE "find usages" confirms a function has no callers before deleting
5. Temp Variable Used Once
- Detection: A local variable is assigned and then used exactly once immediately after, adding no clarity
- Refactoring: Replace Temp with Query - inline the expression or extract a named function
6. Side Effects Hidden Behind Name
- Detection: Function name says
checkPasswordbut it also resets a session - Refactoring: Either rename to reflect all effects (
checkPasswordAndResetSession) or extract the side effect into a separate function
Class-level smells
7. Large Class (God Object)
- Detection: Class has many instance variables, many methods, or describes unrelated concerns. Can't describe it in one sentence without "and".
- Refactoring: Extract Class - group cohesive fields and methods into a new class
- Test: Count instance variables. More than 7-10 is a strong signal.
8. Feature Envy
- Detection: A method accesses data or methods from another class more than its own. The method seems to "want" to live somewhere else.
- Refactoring: Move Method to the class whose data it uses most
// Smell: OrderFormatter envies Order's data
class OrderFormatter {
format(order: Order): string {
return `${order.customer.name}: ${order.calculateTotal()}`;
}
}
// Fixed: Move to Order
class Order {
toSummaryString(): string {
return `${this.customer.name}: ${this.calculateTotal()}`;
}
}9. Inappropriate Intimacy
- Detection: Two classes access each other's private fields directly or call each other's internal methods extensively; they are tightly coupled
- Refactoring: Move fields/methods to reduce coupling; introduce a mediator or extract a shared abstraction
10. Refused Bequest
- Detection: A subclass inherits methods or fields from its parent but doesn't use them, overrides them to do nothing, or throws "not supported" exceptions
- Refactoring: Replace inheritance with composition; use Push Down Method to move the unused methods to a sibling that actually needs them
11. Temporary Field
- Detection: An instance variable is only set and used in one code path; in other paths it is null or meaningless
- Refactoring: Extract Class to hold the temporary field and its related methods, or convert to a local variable passed as a parameter
Structural smells
12. Duplicated Code
- Detection: Same or structurally similar logic in two or more places. Changes to one copy must be mirrored in others.
- Refactoring:
- Same class: Extract Method
- Sibling classes: Pull Up Method to parent, or extract to shared utility
- Unrelated classes: Extract to a standalone function or service
- Warning: Two occurrences might be coincidental. Three is a pattern worth extracting.
13. Primitive Obsession
- Detection: Domain concepts represented as raw strings, numbers, or booleans
instead of types (e.g.,
"active"status,"USD"currency,"555-1234"phone) - Refactoring: Replace with a Value Object or an enum that encapsulates the validation and behavior
// Smell
function charge(amount: number, currency: string): void { ... }
// Fixed
class Money {
constructor(public readonly amount: number, public readonly currency: Currency) {}
}
function charge(amount: Money): void { ... }14. Data Clumps
- Detection: The same group of variables appears together in multiple function
signatures, method calls, or class fields (e.g.,
startX, startY, endX, endY) - Refactoring: Introduce Parameter Object to bundle the group into a named type
// Smell
function drawRect(x: number, y: number, width: number, height: number): void { ... }
// Fixed
interface Rect { x: number; y: number; width: number; height: number; }
function drawRect(rect: Rect): void { ... }15. Divergent Change
- Detection: One class is frequently modified for several unrelated reasons. Every time a new requirement arrives, this class gets touched.
- Refactoring: Split the class by responsibility - each resulting class has one reason to change (Single Responsibility Principle)
16. Shotgun Surgery
- Detection: One logical change requires small edits scattered across many unrelated classes. Opposite of divergent change.
- Refactoring: Move Method / Move Field to consolidate the logic into one class
17. Speculative Generality
- Detection: Abstract classes, interfaces, or parameters added "just in case" with only one concrete implementation
- Refactoring: Remove the abstraction (Collapse Hierarchy). Add it back when a second concrete use case actually materializes (YAGNI)
18. Switch Statements (Repeated)
- Detection: The same
switch/if-elsechain on a type tag appears in multiple places. Adding a new type requires finding and updating every occurrence. - Refactoring: Replace Conditional with Polymorphism - each case becomes a class
Comment smells
19. Redundant Comment
- Detection: Comment restates exactly what the code already says
i++; // increment i- Refactoring: Delete it. The code is the documentation.
20. Commented-Out Code
- Detection: Blocks of code disabled with comments, often with no explanation
- Refactoring: Delete it. Git history preserves every prior version.
21. Journal Comment
- Detection: Changelog entries embedded in source files
// 2024-01-15 - Added validation
// 2024-02-01 - Fixed null pointer- Refactoring: Delete it. That's what
git logis for.
22. Explanatory Comment for Bad Code
- Detection: A comment that explains why the code is confusing, rather than fixing the code
- Refactoring: Remove the comment and fix the code so it explains itself
Choosing the right refactoring
| Smell | Primary Move | Secondary Move |
|---|---|---|
| Long method | Extract Method | Decompose Conditional |
| Too many parameters | Introduce Parameter Object | Preserve Whole Object |
| Switch on type | Replace Conditional with Polymorphism | - |
| Duplicated code | Extract Method | Pull Up Method |
| Large class | Extract Class | Extract Interface |
| Feature envy | Move Method | Move Field |
| Primitive obsession | Replace with Value Object | Introduce Parameter Object |
| Data clumps | Introduce Parameter Object | Extract Class |
| Magic numbers | Replace Magic Number with Constant | - |
| Temp variable | Replace Temp with Query | - |
Refactoring safety checklist
Before any refactoring move:
- Are there tests covering the code you are about to change? If no, write them first.
- Can you name the smell precisely from this catalog?
- What is the minimal refactoring move that addresses it?
- After the move: do all tests still pass?
- Is the code more readable than before? If not, revert.
Frequently Asked Questions
What is refactoring-patterns?
Use this skill when refactoring code to improve readability, reduce duplication, or simplify complex logic. Triggers on extract method, inline variable, replace conditional with polymorphism, introduce parameter object, decompose conditional, replace magic numbers, pull up/push down method, and any task requiring systematic code transformation without changing behavior.
How do I install refactoring-patterns?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill refactoring-patterns in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support refactoring-patterns?
refactoring-patterns works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.