Refactoring: Simplifying Method Calls
Make interfaces easy to call correctly — clear names, fewer and better parameters, queries separated from commands, and factories instead of tricky constructors.
Big idea
A method's signature is its user interface. These refactorings make calls hard to get wrong: names that say what happens, parameters grouped into meaningful objects, no surprising side effects in "getters", and creation hidden behind named factories.
The techniques
| Technique | Move |
|---|---|
| Rename Method | calc2() → monthly_interest() |
| Add / Remove Parameter | Pass what's needed; drop what isn't |
| Separate Query from Modifier | A method that returns a value and changes state → two methods |
| Parameterize Method | raise_5_percent(), raise_10_percent() → raise_by(percent) |
| Replace Parameter with Explicit Methods | set("height", v) with a switch inside → set_height(v) |
| Preserve Whole Object | plan.within(temp.low, temp.high) → plan.within(temp_range) |
| Introduce Parameter Object | Recurring groups of parameters → one class |
| Replace Constructor with Factory Method | Employee(2) → Employee.engineer() |
| Replace Error Code with Exception / Replace Exception with Test | Use the error style that fits the situation |
Think of it like
A vending machine with unlabeled buttons numbered 1–40 versus one with "Water", "Coffee", "Hot chocolate". Both work. Only one is hard to use wrongly — and the labelled one also doesn't secretly charge you when you press "Show price".
In code
from dataclasses import dataclass
@dataclass(frozen=True)
class DateRange: # Introduce Parameter Object
start: int
end: int
def contains(self, day):
return self.start <= day <= self.end
class Employee:
def __init__(self, name, kind, base):
self.name, self.kind, self.base = name, kind, base
@classmethod
def engineer(cls, name): # Replace Constructor with Factory Method
return cls(name, "engineer", 5000)
@classmethod
def intern(cls, name):
return cls(name, "intern", 1500)
def pay_for(self, period: DateRange, absences): # Preserve Whole Object
days = period.end - period.start + 1
missed = sum(1 for d in absences if period.contains(d))
return round(self.base * (days - missed) / days, 2)
class Inbox:
def __init__(self, messages):
self._messages, self._read = list(messages), 0
def peek(self): # query: no side effects
return self._messages[self._read] if self._read < len(self._messages) else None
def mark_read(self): # modifier: no return value
self._read += 1
march = DateRange(1, 31)
for e in (Employee.engineer("Lin"), Employee.intern("Max")):
print(f"{e.name:4} {e.kind:9} {e.pay_for(march, absences=[3, 4, 40]):8.2f}")
inbox = Inbox(["hello", "invoice"])
print(inbox.peek(), inbox.peek()) # asking twice is safe
inbox.mark_read()
print(inbox.peek())output
Lin engineer 4677.42
Max intern 1403.23
hello hello
invoiceRun and edit it in the app.
Watch it
Hands on: a getter with a side effect
next_ticket() both returns the next ticket and removes it. A status page calls it "just to look", silently consuming tickets. Separate the query from the modifier so looking is harmless, and match the output.
class Queue:
def __init__(self, items):
self.items = list(items)
def next_ticket(self):
return self.items.pop(0) if self.items else None
q = Queue(["T1", "T2", "T3"])
print("status page shows:", q.next_ticket()) # meant only to look
print("agent takes:", q.next_ticket())
print("remaining:", q.items)output
status page shows: T1
agent takes: T1
remaining: ['T2', 'T3']Run and edit it in the app.
Why separate queries from modifiers?
What's the main benefit of Employee.engineer("Lin") over Employee("Lin", 2, 5000)?
How it relates
- Fixes Long Parameter List; Replace Constructor with Factory Method leads to Factory Method.
- Builder is the next step when creation has many optional parts.
- Command–query separation pairs with Encapsulation's "tell, don't ask".
Key terms
- Command–query separation — A method either changes state or returns information, not both.
- Parameter object — A class bundling parameters that travel together.
- Preserve whole object — Passing the object itself instead of several values pulled out of it.
- Factory method (refactoring) — A named static/class method that replaces direct constructor calls.