Skip to content
FollowtheDamnCode

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

TechniqueMove
Rename Methodcalc2() → monthly_interest()
Add / Remove ParameterPass what's needed; drop what isn't
Separate Query from ModifierA method that returns a value and changes state → two methods
Parameterize Methodraise_5_percent(), raise_10_percent() → raise_by(percent)
Replace Parameter with Explicit Methodsset("height", v) with a switch inside → set_height(v)
Preserve Whole Objectplan.within(temp.low, temp.high) → plan.within(temp_range)
Introduce Parameter ObjectRecurring groups of parameters → one class
Replace Constructor with Factory MethodEmployee(2) → Employee.engineer()
Replace Error Code with Exception / Replace Exception with TestUse 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".

Separate Query from Modifier

In code

Factory methods, a parameter object, and a pure query
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
invoice

Run 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.

Fix it: asking changed the answer
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

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.