agentsclimarketplace

Refactoring composing methods

Skill lifeodyssey/craftsmanship-skills/skills/refactoring-composing-methods

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

Install
npx -y skills add lifeodyssey/craftsmanship-skills --skill refactoring-composing-methods

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 breaking down and reorganizing methods: Extract Method, Inline Method, Replace Temp with Query, Introduce Explaining Variable, Split Temporary Variable, Replace Method with Method Object, Substitute Algorithm. All with Python before/after examples.

SKILL.md

10.3 KB, as published. Nobody here has run it

Composing Methods

Methods should be small, well-named, and do one thing. The techniques in this skill help you restructure methods so they communicate intent clearly and are easy to understand at a glance.

When to Use This Skill

Triggers:

  • A method is too long to understand at a glance
  • You need comments to explain what sections of a method do
  • A method does multiple distinct things
  • A temporary variable obscures the logic
  • An algorithm is hard to follow and you want to replace it wholesale

Don't Triggers:

  • The problem is about which class a method belongs to (use refactoring-moving-features)
  • The problem is about complex conditional logic (use refactoring-simplifying-conditionals)
  • You're trying to identify smells rather than fix them (use refactoring-code-smells)

Extract Method

Problem: You have a code fragment that can be grouped together.

Solution: Turn the fragment into a method whose name explains the purpose.

# BEFORE
def print_owing(self, outstanding):
    print("***********************")
    print("**** Customer Owes ****")
    print("***********************")

    # calculate outstanding
    for order in self.orders:
        outstanding += order.amount

    # print details
    print(f"name: {self.name}")
    print(f"amount: {outstanding}")
# AFTER
def print_owing(self, outstanding):
    self._print_banner()
    outstanding = self._calculate_outstanding(outstanding)
    self._print_details(outstanding)

def _print_banner(self):
    print("***********************")
    print("**** Customer Owes ****")
    print("***********************")

def _calculate_outstanding(self, outstanding):
    for order in self.orders:
        outstanding += order.amount
    return outstanding

def _print_details(self, outstanding):
    print(f"name: {self.name}")
    print(f"amount: {outstanding}")

Steps:

  1. Create a new method and name it after what it does (not how).
  2. Copy the extracted code into the new method.
  3. Replace the original code with a call to the new method.
  4. Pass local variables as parameters if needed.
  5. If the extracted code modifies a local variable, return it.

Inline Method

Problem: A method's body is as clear as its name.

Solution: Replace calls to the method with the method's body, then remove the method.

# BEFORE — method adds no clarity
class Customer:
    def get_rating(self):
        return 2 if self._more_than_five_late_deliveries() else 1

    def _more_than_five_late_deliveries(self):
        return self._number_of_late_deliveries > 5
# AFTER — inline the method
class Customer:
    def get_rating(self):
        return 2 if self._number_of_late_deliveries > 5 else 1

Steps:

  1. Verify the method isn't polymorphic (not overridden in subclasses).
  2. Copy the method body to all call sites.
  3. Remove the method.

Replace Temp with Query

Problem: You assign a value to a temporary variable, then use it once or twice.

Solution: Replace the variable with a method call.

# BEFORE
class Order:
    def get_price(self):
        base_price = self._quantity * self._item_price
        if base_price > 1000:
            return base_price * 0.95
        else:
            return base_price * 0.98
# AFTER
class Order:
    def get_price(self):
        if self._base_price() > 1000:
            return self._base_price() * 0.95
        else:
            return self._base_price() * 0.98

    def _base_price(self):
        return self._quantity * self._item_price

Steps:

  1. Identify the temporary variable and the expression that computes it.
  2. Extract the expression into a method.
  3. Replace all uses of the temporary variable with calls to the method.
  4. Remove the temporary variable.

When to stop: If the expression is expensive (database call, complex computation), consider caching the result or keeping the temp.


Introduce Explaining Variable

Problem: You have a complex expression that is hard to understand.

Solution: Put the result of the expression, or parts of it, in a temporary variable with a name that explains the purpose.

# BEFORE
def calculate_total(self):
    if (self.order.quantity * self.order.price - self.order.discount
            > 1000 and self.customer.loyalty_years > 2):
        return self.order.total * 0.9
    return self.order.total
# AFTER
def calculate_total(self):
    order_value = self.order.quantity * self.order.price - self.order.discount
    is_high_value_order = order_value > 1000
    is_loyal_customer = self.customer.loyalty_years > 2

    if is_high_value_order and is_loyal_customer:
        return self.order.total * 0.9
    return self.order.total

