agentsclimarketplace

Refactoring moving features

Skill lifeodyssey/craftsmanship-skills/skills/refactoring-moving-features

Agent Skills distilled from Clean Code & Refactoring. Install: npx skills add lifeodyssey/craftsmanship-skills

Install
npx -y skills add lifeodyssey/craftsmanship-skills --skill refactoring-moving-features

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Techniques for moving behavior and data between classes: Move Method, Move Field, Extract Class, Inline Class, Hide Delegate, Remove Middle Man. All with Python before/after examples.

SKILL.md

9.6 KB, as published. Nobody here has run it

Moving Features Between Objects

Objects should have the right responsibilities. When behavior or data is in the wrong place, these refactorings move it to where it belongs.

When to Use This Skill

Triggers:

  • A method uses more data from another class than its own (Feature Envy)
  • A class has too many responsibilities (Large Class)
  • Changing one class requires changing many others (Shotgun Surgery)
  • Two classes are too tightly coupled (Inappropriate Intimacy)
  • Client code chains through multiple objects (Message Chains)
  • A class mostly delegates to another (Middle Man)

Don't Triggers:

  • The problem is within a single method's internal structure (use refactoring-composing-methods)
  • The problem is about complex conditional logic (use refactoring-simplifying-conditionals)
  • You're trying to identify what's wrong (use refactoring-code-smells)

Move Method

Problem: A method uses or is used by more features of another class than its own.

Solution: Create a method in the class it uses most, and delegate from the original.

# BEFORE — AccountType.get_interest_rate() belongs in Account
class Account:
    def __init__(self, account_type, days_overdrawn):
        self.type = account_type
        self.days_overdrawn = days_overdrawn

    def calculate_interest(self):
        if self.days_overdrawn > 0:
            return self.type.get_overdraft_interest_rate() * self.days_overdrawn
        return self.type.get_normal_interest_rate()


class AccountType:
    def __init__(self, is_premium):
        self.is_premium = is_premium

    def get_overdraft_interest_rate(self):
        return 0.1 if self.is_premium else 0.15

    def get_normal_interest_rate(self):
        return 0.03 if self.is_premium else 0.05
# AFTER — move the calculation to Account, where it's most used
class Account:
    def __init__(self, account_type, days_overdrawn):
        self.type = account_type
        self.days_overdrawn = days_overdrawn

    def calculate_interest(self):
        if self.days_overdrawn > 0:
            return self._overdraft_interest_rate() * self.days_overdrawn
        return self._normal_interest_rate()

    def _overdraft_interest_rate(self):
        return 0.1 if self.type.is_premium else 0.15

    def _normal_interest_rate(self):
        return 0.03 if self.type.is_premium else 0.05

Steps:

  1. Examine all features used by the method in its current class.
  2. Examine the target class to see if it already has a similar method.
  3. Create a new method in the target class. Copy the body.
  4. Turn the original method into a delegation, or remove it if no other callers exist.

Move Field

Problem: A field is used by another class more than the class it is defined in.

Solution: Create a field in the new class and redirect all users.

# BEFORE — discount_rate is used mostly by Customer, not by Account
class Account:
    def __init__(self, discount_rate):
        self.discount_rate = discount_rate


class Customer:
    def __init__(self, account):
        self.account = account

    def apply_discount(self, amount):
        return amount * (1 - self.account.discount_rate)
# AFTER — discount_rate lives where it's used
class Account:
    def __init__(self):
        pass


class Customer:
    def __init__(self, discount_rate):
        self.discount_rate = discount_rate

    def apply_discount(self, amount):
        return amount * (1 - self.discount_rate)

Steps:

  1. Make sure the source field is encapsulated (use getter/setter or property).
  2. Create a field and accessors in the target class.
  3. Change all callers to use the new field.
  4. Remove the field from the source class.

Extract Class

Problem: One class does the work of two.

Solution: Create a new class and move the relevant fields and methods.

# BEFORE — Person handles both personal info and phone number logic
class Person:
    def __init__(self, name, office_area_code, office_number):
        self.name = name
        self.office_area_code = office_area_code
        self.office_number = office_number

    def get_telephone_number(self):
        return f"({self.office_area_code}) {self.office_number}"

    def get_office_area_code(self):
        return self.office_area_code

    def get_office_number(self):
        return self.office_number
