agentleFS
mehdiozdemirmehdiozdemir/awesome-agent-skills/skills/refactoring-assistant/SKILL.md

refactoring-assistant

Skill2 starsChanged 10 months agoMITView on GitHub

Helps improve code quality through systematic refactoring, applying SOLID principles, DRY patterns, and design patterns. Use when cleaning up legacy code, reducing complexity, eliminating code smells, improving maintainability, or restructuring codebases for better architecture.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

16 rules. About 0 are common, 16 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈16

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use configuration over hardcoding

    line 25
  2. Distinctive:

    Prefer simple solutions over clever ones

    line 29
  3. Distinctive:

    Write readable, self-documenting code

    line 32
  4. Distinctive:

    Don't add functionality until needed

    line 35
  5. Distinctive:

    Check complexity metrics (cyclomatic complexity, LOC)

    line 717
  6. Distinctive:

    Look for repeated patterns

    line 718
  7. Distinctive:

    Write tests before refactoring (if missing)

    line 721
  8. Distinctive:

    Use tests as a safety net

    line 723
  9. Distinctive:

    Commit after each successful change

    line 727
  10. Distinctive:

    Run all tests after each change

    line 731
  11. Distinctive:

    When suggesting refactorings, provide:

    line 791
  12. Distinctive:

    Always refactor with tests as a safety net

    line 835
  13. Distinctive:

    Prefer many small refactorings over big rewrites

    line 836
  14. Distinctive:

    Don't mix refactoring with adding new features

    line 837
  15. Distinctive:

    Consider team readability over clever solutions

    line 840
  16. Distinctive:

    Document significant design decisions

    line 841

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Refactoring Assistant Skill

This skill guides systematic code improvement through proven refactoring techniques. Use this whenever you need to clean up code, reduce technical debt, improve structure, or apply better design patterns.

Core Refactoring Principles

1. SOLID Principles

PrincipleDescriptionViolation Signs
Single ResponsibilityOne class = one reason to changeLarge classes, mixed concerns
Open/ClosedOpen for extension, closed for modificationFrequent edits to existing code
Liskov SubstitutionSubtypes must be substitutableBroken inheritance, type checks
Interface SegregationMany specific interfaces > one generalFat interfaces, unused methods
Dependency InversionDepend on abstractions, not concretionsHard-coded dependencies