Steps:

  1. Assign parts of the complex expression to temporary variables named for what they represent.
  2. Use the variables in place of the expression parts.

Note: Once you have explaining variables, consider Extract Method to make them permanent.


Split Temporary Variable

Problem: A temporary variable is assigned more than once but is not a loop variable or an accumulator.

Solution: Make a separate temporary variable for each assignment.

# BEFORE — temp serves two unrelated purposes
def calculate_distance(self):
    temp = 2 * (self.height + self.width)
    print(f"Perimeter: {temp}")

    temp = self.height * self.width
    print(f"Area: {temp}")
# AFTER — each temp has a single purpose
def calculate_distance(self):
    perimeter = 2 * (self.height + self.width)
    print(f"Perimeter: {perimeter}")

    area = self.height * self.width
    print(f"Area: {area}")

Steps:

  1. Identify a temporary variable that is assigned more than once.
  2. Change the name at the second (and subsequent) assignment sites.
  3. Compile/test after each change.

Replace Method with Method Object

Problem: You have a long method that uses local variables in such a way that you cannot apply Extract Method.

Solution: Turn the method into its own object so that all the local variables become fields on that object. Then you can decompose the method into other methods on the same object.

# BEFORE — complex method with many locals, hard to extract
class Order:
    def calculate_price(self):
        primary_base_price = self._quantity * self._item_price
        secondary_base_price = self._quantity * self._item_price * 0.95
        tertiary_base_price = self._quantity * self._item_price * 0.92
        # ... many more steps using these values
        discount_factor = 0.98 if primary_base_price > 1000 else 0.95
        return (primary_base_price + secondary_base_price + tertiary_base_price) * discount_factor
# AFTER — method object captures all locals as fields
class PriceCalculator:
    def __init__(self, order):
        self._order = order
        self._primary_base_price = 0
        self._secondary_base_price = 0
        self._tertiary_base_price = 0
        self._discount_factor = 0

    def calculate(self):
        self._compute_base_prices()
        self._compute_discount_factor()
        return self._total()

    def _compute_base_prices(self):
        quantity = self._order.quantity
        item_price = self._order.item_price
        self._primary_base_price = quantity * item_price
        self._secondary_base_price = quantity * item_price * 0.95
        self._tertiary_base_price = quantity * item_price * 0.92

    def _compute_discount_factor(self):
        self._discount_factor = 0.98 if self._primary_base_price > 1000 else 0.95

    def _total(self):
        return (self._primary_base_price
                + self._secondary_base_price
                + self._tertiary_base_price) * self._discount_factor


class Order:
    def calculate_price(self):
        return PriceCalculator(self).calculate()

Steps:

  1. Create a new class named after the method.
  2. Give the new class a field for the object that hosted the original method (the "source object").
  3. Give it fields for each temporary variable and parameter in the method.
  4. Give it a calculate method (or similarly named) with the body of the original method.
  5. Replace the original method body with a call to the method object.

Substitute Algorithm

Problem: You want to replace an algorithm with one that is clearer.

Solution: Replace the body of the method with the new algorithm.

# BEFORE — imperative, hard to follow
def found_person(self, people):
    for i in range(len(people)):
        if people[i] == "Don":
            return "Don"
        if people[i] == "John":
            return "John"
        if people[i] == "Kent":
            return "Kent"
    return ""
# AFTER — declarative, clear intent
def found_person(self, people):
    targets = {"Don", "John", "Kent"}
    matches = [p for p in people if p in targets]
    return matches[0] if matches else ""

Steps:

  1. Verify you have adequate tests for the existing algorithm.
  2. Replace the method body with the new algorithm.
  3. Run tests to verify the new algorithm works.

When to use: When you find a simpler or more expressive way to accomplish the same result. This is the refactoring of last resort — try others first.


Summary Table

RefactoringProblemSolution
Extract MethodCode fragment grouped togetherTurn fragment into a named method
Inline MethodMethod body as clear as nameReplace calls with body, remove method
Replace Temp with QueryTemp variable used once or twiceReplace with method call
Introduce Explaining VariableComplex expression hard to readPut parts in named temporaries
Split Temporary VariableTemp assigned for unrelated purposesSeparate into distinct variables
Replace Method with Method ObjectLong method, too many locals to extractTurn method into its own class
Substitute AlgorithmAlgorithm is unclearReplace with clearer algorithm

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.