| name | refactoring-composing-methods |
| description | 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. |
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.
def print_owing(self, outstanding):
print("***********************")
print("**** Customer Owes ****")
print("***********************")
for order in self.orders:
outstanding += order.amount
print(f"name: {self.name}")
print(f"amount: {outstanding}")
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:
- Create a new method and name it after what it does (not how).
- Copy the extracted code into the new method.
- Replace the original code with a call to the new method.
- Pass local variables as parameters if needed.
- 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.
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
class Customer:
def get_rating(self):
return 2 if self._number_of_late_deliveries > 5 else 1
Steps:
- Verify the method isn't polymorphic (not overridden in subclasses).
- Copy the method body to all call sites.
- 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.
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
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:
- Identify the temporary variable and the expression that computes it.
- Extract the expression into a method.
- Replace all uses of the temporary variable with calls to the method.
- 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.
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
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:
- Assign parts of the complex expression to temporary variables named for what they represent.
- 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.
def calculate_distance(self):
temp = 2 * (self.height + self.width)
print(f"Perimeter: {temp}")
temp = self.height * self.width
print(f"Area: {temp}")
def calculate_distance(self):
perimeter = 2 * (self.height + self.width)
print(f"Perimeter: {perimeter}")
area = self.height * self.width
print(f"Area: {area}")
Steps:
- Identify a temporary variable that is assigned more than once.
- Change the name at the second (and subsequent) assignment sites.
- 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.
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
discount_factor = 0.98 if primary_base_price > 1000 else 0.95
return (primary_base_price + secondary_base_price + tertiary_base_price) * discount_factor
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:
- Create a new class named after the method.
- Give the new class a field for the object that hosted the original method (the "source object").
- Give it fields for each temporary variable and parameter in the method.
- Give it a
calculate method (or similarly named) with the body of the original method.
- 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.
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 ""
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:
- Verify you have adequate tests for the existing algorithm.
- Replace the method body with the new algorithm.
- 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
| Refactoring | Problem | Solution |
|---|
| Extract Method | Code fragment grouped together | Turn fragment into a named method |
| Inline Method | Method body as clear as name | Replace calls with body, remove method |
| Replace Temp with Query | Temp variable used once or twice | Replace with method call |
| Introduce Explaining Variable | Complex expression hard to read | Put parts in named temporaries |
| Split Temporary Variable | Temp assigned for unrelated purposes | Separate into distinct variables |
| Replace Method with Method Object | Long method, too many locals to extract | Turn method into its own class |
| Substitute Algorithm | Algorithm is unclear | Replace with clearer algorithm |
Source: Martin Fowler, "Refactoring: Improving the Design of Existing Code" (2nd Edition)