2. DRY (Don't Repeat Yourself)

  • Eliminate duplicate code
  • Extract common logic into reusable components
  • Use configuration over hardcoding
  • Single source of truth for data

3. KISS (Keep It Simple, Stupid)

  • Prefer simple solutions over clever ones
  • Avoid premature optimization
  • Reduce unnecessary complexity
  • Write readable, self-documenting code

4. YAGNI (You Ain't Gonna Need It)

  • Don't add functionality until needed
  • Remove unused code
  • Avoid speculative generality
  • Build for today's requirements

Code Smells Catalog

Bloaters

Code that has grown excessively large:

SmellDescriptionRefactoring
Long MethodMethod > 20-30 linesExtract Method
Large ClassClass doing too muchExtract Class
Primitive ObsessionOveruse of primitivesReplace with Value Object
Long Parameter List> 3-4 parametersParameter Object, Builder
Data ClumpsGroups of data appearing togetherExtract Class

Object-Orientation Abusers

Misuse of OOP principles:

SmellDescriptionRefactoring
Switch StatementsComplex conditionalsReplace with Polymorphism
Temporary FieldFields only sometimes usedExtract Class
Refused BequestSubclass not using parent methodsReplace Inheritance with Delegation
Alternative ClassesSimilar classes, different interfacesRename, Merge Classes

Change Preventers

Code that makes changes difficult:

SmellDescriptionRefactoring
Divergent ChangeOne class changed for many reasonsExtract Class
Shotgun SurgeryOne change affects many classesMove Method, Inline Class
Parallel InheritanceSubclass requires parallel subclassMove Method, Move Field

Dispensables

Code that could be removed:

SmellDescriptionRefactoring
CommentsExcessive comments hiding bad codeExtract Method, Rename
Duplicate CodeSame code in multiple placesExtract Method/Class
Dead CodeUnreachable/unused codeDelete it
Speculative GeneralityUnused abstractionsCollapse Hierarchy
Lazy ClassClass doing too littleInline Class

Couplers

Excessive coupling between classes:

SmellDescriptionRefactoring
Feature EnvyMethod uses another class moreMove Method
Inappropriate IntimacyClasses too dependentMove Method, Extract Class
Message Chainsa.b().c().d()Hide Delegate
Middle ManClass only delegatesRemove Middle Man

Refactoring Patterns

Extract Method

Transform long methods into smaller, focused ones.

Before:

def process_order(order):
    # Validate order
    if not order.items:
        raise ValueError("Order must have items")
    if order.total < 0:
        raise ValueError("Invalid total")
    
    # Calculate discounts
    discount = 0
    if order.customer.is_premium:
        discount = order.total * 0.1
    if order.total > 100:
        discount += order.total * 0.05
    
    # Apply tax
    subtotal = order.total - discount
    tax = subtotal * 0.08
    final_total = subtotal + tax
    
    # Send confirmation
    email = f"Order confirmed. Total: ${final_total}"
    send_email(order.customer.email, "Order Confirmation", email)
    
    return final_total

After:

def process_order(order):
    validate_order(order)
    discount = calculate_discount(order)
    final_total = apply_tax(order.total - discount)
    send_order_confirmation(order.customer, final_total)
    return final_total

def validate_order(order):
    if not order.items:
        raise ValueError("Order must have items")
    if order.total < 0:
        raise ValueError("Invalid total")

def calculate_discount(order):
    discount = 0
    if order.customer.is_premium:
        discount = order.total * 0.1
    if order.total > 100:
        discount += order.total * 0.05
    return discount

def apply_tax(subtotal, tax_rate=0.08):
    return subtotal * (1 + tax_rate)

def send_order_confirmation(customer, total):
    email = f"Order confirmed. Total: ${total}"
    send_email(customer.email, "Order Confirmation", email)

Extract Class

Split a class with multiple responsibilities.

Before:

class User:
    def __init__(self, name, email, street, city, zip_code, country):
        self.name = name
        self.email = email
        self.street = street
        self.city = city
        self.zip_code = zip_code
        self.country = country
    
    def get_full_address(self):
        return f"{self.street}, {self.city}, {self.zip_code}, {self.country}"
    
    def validate_address(self):
        return bool(self.street and self.city and self.zip_code)
    
    def format_mailing_label(self):
        return f"{self.name}\n{self.get_full_address()}"

After:

class Address:
    def __init__(self, street, city, zip_code, country):
        self.street = street
        self.city = city
        self.zip_code = zip_code
        self.country = country
    
    def get_full_address(self):
        return f"{self.street}, {self.city}, {self.zip_code}, {self.country}"
    
    def is_valid(self):
        return bool(self.street and self.city and self.zip_code)

class User:
    def __init__(self, name, email, address: Address):
        self.name = name
        self.email = email
        self.address = address
    
    def format_mailing_label(self):
        return f"{self.name}\n{self.address.get_full_address()}"

Replace Conditional with Polymorphism

Replace complex conditionals with inheritance.

Before:

class PaymentProcessor:
    def process_payment(self, payment_type, amount, details):
        if payment_type == "credit_card":
            # Validate card
            if not self._validate_card(details["card_number"]):
                raise ValueError("Invalid card")
            # Charge card
            response = self._charge_card(details["card_number"], amount)
            return response
        
        elif payment_type == "paypal":
            # Authenticate with PayPal
            token = self._get_paypal_token(details["email"])
            # Process PayPal payment
            response = self._paypal_charge(token, amount)
            return response
        
        elif payment_type == "bank_transfer":
            # Validate bank details
            if not self._validate_iban(details["iban"]):
                raise ValueError("Invalid IBAN")
            # Initiate transfer
            response = self._initiate_transfer(details["iban"], amount)
            return response
        
        else:
            raise ValueError(f"Unknown payment type: {payment_type}")

After:

from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def validate(self, details: dict) -> bool:
        pass
    
    @abstractmethod
    def process(self, amount: float, details: dict) -> dict:
        pass

class CreditCardPayment(PaymentMethod):
    def validate(self, details):
        return self._validate_card(details["card_number"])
    
    def process(self, amount, details):
        if not self.validate(details):
            raise ValueError("Invalid card")
        return self._charge_card(details["card_number"], amount)

class PayPalPayment(PaymentMethod):
    def validate(self, details):
        return bool(details.get("email"))
    
    def process(self, amount, details):
        token = self._get_paypal_token(details["email"])
        return self._paypal_charge(token, amount)

class BankTransferPayment(PaymentMethod):
    def validate(self, details):
        return self._validate_iban(details["iban"])
    
    def process(self, amount, details):
        if not self.validate(details):
            raise ValueError("Invalid IBAN")
        return self._initiate_transfer(details["iban"], amount)

class PaymentProcessor:
    def __init__(self):
        self._methods = {
            "credit_card": CreditCardPayment(),
            "paypal": PayPalPayment(),
            "bank_transfer": BankTransferPayment(),
        }
    
    def process_payment(self, payment_type, amount, details):
        method = self._methods.get(payment_type)
        if not method:
            raise ValueError(f"Unknown payment type: {payment_type}")
        return method.process(amount, details)

Introduce Parameter Object

Replace long parameter lists with an object.

Before:

def create_report(
    title,
    author,
    start_date,
    end_date,
    include_charts,
    include_summary,
    page_size,
    orientation,
    department,
    category
):
    # Create report with all these parameters...
    pass

def email_report(
    title,
    author,
    start_date,
    end_date,
    include_charts,
    include_summary,
    recipients
):
    # Email report with overlapping parameters...
    pass

After:

from dataclasses import dataclass
from datetime import date
from typing import Optional

@dataclass
class ReportConfig:
    title: str
    author: str
    start_date: date
    end_date: date
    include_charts: bool = True
    include_summary: bool = True

@dataclass
class PrintConfig:
    page_size: str = "A4"
    orientation: str = "portrait"

@dataclass
class ReportFilter:
    department: Optional[str] = None
    category: Optional[str] = None

def create_report(
    config: ReportConfig,
    print_config: PrintConfig,
    filter: ReportFilter
):
    # Much cleaner!
    pass

def email_report(
    config: ReportConfig,
    recipients: list[str]
):
    # Reuses ReportConfig
    pass

Replace Magic Numbers/Strings with Constants

Before:

def calculate_shipping(weight, distance):
    if weight < 1:
        base_cost = 5.99
    elif weight < 5:
        base_cost = 9.99
    else:
        base_cost = 14.99
    
    if distance > 500:
        base_cost *= 1.5
    
    if distance > 1000:
        base_cost *= 2.0
    
    return base_cost

def get_status_message(status):
    if status == 1:
        return "Pending"
    elif status == 2:
        return "Processing"
    elif status == 3:
        return "Shipped"
    elif status == 4:
        return "Delivered"

After:

from enum import Enum, auto
from dataclasses import dataclass

# Weight-based pricing
class ShippingTier:
    LIGHT_WEIGHT_LIMIT = 1  # kg
    MEDIUM_WEIGHT_LIMIT = 5  # kg
    
    LIGHT_COST = 5.99
    MEDIUM_COST = 9.99
    HEAVY_COST = 14.99

# Distance multipliers
class DistanceMultiplier:
    LONG_DISTANCE_THRESHOLD = 500  # km
    VERY_LONG_DISTANCE_THRESHOLD = 1000  # km
    
    LONG_DISTANCE_MULTIPLIER = 1.5
    VERY_LONG_DISTANCE_MULTIPLIER = 2.0

class OrderStatus(Enum):
    PENDING = auto()
    PROCESSING = auto()
    SHIPPED = auto()
    DELIVERED = auto()
    
    @property
    def display_name(self):
        return self.name.capitalize()

def calculate_shipping(weight: float, distance: float) -> float:
    if weight < ShippingTier.LIGHT_WEIGHT_LIMIT:
        base_cost = ShippingTier.LIGHT_COST
    elif weight < ShippingTier.MEDIUM_WEIGHT_LIMIT:
        base_cost = ShippingTier.MEDIUM_COST
    else:
        base_cost = ShippingTier.HEAVY_COST
    
    if distance > DistanceMultiplier.VERY_LONG_DISTANCE_THRESHOLD:
        base_cost *= DistanceMultiplier.VERY_LONG_DISTANCE_MULTIPLIER
    elif distance > DistanceMultiplier.LONG_DISTANCE_THRESHOLD:
        base_cost *= DistanceMultiplier.LONG_DISTANCE_MULTIPLIER
    
    return base_cost

def get_status_message(status: OrderStatus) -> str:
    return status.display_name

Dependency Injection

Replace hard-coded dependencies with injected ones.

Before:

class UserService:
    def __init__(self):
        # Hard-coded dependencies - difficult to test
        self.db = PostgresDatabase("localhost", "users_db")
        self.cache = RedisCache("localhost:6379")
        self.mailer = SmtpMailer("smtp.gmail.com")
    
    def create_user(self, data):
        user = self.db.insert("users", data)
        self.cache.set(f"user:{user.id}", user)
        self.mailer.send(user.email, "Welcome!")
        return user

After:

from abc import ABC, abstractmethod

# Define interfaces (abstractions)
class Database(ABC):
    @abstractmethod
    def insert(self, table: str, data: dict) -> dict:
        pass

class Cache(ABC):
    @abstractmethod
    def set(self, key: str, value: any) -> None:
        pass

class Mailer(ABC):
    @abstractmethod
    def send(self, to: str, message: str) -> None:
        pass

# Concrete implementations
class PostgresDatabase(Database):
    def __init__(self, host: str, db_name: str):
        self.host = host
        self.db_name = db_name
    
    def insert(self, table, data):
        # PostgreSQL-specific implementation
        pass

class RedisCache(Cache):
    def __init__(self, url: str):
        self.url = url
    
    def set(self, key, value):
        # Redis-specific implementation
        pass

# Service with injected dependencies
class UserService:
    def __init__(self, db: Database, cache: Cache, mailer: Mailer):
        self.db = db
        self.cache = cache
        self.mailer = mailer
    
    def create_user(self, data):
        user = self.db.insert("users", data)
        self.cache.set(f"user:{user.id}", user)
        self.mailer.send(user.email, "Welcome!")
        return user

# Easy to test with mocks!
class MockDatabase(Database):
    def insert(self, table, data):
        return {"id": 1, **data}

# Production usage
service = UserService(
    db=PostgresDatabase("localhost", "users_db"),
    cache=RedisCache("localhost:6379"),
    mailer=SmtpMailer("smtp.gmail.com")
)

# Test usage
test_service = UserService(
    db=MockDatabase(),
    cache=MockCache(),
    mailer=MockMailer()
)

Strategy Pattern

Encapsulate algorithms that can be swapped.

Before:

class PriceCalculator:
    def calculate(self, items, customer_type, promo_code):
        total = sum(item.price * item.quantity for item in items)
        
        # Discount logic scattered and hard to maintain
        if customer_type == "premium":
            total *= 0.9  # 10% discount
        elif customer_type == "vip":
            total *= 0.8  # 20% discount
        
        if promo_code == "SUMMER20":
            total *= 0.8
        elif promo_code == "FLASH50":
            total *= 0.5
        elif promo_code == "FREESHIP":
            pass  # Free shipping handled elsewhere
        
        return total

After:

from abc import ABC, abstractmethod

class DiscountStrategy(ABC):
    @abstractmethod
    def apply(self, total: float) -> float:
        pass

class NoDiscount(DiscountStrategy):
    def apply(self, total):
        return total

class PercentageDiscount(DiscountStrategy):
    def __init__(self, percentage: float):
        self.percentage = percentage
    
    def apply(self, total):
        return total * (1 - self.percentage / 100)

class FixedDiscount(DiscountStrategy):
    def __init__(self, amount: float):
        self.amount = amount
    
    def apply(self, total):
        return max(0, total - self.amount)

class CompositeDiscount(DiscountStrategy):
    def __init__(self, *strategies: DiscountStrategy):
        self.strategies = strategies
    
    def apply(self, total):
        for strategy in self.strategies:
            total = strategy.apply(total)
        return total

# Strategy factory
class DiscountFactory:
    CUSTOMER_DISCOUNTS = {
        "regular": NoDiscount(),
        "premium": PercentageDiscount(10),
        "vip": PercentageDiscount(20),
    }
    
    PROMO_DISCOUNTS = {
        "SUMMER20": PercentageDiscount(20),
        "FLASH50": PercentageDiscount(50),
        "SAVE10": FixedDiscount(10),
    }
    
    @classmethod
    def get_discount(cls, customer_type: str, promo_code: str = None) -> DiscountStrategy:
        strategies = []
        
        customer_discount = cls.CUSTOMER_DISCOUNTS.get(customer_type, NoDiscount())
        strategies.append(customer_discount)
        
        if promo_code:
            promo_discount = cls.PROMO_DISCOUNTS.get(promo_code)
            if promo_discount:
                strategies.append(promo_discount)
        
        if len(strategies) == 1:
            return strategies[0]
        return CompositeDiscount(*strategies)

class PriceCalculator:
    def calculate(self, items, customer_type, promo_code=None):
        total = sum(item.price * item.quantity for item in items)
        discount = DiscountFactory.get_discount(customer_type, promo_code)
        return discount.apply(total)

Null Object Pattern

Replace null checks with a null object.

Before:

class UserService:
    def get_user(self, user_id):
        user = self.db.find(user_id)
        return user  # Could be None
    
    def display_welcome(self, user_id):
        user = self.get_user(user_id)
        if user is not None:
            if user.name is not None:
                print(f"Welcome, {user.name}!")
            else:
                print("Welcome!")
            
            if user.preferences is not None:
                if user.preferences.theme is not None:
                    apply_theme(user.preferences.theme)
                else:
                    apply_theme("default")
            else:
                apply_theme("default")
        else:
            print("Welcome, Guest!")
            apply_theme("default")

After:

from dataclasses import dataclass, field

@dataclass
class Preferences:
    theme: str = "default"
    language: str = "en"
    notifications: bool = True

@dataclass
class User:
    id: str
    name: str
    email: str
    preferences: Preferences = field(default_factory=Preferences)

class NullUser(User):
    """Null Object representing a guest/anonymous user."""
    
    def __init__(self):
        super().__init__(
            id="guest",
            name="Guest",
            email="",
            preferences=Preferences()
        )
    
    def is_authenticated(self):
        return False

class UserService:
    NULL_USER = NullUser()
    
    def get_user(self, user_id) -> User:
        user = self.db.find(user_id)
        return user if user else self.NULL_USER
    
    def display_welcome(self, user_id):
        user = self.get_user(user_id)
        # No null checks needed!
        print(f"Welcome, {user.name}!")
        apply_theme(user.preferences.theme)

Refactoring Process

Step 1: Identify the Smell

  • Review code for patterns listed above
  • Check complexity metrics (cyclomatic complexity, LOC)
  • Look for repeated patterns

Step 2: Ensure Test Coverage

  • Write tests before refactoring (if missing)
  • Tests should pass before AND after
  • Use tests as a safety net

Step 3: Make Small Changes

  • One refactoring at a time
  • Commit after each successful change
  • Keep changes reversible

Step 4: Verify Behavior

  • Run all tests after each change
  • Check for regressions
  • Validate performance if relevant

Step 5: Clean Up

  • Remove dead code
  • Update documentation
  • Review for additional improvements

Complexity Metrics

When to Refactor

MetricThresholdAction
Cyclomatic Complexity> 10Extract methods
Lines per Method> 30Extract methods
Lines per Class> 300Extract classes
Parameters> 4Parameter object
Nesting Depth> 3Early returns, extract
Duplicate Code> 3 occurrencesExtract to shared

Measuring Complexity

# Tools for measuring
# Python: radon, flake8-cognitive-complexity
# JavaScript: eslint-plugin-complexity
# General: SonarQube, CodeClimate

# Example: radon output
# M 42:0 process_order - C (13)  # Complexity 13 = needs refactoring
# M 78:0 validate_input - A (3)   # Complexity 3 = good

# Run with: radon cc src/ -a -s

Refactoring Checklist

Before refactoring:

  • Tests exist and pass
  • Code is version controlled
  • Understand the current behavior
  • Identified specific smell to fix

During refactoring:

  • Make one change at a time
  • Run tests after each change
  • Commit frequently
  • Keep behavior identical

After refactoring:

  • All tests still pass
  • Code is more readable
  • Complexity is reduced
  • Documentation updated
  • No new bugs introduced

Output Format

When suggesting refactorings, provide:

## Refactoring Analysis

### Code Smell Identified
**Type:** [Smell name]
**Severity:** [High/Medium/Low]
**Location:** `file.py:42-78`

### Current Code Issues
1. [Issue 1]
2. [Issue 2]
3. [Issue 3]

### Recommended Refactoring
**Pattern:** [Refactoring pattern name]

**Current Code:**
```python
[Code before refactoring]

Refactored Code:

[Code after refactoring]

Benefits

  • [Benefit 1]
  • [Benefit 2]
  • [Benefit 3]

Migration Steps

  1. [Step 1]
  2. [Step 2]
  3. [Step 3]

Testing Considerations

  • [What tests to add/modify]

## Notes

- Always refactor with tests as a safety net
- Prefer many small refactorings over big rewrites
- Don't mix refactoring with adding new features
- Refactor incrementally, not all at once
- Sometimes "good enough" code is acceptable
- Consider team readability over clever solutions
- Document significant design decisions

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.