# AFTER — phone number logic extracted to its own class
class TelephoneNumber:
    def __init__(self, area_code, number):
        self.area_code = area_code
        self.number = number

    def to_string(self):
        return f"({self.area_code}) {self.number}"


class Person:
    def __init__(self, name, area_code, number):
        self.name = name
        self.office_telephone = TelephoneNumber(area_code, number)

    def get_telephone_number(self):
        return self.office_telephone.to_string()

Steps:

  1. Decide how to split the responsibilities of the class.
  2. Create a new class to express the split-off responsibility.
  3. Use a field in the old class to hold an instance of the new class.
  4. Move fields and methods from the old class to the new class.
  5. Update callers.

Inline Class

Problem: A class is doing too little to justify its existence.

Solution: Move all its features into another class and delete it.

# BEFORE — TelephoneNumber adds no real value
class TelephoneNumber:
    def __init__(self, area_code, number):
        self.area_code = area_code
        self.number = number

    def to_string(self):
        return f"({self.area_code}) {self.number}"


class Person:
    def __init__(self, name, area_code, number):
        self.name = name
        self.office_telephone = TelephoneNumber(area_code, number)

    def get_telephone_number(self):
        return self.office_telephone.to_string()
# AFTER — inline TelephoneNumber into Person
class Person:
    def __init__(self, name, area_code, number):
        self.name = name
        self.office_area_code = area_code
        self.office_number = number

    def get_telephone_number(self):
        return f"({self.office_area_code}) {self.office_number}"

Steps:

  1. Adjust all methods in the delegating class to use the delegate class directly.
  2. Change all callers to use the delegate class directly.
  3. Delete the delegate class.

When to use: When Extract Class was done prematurely, or when the extracted class no longer carries its weight.


Hide Delegate

Problem: A client calls a method on a delegate object that's obtained from a server object.

Solution: Create a method on the server that delegates to the delegate.

# BEFORE — client reaches through Person to get Department's manager
class Department:
    def __init__(self, manager):
        self.manager = manager


class Person:
    def __init__(self, department):
        self.department = department


# Client code:
manager = john.department.manager
# AFTER — Person hides the delegation
class Department:
    def __init__(self, manager):
        self.manager = manager


class Person:
    def __init__(self, department):
        self._department = department

    def get_manager(self):
        return self._department.manager


# Client code:
manager = john.get_manager()

Steps:

  1. For each method on the delegate that the client calls, create a method on the server that delegates to the delegate.
  2. Change the client to call the server's methods.
  3. If the client no longer needs the delegate, remove the accessor for it.

Remove Middle Man

Problem: A class has too many methods that do nothing but delegate to another.

Solution: Have the client call the delegate directly.

# BEFORE — Person is just a middle man for Department
class Person:
    def __init__(self, department):
        self._department = department

    def get_manager(self):
        return self._department.manager

    def get_department_name(self):
        return self._department.name

    def get_department_budget(self):
        return self._department.budget

    def get_department_headcount(self):
        return self._department.headcount
# AFTER — clients get the Department directly
class Person:
    def __init__(self, department):
        self.department = department  # public attribute


# Client code:
manager = john.department.manager
name = john.department.name

Steps:

  1. Expose the delegate object directly (make it a public attribute or return it from a getter).
  2. Change all clients to call through the delegate directly.
  3. Remove the delegating methods.

When to use: This is the inverse of Hide Delegate. Use it when Hide Delegate went too far and the server class became a bloated proxy.


Summary Table

RefactoringProblemSolution
Move MethodMethod uses data from another classMove method to the class it uses most
Move FieldField is used by another classMove field to the class that uses it
Extract ClassOne class does the work of twoCreate new class, move relevant fields/methods
Inline ClassClass does too littleMerge into another class, delete it
Hide DelegateClient reaches through server to delegateAdd delegation method on server
Remove Middle ManToo many delegating methodsLet clients access the delegate directly

Source: Martin Fowler, "Refactoring: Improving the Design of Existing Code" (2nd Edition)

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.