Replace Parameter with Explicit Methods: Name Boards Instead of Secret Codes
Learn the Replace Parameter with Explicit Methods refactoring with a bank counter story, TypeScript and Python examples, safe mechanics, and the seesaw rule that pairs it with Parameterize Method.
🏦 The Story of the Secret Code Counter
Meet Imran, a class seven student in Lucknow. Every month his father sends money to grandmother through a small cooperative bank near the chowk. One Saturday, Imran goes along — and meets the strangest counter he has ever seen.
There is only ONE counter. Above it hangs a board that says: "ALL WORK HERE. TELL YOUR CODE NUMBER." Below, in faded ink: Code 1 = Deposit. Code 2 = Withdraw. Code 3 = Passbook entry. Code 4 = New account. Code 5 = Demand draft.
Father says, "Code 2, five thousand." The clerk — his name plate says Mr. Tripathi — hears "2", opens drawer number 2, pulls out the withdrawal forms, and starts writing.
Behind them, an old uncle shuffles forward and says "Code 3"... no wait, "Code 2"... he scratches his beard. He has forgotten his code. Mr. Tripathi sighs and recites the whole legend again, for the ninth time that morning. A nervous aunty says "Code 4, five thousand" when she meant Code 1 — she wanted to deposit five thousand, not open a new account — and twenty minutes of confusion follow, with forms torn up and apologies exchanged. By eleven o'clock, Mr. Tripathi has spent more time decoding numbers than doing banking.
Imran watches all this and thinks of the big bank near his school. There, five counters stand in a row, each with a bold name board: DEPOSITS. WITHDRAWALS. PASSBOOK PRINTING. NEW ACCOUNTS. DEMAND DRAFTS. Nobody memorizes codes. Nobody can ask for the wrong service by a slip of the tongue, because you physically walk to the counter whose board says what you want. The clerk at the Deposits counter does deposits all day — no drawer-hunting, no legend-reciting, no guessing.
Here is the key observation: at the secret-code counter, the customer already knows what they want before they speak. The code "2" carries no data — it only selects a behaviour. So why make the clerk decode it on every visit? Put the decision where the knowledge is: let the customer walk straight to the clearly named counter.
Watch how the conversation changes once the decision moves to the caller's side:
Code suffers from secret-code counters too. A method like doBanking(actionCode, amount) that immediately switches on actionCode is the one-counter bank: every call passes a magic number, the method re-decides something the caller already knew, and a wrong code slips through silently. The cure is called Replace Parameter with Explicit Methods: give every behaviour its own counter with its own name board.
🤔 What is Replace Parameter with Explicit Methods?
Replace Parameter with Explicit Methods is the refactoring where you take a method whose parameter merely selects which behaviour to run, and split it into one clearly named method per behaviour, deleting the selector parameter.
The telltale shape of the patient: a method receives an enum, string, number code, or boolean, and the first thing it does is branch on it — if, else if, switch — into blocks that do quite different things. Callers almost always pass a literal: doBanking(1, 500), setDimension("height", 10), book(true). The parameter is not data flowing into one computation; it is a menu choice, made at the moment the programmer wrote the call.
This technique comes from the first edition of Martin Fowler's Refactoring (1999) and remains catalogued on Refactoring Guru and SourceMaking under Simplifying Method Calls. In the second edition (2018), its most famous special case got its own entry: Remove Flag Argument, for methods that take a boolean to switch behaviour. For larger case-sets, the second edition steers toward Replace Conditional with Polymorphism. The heart of all three is the same: a decision the caller already made should not be re-made at runtime inside the method.
How bad is the one-counter design in practice? Imran did a small survey at the cooperative bank, the way only a curious class seven student can. Of all the time Mr. Tripathi spent per customer, only part was actual banking:
Nearly half the effort was spent servicing the code system itself — explaining it and repairing its failures. That overhead is exactly what a switch-on-a-selector method costs a codebase: every reader decodes, every reviewer double-checks literals, every bad code becomes a runtime surprise.
What do we gain by splitting?
- Wrong calls become impossible. Nobody can pass code 7 to a counter that does not exist — there is simply no method to call. The compiler enforces what the faded legend never could.
- Call sites read like sentences.
account.withdraw(5000)needs no legend.account.doBanking(2, 5000)needs a trip to the documentation. - Each body shrinks to one job. The switch disappears; every method holds exactly one behaviour, easy to test and easy to change.
- Autocomplete becomes a menu. Typing
account.shows deposit, withdraw, printPassbook — discoverable, like name boards in a row.
One line to remember: if a parameter answers "WHICH thing should you do?" rather than "with WHAT data?", delete the parameter and give each "which" its own well-named method. Data deserves a parameter; a decision deserves a name.
College corner: the boolean special case of this disease has a famous name — the flag argument smell. Both Martin Fowler (in his FlagArgument bliki entry and the Remove Flag Argument catalog page) and Robert C. Martin (in Clean Code) call it out: a function that accepts a boolean to choose between two behaviours is loudly announcing that it does two things, which already violates the single-responsibility idea at function level. render(true) is unreadable at the call site; renderForPrint() is self-documenting. The deeper principle is that a flag argument couples the caller to the internal structure of the callee — the caller must know that the method forks inside, and which fork the literal selects. Splitting restores the proper contract: the name promises one behaviour, the body delivers exactly that behaviour, and the type system polices the menu.
And the standing warning: this refactoring has an exact inverse, Parameterize Method. If the "cases" turn out to be twins differing only by a value — 5%, 10%, 20% — splitting them is the wrong direction. The seesaw table and quadrant chart later in this post decide.
🚦 When do we need it?
Watch for these signs:
- Callers always pass literals. Search every call site of
doBanking(...). If the first argument is always a hard-coded1,2,"deposit", ortrue— never a variable computed at runtime — the parameter is a compile-time decision wearing a runtime costume. Split it. - A cryptic flag or code.
process(order, true, false)— what do the booleans mean? Every reader must open the method to find out. Booleans and number codes are the worst offenders; this is also a whiff of Primitive Obsession, where a humble primitive smuggles in a whole concept. - The method's body is a switch over the parameter, branch bodies unrelated. Deposit logic touches cash-in records; withdrawal logic checks balance and daily limits; draft logic talks to another branch. These blocks share nothing — they live together only because one method was made to host them all.
- An "unknown code" error branch exists.
default: throw new Error("bad action")is the clerk shrugging at code 7. After the split, that branch evaporates — the type system does the checking. - Long Parameter List pressure. Selector parameters often drag friends along —
doBanking(code, amount, accountNo, draftPayee, draftBranch)wheredraftPayeematters only for code 5. Splitting lets each explicit method ask only for what its behaviour needs, directly shrinking a Long Parameter List. And if the leftover parameters form a repeating group — payee, branch, IFSC travelling together — that is a Data Clumps signal: bundle them into one object as a follow-up.
And when the answer is no:
- The value is genuine runtime data. If the action code arrives from a parsed bank file or a user's menu tap, somebody must branch on it. Splitting the method just relocates the same switch into every caller — worse, duplicated. Keep one parameterized entry point (or a single thin dispatcher at the boundary that maps the runtime value to the explicit methods).
- There are many cases. Splitting a 12-way switch creates 12 methods; the class explodes. Prefer a strategy map or polymorphism.
- New cases arrive frequently. Each new case means editing the class and adding a method. If the case-set churns weekly, an extensible design beats a fixed menu.
The standard decision flow, shared with the inverse refactoring:
👀 Before and after at a glance
Imran's cooperative bank, in TypeScript:
// BEFORE: one counter, secret codes
class Account {
doBanking(actionCode: number, amount: number): void {
if (actionCode === 1) {
this.balance += amount;
this.history.push({ kind: "deposit", amount });
} else if (actionCode === 2) {
if (amount > this.balance) throw new Error("Insufficient balance");
if (amount > this.dailyLimit) throw new Error("Daily limit crossed");
this.balance -= amount;
this.history.push({ kind: "withdraw", amount });
} else if (actionCode === 3) {
this.printer.printEntries(this.history);
} else {
throw new Error(`Unknown action code: ${actionCode}`);
}
}
}
// callers — every one passes a literal, and hopes it is the right one:
account.doBanking(1, 5000); // deposit... probably?
account.doBanking(2, 5000); // withdraw... check the legend!
account.doBanking(3, 0); // amount means nothing here, but must be passedThree different behaviours share one door, one signature, and one meaningless amount for passbook printing. Now the bank with name boards:
// AFTER: one clearly named counter per service
class Account {
deposit(amount: number): void {
this.balance += amount;
this.history.push({ kind: "deposit", amount });
}
withdraw(amount: number): void {
if (amount > this.balance) throw new Error("Insufficient balance");
if (amount > this.dailyLimit) throw new Error("Daily limit crossed");
this.balance -= amount;
this.history.push({ kind: "withdraw", amount });
}
printPassbook(): void {
this.printer.printEntries(this.history);
}
}
// callers read like sentences — and code 7 is now a compile error, not a surprise:
account.deposit(5000);
account.withdraw(5000);
account.printPassbook(); // no fake amount neededNotice the side benefits. printPassbook() lost the meaningless amount parameter entirely. The Unknown action code branch vanished. And each method's signature now matches its own needs.
🪜 Step-by-step, the safe way
Do not delete doBanking in one heroic stroke — some far-away caller will break at midnight. The codebase should pass through deliberate, reversible states:
Climb the ladder:
- List the discrete values and map each to its behaviour. Read every branch. Write a small table: 1 → deposit, 2 → withdraw, 3 → passbook. Also search all call sites: does any caller pass a variable instead of a literal? Mark those — they need special handling in step 6.
- Create one explicit method per case, copying only that branch's body. Leave the original method untouched. Give each new method a name that says the behaviour, and a signature with only the parameters that behaviour uses.
- Make the old method delegate to the new ones. This intermediate keeps every existing caller working while the duplication disappears:
// Intermediate state: the old door still opens, but each room is now its own method
class Account {
deposit(amount: number): void { /* moved branch body */ }
withdraw(amount: number): void { /* moved branch body */ }
printPassbook(): void { /* moved branch body */ }
/** @deprecated walk to the named counter instead */
doBanking(actionCode: number, amount: number): void {
if (actionCode === 1) this.deposit(amount);
else if (actionCode === 2) this.withdraw(amount);
else if (actionCode === 3) this.printPassbook();
else throw new Error(`Unknown action code: ${actionCode}`);
}
}- Run all tests. Behaviour must be unchanged — same deposits, same errors, same history entries.
- Migrate literal-passing callers, one batch at a time.
account.doBanking(2, 5000)becomesaccount.withdraw(5000). The literal tells you exactly which method to substitute. Test after each batch. - Handle the runtime-value callers, if any exist. If some caller reads the action from user input or a file, keep ONE thin dispatcher at that boundary (it may simply be the slimmed-down
doBanking) that maps the runtime value to the explicit methods. The dispatch happens once, at the edge — not inside the domain logic. - Delete the old method when no caller remains (or keep only the boundary dispatcher). Remove its tests for unknown codes; add per-method tests instead.
The classic mistake: assuming all callers pass literals without checking. If even one caller does account.doBanking(codeFromFile, amt) and you delete the dispatcher, you force that caller to write its own switch — and the next such caller writes another. Before step 7, grep every call site. Compile-time selectors split cleanly; runtime selectors need exactly one boundary dispatcher to survive.
📚 A bigger real-life example
Imran's school library runs on software with the same disease, plus the dragged-along-parameters problem:
// BEFORE: one method, a string selector, and parameters that only sometimes matter
class LibraryDesk {
performAction(
action: "issue" | "return" | "renew" | "reserve",
memberId: string,
bookId: string,
days: number, // only matters for issue/renew
queuePosition: number // only matters for reserve
): string {
switch (action) {
case "issue":
if (this.loans.countFor(memberId) >= 3) return "LIMIT_REACHED";
this.loans.add(memberId, bookId, days);
return "ISSUED";
case "return":
const fine = this.loans.lateFine(memberId, bookId);
this.loans.close(memberId, bookId);
return fine > 0 ? `RETURNED_WITH_FINE_${fine}` : "RETURNED";
case "renew":
if (this.reservations.existsFor(bookId)) return "RESERVED_BY_OTHER";
this.loans.extend(memberId, bookId, days);
return "RENEWED";
case "reserve":
this.reservations.add(memberId, bookId, queuePosition);
return "RESERVED";
}
}
}
// callers must pass dummies for parameters their action ignores:
desk.performAction("return", "M042", "B777", 0, 0); // 0 and 0 mean... nothingEvery branch is a different world: issuing checks loan limits, returning computes fines, renewing checks reservations, reserving manages a queue. They share no logic — only a roof. And look at the callers: the "return" call must invent two zeros just to satisfy the signature.
After the split:
// AFTER: four counters, four name boards, four honest signatures
class LibraryDesk {
issueBook(memberId: string, bookId: string, days: number): string {
if (this.loans.countFor(memberId) >= 3) return "LIMIT_REACHED";
this.loans.add(memberId, bookId, days);
return "ISSUED";
}
returnBook(memberId: string, bookId: string): string {
const fine = this.loans.lateFine(memberId, bookId);
this.loans.close(memberId, bookId);
return fine > 0 ? `RETURNED_WITH_FINE_${fine}` : "RETURNED";
}
renewBook(memberId: string, bookId: string, days: number): string {
if (this.reservations.existsFor(bookId)) return "RESERVED_BY_OTHER";
this.loans.extend(memberId, bookId, days);
return "RENEWED";
}
reserveBook(memberId: string, bookId: string, queuePosition: number): string {
this.reservations.add(memberId, bookId, queuePosition);
return "RESERVED";
}
}
desk.returnBook("M042", "B777"); // no dummy zeros, no string to mistypeCount the wins: the switch is gone; each signature carries only real parameters; "isue" (a typo that the old string selector would have caught only at runtime, if at all) is now impossible; and each method can be tested in isolation with a tiny, focused test. One thing remains worth watching: memberId, bookId travel together through every method — if more friends join them, that is a Data Clumps hint to introduce a LoanRequest object.
The dummy-argument problem alone is worth a chart. In the before design, every call must supply five arguments no matter which action runs; after the split, each call supplies exactly what its behaviour needs:
Five arguments — two of them meaningless zeros — down to two honest ones. Multiply that by every call site and every future reader who must ask "why zero?", and the cost of the secret-code counter becomes visible.
College corner: the "when NOT to split" list is really the Open-Closed Principle wearing street clothes. A class with one explicit method per case is closed against wrong calls but open to modification pain: every new case edits the class. A parameterized dispatcher is the reverse. The textbook escape from this tension, when the case-set is large or churns often, is Replace Conditional with Polymorphism or a registry/strategy map: new cases become new classes or entries rather than edits to existing code. So treat Replace Parameter with Explicit Methods as the right tool for small, stable, policy-like menus — bank counter services, library desk actions — and reach for polymorphic dispatch when the menu behaves more like a plugin system. Knowing which regime you are in is a design judgement, not a syntax question.
🐍 The same refactoring in Python
Python has no compiler to police codes, which makes secret-code methods even more dangerous there — and name boards even more valuable:
# BEFORE: a mode string selects the behaviour
class NotificationService:
def send(self, mode: str, user, message: str) -> None:
if mode == "sms":
if not user.phone:
raise ValueError("No phone number")
self._sms_gateway.push(user.phone, message[:160])
elif mode == "email":
if not user.email:
raise ValueError("No email address")
self._mailer.send(user.email, subject="Update", body=message)
elif mode == "push":
self._push_broker.notify(user.device_token, message)
else:
raise ValueError(f"Unknown mode: {mode}")
# service.send("smss", user, "Fee due Friday") -> blows up only at runtimeAfter the split:
# AFTER: each channel is its own method
class NotificationService:
def send_sms(self, user, message: str) -> None:
if not user.phone:
raise ValueError("No phone number")
self._sms_gateway.push(user.phone, message[:160])
def send_email(self, user, message: str) -> None:
if not user.email:
raise ValueError("No email address")
self._mailer.send(user.email, subject="Update", body=message)
def send_push(self, user, message: str) -> None:
self._push_broker.notify(user.device_token, message)
# service.send_smss(...) -> AttributeError immediately, and your linter
# flags it before the program even runs. A typo in a string never got that.If one boundary of the app genuinely receives the channel as data — say, the user's saved preference "email" — keep a single dispatcher there: a small dictionary mapping "sms" to service.send_sms, "email" to service.send_email, and "push" to service.send_push. One lookup at the edge; clean named calls everywhere else.
🛠️ IDE support
There is no one-click "Replace Parameter with Explicit Methods" in any mainstream IDE — the split requires human judgement about names and case boundaries. But the surrounding moves are well automated:
- IntelliJ IDEA / PyCharm / Rider / WebStorm — use Extract Method to pull each branch body into its own method (the IDE handles variables and return values), then Find Usages on the original to locate every literal-passing caller, then Safe Delete to confirm nothing still calls the old method before removal. Change Signature (Ctrl+F6) helps remove the now-dead selector and dummy parameters from any surviving dispatcher.
- ReSharper / Visual Studio (C#) — Extract Method, Change Signature, and Inline Method cover the ladder; ReSharper's usage analysis updates or flags every call site across the solution.
- VS Code (TypeScript / C# extensions) — Extract to method/function quick actions plus rename and reference search do the same job with a little more manual stitching.
- A pleasant detail: once split, autocomplete itself becomes documentation — typing
account.listsdeposit,withdraw,printPassbooklike a row of name boards, something a singledoBankingcould never offer.
⚖️ Benefits and risks
| Aspect | Benefit | Risk / Cost |
|---|---|---|
| Safety | Invalid codes become impossible; the "unknown code" branch is deleted | If a runtime-value caller exists, careless deletion of the dispatcher pushes a switch into every such caller |
| Readability | withdraw(5000) reads like a sentence; no legend needed | More methods on the class — a 12-case split is an explosion; prefer polymorphism or a strategy map there |
| Signatures | Each method asks only for what its behaviour needs; dummy arguments vanish | — |
| Performance | The per-call branch disappears (a micro-win; clarity is the real prize) | — |
| Evolution | Each behaviour changes independently, tested in isolation | Every NEW case means editing the class and adding a method; bad fit when cases churn frequently |
🎢 The seesaw: Replace Parameter with Explicit Methods ↔ Parameterize Method
This refactoring and Parameterize Method are exact inverses — two ends of one seesaw. Neither end is "correct"; the shape of the variation decides:
| Question to ask | Tips toward explicit methods (this post) | Tips toward Parameterize Method |
|---|---|---|
| What does the parameter select? | A behaviour — deposit vs withdraw are different actions | A value — 200 ml vs 300 ml feed the same formula |
| Is the case-set small, fixed, discrete? | Yes — a short menu of services | No — any value on a continuum may arrive |
| Does the body branch on the parameter into unrelated blocks? | Yes — a switch with strangers in each branch | No — the value flows straight through one computation |
| Do callers pass literals known at coding time? | Yes — always 1, "height", true | Sometimes a runtime value, like a user-chosen size |
| Is the parameter a cryptic flag or code? | Yes — doBanking(2) needs a legend | No — makeJuice(300) reads naturally |
Plot your own method on the same map we use in the sister post. The banking codes sit deep in the split corner — different behaviour inside, cryptic flag outside:
The two failure smells, in both directions: if your freshly split methods turn out to be twins — same body, one constant different — you tipped the wrong way; merge them back with Parameterize Method. And if your freshly merged method immediately grows if (mode === ...) branches inside, the variants were behaviours all along; split them again. Healthy codebases ride this seesaw repeatedly as understanding deepens.
College corner: there is a type-theory way to say all of this — make illegal states unrepresentable. The selector parameter actionCode: number has millions of inhabitants, of which exactly three are legal; the gap between the type's size and the legal set's size is precisely where bugs live (the aunty's code 4). Splitting into explicit methods shrinks each "type" to exactly its legal inhabitants: the method either exists or it does not. Languages with rich type systems offer middle paths — TypeScript union types like "issue" | "return", C# enums, Rust's exhaustively-matched sums — which keep one entry point while closing the illegal gap. But even with a perfect union type, the readability argument for splitting still stands when the branches are unrelated behaviours: a type can stop wrong codes, but it cannot make performAction("return", ...) read as naturally as returnBook(...).
🩺 Which smells does it cure?
| Smell | How Replace Parameter with Explicit Methods helps |
|---|---|
| Long Method | The internal switch — often the bulkiest part — is dissolved into small single-purpose methods |
| Primitive Obsession | The magic number/string/boolean selector is deleted instead of decorated |
| Long Parameter List | Dragged-along parameters that only some cases used disappear from the other cases' signatures |
| Data Clumps | The split exposes which parameters truly travel together per behaviour, making clumps visible and bundle-able |
| Switch Statements | One central conditional on a type-code is removed at the source |
🧠 The whole idea in one mindmap
📝 Quick revision box
+======= REPLACE PARAMETER WITH EXPLICIT METHODS ======+
| |
| SMELL : doBanking(actionCode, amount) |
| 1 = deposit, 2 = withdraw, 3 = passbook |
| method switches on a code callers |
| always pass as a LITERAL |
| |
| MOVE : one named method per case |
| deposit(amt) / withdraw(amt) / |
| printPassbook() |
| |
| LADDER: 1 map codes to behaviours 2 extract one |
| method per case 3 old method delegates |
| 4 migrate literal callers 5 keep ONE |
| boundary dispatcher iff runtime values |
| 6 delete the code-switch |
| |
| SEESAW: behaviour menu -> explicit methods |
| value continuum -> Parameterize Method |
| (exact inverse refactoring!) |
+======================================================+🏋️ Practice exercise
A railway-station enquiry kiosk runs on this method — refactor it yourself:
class EnquiryKiosk {
handle(option: number, trainNo: string, coach: string): string {
if (option === 1) { // running status
return this.tracker.statusOf(trainNo);
} else if (option === 2) { // platform number
return `Platform ${this.schedule.platformFor(trainNo)}`;
} else if (option === 3) { // coach position
return this.layout.positionOf(trainNo, coach);
} else {
return "INVALID OPTION";
}
}
}
// kiosk.handle(2, "12627", ""); // why is coach "" here?
// kiosk.handle(3, "12627", "S4");Your tasks:
- Make the code-to-behaviour table: 1 → running status, 2 → platform, 3 → coach position.
- Create
runningStatus(trainNo),platformNumber(trainNo), andcoachPosition(trainNo, coach)— note howcoachbelongs to only one of them. - Turn
handleinto a thin delegator, run your tests, then migrate the literal callers. - The kiosk's touch screen sends the tapped option as a number at runtime. Decide: where should the one-and-only dispatcher live, and what should happen to
"INVALID OPTION"? - Stretch question: suppose tomorrow a fourth case appears — option 4, "fare between two stations" — and then options 5 through 12 follow within a year. At what point would you stop adding explicit methods and switch to a different design (a map of option handlers, or polymorphism)? Write down your personal threshold and your reason.
- Seesaw check: a teammate proposes merging
platformNumberandcoachPositioninto one methodlocate(trainNo, what)because "both find where something is". Use the decision table from this post to argue which way the seesaw should tip — and why. - Chart it: place the kiosk's
handlemethod on the quadrant chart from Figure 10. Then place the mergedlocate(trainNo, what)your teammate proposed. Which quadrant does each fall in, and what does that tell you?
Frequently asked questions
- What does Replace Parameter with Explicit Methods actually change?
- It takes one method that receives a code or flag — like doBanking(1, amount) where 1 means deposit and 2 means withdraw — and splits it into separate, clearly named methods: deposit(amount) and withdraw(amount). The branching parameter disappears, and each behaviour gets its own front door with a name board.
- How is this different from just renaming a method?
- Renaming changes one label. This refactoring removes a runtime decision. Before, the method received a value and used an if or switch to choose what to do. After, the caller chooses directly by calling the right method, so the decision happens at compile time, invalid codes become impossible, and each method body holds only one behaviour.
- When should I NOT split a parameter into explicit methods?
- When the value arrives as genuine runtime data — say, the action comes from a parsed file or user input — splitting just pushes the same switch out to every caller. Also avoid it when there are very many cases (you would create a method explosion; prefer a strategy map or polymorphism) or when new cases are added every month, since each one would mean editing the class.
- Is this refactoring in Fowler's second edition?
- Not under this name. It comes from the first edition (1999) and remains on Refactoring Guru and SourceMaking. The second edition covers its most common special case as Remove Flag Argument — splitting a method that takes a boolean flag — and handles the bigger multi-case situations with Replace Conditional with Polymorphism.
- What is its relationship to Parameterize Method?
- They are exact inverses on a seesaw. If variants differ only by a value on a continuum — 5%, 10%, 20% — merge them with Parameterize Method. If a parameter is a cryptic selector that picks between genuinely different behaviours, split it with explicit methods. Codebases legitimately move both ways as designs evolve.
Further reading
Related Lessons
Parameterize Method: One Juice Recipe with a Size Input
Learn the Parameterize Method refactoring with a juice stall story, TypeScript and C# examples, safe step-by-step mechanics, and the seesaw rule that pairs it with Replace Parameter with Explicit Methods.
Replace Parameter with Method Call: Don't Tell the Shopkeeper His Own Prices
Learn the Replace Parameter with Method Call refactoring (Replace Parameter with Query in Fowler's 2nd edition) with a kirana shop story, TypeScript and C# examples, safe mechanics, and the testability fine print.
Preserve Whole Object: Show the Whole ID Card
Learn the Preserve Whole Object refactoring with a school ID card story, TypeScript and C# examples, safe step-by-step mechanics, and an honest look at the coupling cost of passing whole objects.
Long Parameter List: The Chai Order That Took Ten Instructions
Long Parameter List code smell made simple — why methods with too many arguments cause bugs, and how parameter objects make calls short, clear, and safe.