Replace Parameter with Explicit Methods: গোপন কোড নয়, নামের বোর্ড লাগাও
Replace Parameter with Explicit Methods refactoring শেখো একটা ব্যাংক কাউন্টারের গল্পের মাধ্যমে — TypeScript আর Python উদাহরণ, safe mechanics, আর seesaw rule যেটা Parameterize Method-এর সাথে এর সম্পর্ক বোঝায়।
গোপন কোডের কাউন্টারের গল্প
ধরো তুমি প্রতি মাসে তোমার দাদুবাড়িতে টাকা পাঠাও একটা ছোট cooperative bank-এর মাধ্যমে। একদিন তুমি গিয়ে দেখলে ব্যাংকে মাত্র একটাই কাউন্টার। উপরে বোর্ড লেখা: "সব কাজ এখানে। কোড নম্বর বলো।" নিচে ফ্যাকাশে হয়ে যাওয়া কালিতে লেখা: কোড ১ = জমা। কোড ২ = উত্তোলন। কোড ৩ = পাসবুক এন্ট্রি। কোড ৪ = নতুন একাউন্ট। কোড ৫ = ডিমান্ড ড্রাফট।
তোমার বাবা বললেন, "কোড ২, পাঁচ হাজার।" ক্লার্ক — নামপ্লেটে লেখা জামাল সাহেব — "২" শুনে দ্বিতীয় ড্রয়ার খুললেন, withdrawal form বের করলেন, লিখতে শুরু করলেন।
পেছনে একজন বৃদ্ধ চাচা এগিয়ে এলেন। বললেন "কোড ৩"... না, "কোড ২"... দাড়ি চুলকালেন। কোড মনে নেই। জামাল সাহেব আবার পুরো তালিকা পড়লেন — আজ সকাল থেকে নবমবার। একজন আন্টি বললেন "কোড ৪, পাঁচ হাজার" — কিন্তু তিনি আসলে জমা দিতে চেয়েছিলেন, নতুন একাউন্ট খুলতে না। বিশ মিনিট গোলমাল, form ছেঁড়া, ক্ষমা চাওয়া। এগারোটা বাজতে না বাজতে জামাল সাহেব banking-এর চেয়ে বেশি সময় দিচ্ছেন কোড বোঝাতে।
এখন ভাবো তোমার school-এর কাছের বড় ব্যাংকের কথা। সেখানে পাঁচটা কাউন্টার সারি দিয়ে আছে, প্রতিটায় বড় বোর্ড: জমা। উত্তোলন। পাসবুক প্রিন্ট। নতুন একাউন্ট। ডিমান্ড ড্রাফট। কেউ কোড মুখস্থ করে না। কেউ ভুল service চাইতে পারে না — কারণ তুমি সরাসরি ওই কাউন্টারে যাও যেটার বোর্ডে তুমি যা চাও সেটা লেখা আছে।
এখানে মূল কথাটা হলো: গোপন কোডের কাউন্টারে customer আসার আগেই জানে সে কী চায়। "২" কোডটা কোনো data বহন করে না — এটা শুধু একটা behaviour বেছে নেয়। তাহলে clerk-কে প্রতিবার decode করতে দেওয়া কেন? সিদ্ধান্তটা যার কাছে তথ্য আছে তার কাছেই রাখো: customer সরাসরি নামের বোর্ড দেখে কাউন্টারে যাক।
দেখো conversation টা কেমন বদলে যায় যখন সিদ্ধান্তটা caller-এর দিকে চলে যায়:
Code-এও এই গোপন কোডের সমস্যা হয়। একটা method যেমন doBanking(actionCode, amount) — যেটা সাথে সাথে actionCode-এ switch করে — সেটা হলো এক কাউন্টারের ব্যাংক। প্রতিটা call-এ magic number pass হয়, method টা এমন একটা সিদ্ধান্ত আবার নেয় যেটা caller আগে থেকেই জানত, আর ভুল code টুপ করে ঢুকে যায়। এর সমাধান হলো Replace Parameter with Explicit Methods: প্রতিটা behaviour-কে নিজের নামের বোর্ড সহ আলাদা কাউন্টার দাও।
Replace Parameter with Explicit Methods আসলে কী?
Replace Parameter with Explicit Methods হলো সেই refactoring যেখানে তুমি এমন একটা method নাও যার parameter শুধু বেছে দেয় কোন behaviour চালাবে, আর সেটাকে ভেঙে প্রতিটা behaviour-এর জন্য একটা করে clearly নামকরণ করা method বানাও, আর সেই selector parameter মুছে ফেলো।
সমস্যাটা চেনার উপায়: একটা method enum, string, number code, বা boolean receive করে, আর প্রথম কাজ হিসেবেই সেটায় branch করে — if, else if, switch — এমন block-এ যেগুলো একদম আলাদা কাজ করে। Caller প্রায় সবসময় literal pass করে: doBanking(1, 500), setDimension("height", 10), book(true)। এই parameter একটা computation-এ data নয়; এটা একটা menu choice, যেটা programmer call লেখার সময়ই নিয়েছে।
এই technique টা Martin Fowler-এর Refactoring-এর প্রথম edition (1999) থেকে এসেছে। Second edition (2018)-এ এর সবচেয়ে বিখ্যাত special case নিজের entry পেয়েছে: Remove Flag Argument, boolean দিয়ে behaviour switch করা method-এর জন্য। আর বড় case-set-এর জন্য Replace Conditional with Polymorphism। তিনটার মূল কথা এক: caller যে সিদ্ধান্ত আগেই নিয়েছে সেটা method-এর ভেতরে runtime-এ আবার নেওয়া উচিত না।
এক কাউন্টারের ব্যাংক কতটা খারাপ? একটু চার্টে দেখো:
প্রায় অর্ধেক effort যাচ্ছে code system টাকে চালু রাখতে — বোঝাতে আর ভুল ঠিক করতে। Code-এ switch-on-selector method-ও ঠিক এই cost নেয়: প্রতিটা reader decode করে, প্রতিটা reviewer literal double-check করে, আর ভুল code runtime-এ চমকে দেয়।
Split করলে কী লাভ?
- ভুল call অসম্ভব। কেউ code 7 pass করতে পারবে না এমন কাউন্টারে যেটার অস্তিত্বই নেই — call করার কোনো method নেই। Compiler সেটা enforce করে যা ফ্যাকাশে legend কখনো পারত না।
- Call site পড়তে sentence-এর মতো লাগে।
account.withdraw(5000)বুঝতে কোনো legend লাগে না।account.doBanking(2, 5000)লাগে। - প্রতিটা body একটাই কাজ করে। Switch উধাও হয়ে যায়; প্রতিটা method শুধু একটা behaviour ধরে — test করা সহজ, বদলানো সহজ।
- Autocomplete হয়ে যায় menu।
account.টাইপ করলে deposit, withdraw, printPassbook দেখায় — নামের বোর্ডের সারির মতো।
একটা লাইনে মনে রাখো: যদি parameter উত্তর দেয় "কোন কাজ করবে?" এর পরিবর্তে "কী data দিয়ে করবে?", তাহলে parameter মুছে ফেলো আর প্রতিটা "কোন কাজ"-এর জন্য আলাদা নামের method দাও। Data-র জায়গা parameter-এ; সিদ্ধান্তের জায়গা নামে।
একটু deeper যাই: boolean-এর special case-এর একটা বিখ্যাত নাম আছে — flag argument smell। Martin Fowler আর Robert C. Martin দুজনই এটা নিয়ে বলেছেন: একটা function যেটা boolean নিয়ে দুটো behaviour-এর মধ্যে choose করে, সে আসলে জোরে জোরে বলছে সে দুটো কাজ করে — যেটা function level-এ single-responsibility ভাঙে। render(true) call site-এ একদম unreadable; renderForPrint() নিজেই বলে দেয় কী করছে। আর flag argument caller-কে callee-র ভেতরের structure-এর সাথে couple করে দেয় — caller-কে জানতে হয় method টা ভেতরে fork করে, আর কোন literal কোন fork select করে। Split করলে সেই coupling চলে যায়।
একটা warning: এই refactoring-এর exact inverse হলো Parameterize Method। যদি "cases" গুলো আসলে শুধু একটা value-এ আলাদা — 5%, 10%, 20% — তাহলে split করা ভুল দিক। এই post-এর শেষের দিকে seesaw table আর quadrant chart দিয়ে সিদ্ধান্ত নাও।
কখন দরকার?
এই লক্ষণগুলো দেখলে বুঝবে:
- Caller সবসময় literal pass করে।
doBanking(...)-এর সব call site search করো। যদি প্রথম argument সবসময় hard-coded1,2,"deposit", বাtrueহয় — কখনো runtime-এ compute করা variable না — তাহলে parameter টা compile-time সিদ্ধান্ত runtime-এর পোশাকে আছে। Split করো। - Cryptic flag বা code।
process(order, true, false)— boolean দুটোর মানে কী? প্রতিটা reader-কে method খুলে দেখতে হবে। Boolean আর number code সবচেয়ে বড় অপরাধী; এটা Primitive Obsession-এরও একটা নমুনা। - Method-এর body হলো parameter-এর উপর switch, branch-গুলো একদম আলাদা। Deposit logic টাকা জমার record করে; withdrawal balance আর daily limit check করে; draft অন্য branch-এর সাথে কথা বলে। এরা কোনো logic share করে না — শুধু একটা method-এর ছাদের নিচে আছে বলে একসাথে।
- "Unknown code" error branch আছে।
default: throw new Error("bad action")হলো clerk-এর code 7 দেখে কাঁধ ঝাঁকানো। Split করলে এই branch উধাও হয়ে যায় — type system করে দেয়। - Long Parameter List চাপ। Selector parameter প্রায়ই বন্ধু টেনে আনে —
doBanking(code, amount, accountNo, draftPayee, draftBranch)যেখানেdraftPayeeশুধু code 5-এর কাজে লাগে। Split করলে প্রতিটা explicit method শুধু তার নিজের behaviour-এর দরকারি parameter-ই চায়, সরাসরি Long Parameter List ছোট করে। আর যদি বাকি parameter-গুলো একসাথে ঘুরতে থাকে — payee, branch, IFSC সবসময় একসাথে — সেটা Data Clumps-এর ইঙ্গিত।
কখন না করবে:
- Value টা সত্যিকারের runtime data। যদি action code কোনো parsed file বা user-এর menu tap থেকে আসে, কেউ না কেউ সেটায় branch করবেই। Method split করলে শুধু সেই switch প্রতিটা caller-এ ছড়িয়ে যাবে — আরো খারাপ। এক জায়গায় একটা thin dispatcher রাখো।
- অনেক বেশি case। 12-way switch split করলে 12টা method হয়; class ফেটে যায়। Strategy map বা polymorphism ভালো।
- নতুন case প্রায়ই আসে। প্রতিটা নতুন case মানে class edit করা আর নতুন method যোগ করা। Case-set যদি সপ্তাহে সপ্তাহে বদলায়, extensible design বেশি মানায়।
Standard decision flow:
আগে আর পরে এক নজরে
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 passedতিনটা আলাদা behaviour একটা দরজা, একটা signature, আর passbook printing-এর জন্য একটা অর্থহীন amount share করছে। এখন নামের বোর্ড সহ ব্যাংক:
// 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 neededদেখো সাথে কী কী bonus মিলল। printPassbook()-এর অর্থহীন amount parameter চলে গেল। Unknown action code branch উধাও হলো। আর প্রতিটা method-এর signature তার নিজের কাজের সাথে মিলে গেল।
ধাপে ধাপে, নিরাপদে
doBanking এক ধাক্কায় মুছে ফেলো না — কোনো দূরের caller রাতে ভেঙে পড়বে। Codebase টা deliberate, reversible অবস্থার মধ্য দিয়ে যাওয়া উচিত:
ধাপগুলো follow করো:
- প্রতিটা value আর তার behaviour map করো। প্রতিটা branch পড়ো। একটা ছোট table লেখো: 1 → deposit, 2 → withdraw, 3 → passbook। সব call site-ও search করো: কোনো caller কি literal-এর বদলে variable pass করে? সেগুলো mark করো — ধাপ 6-এ আলাদা handle করতে হবে।
- প্রতিটা case-এর জন্য একটা করে explicit method তৈরি করো, শুধু সেই branch-এর body কপি করে। Original method ছুঁয়ো না। প্রতিটা নতুন method-এ behaviour বলা নাম দাও, আর শুধু সেই behaviour-এর দরকারি parameter রাখো।
- পুরানো method-কে নতুনগুলোতে delegate করাও। এই intermediate অবস্থায় সব existing caller কাজ করতে থাকে, আর duplication চলে যায়:
// 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}`);
}
}- সব test চালাও। Behaviour অবশ্যই একই থাকবে — same deposit, same error, same history entry।
- Literal-passing caller-দের migrate করো, এক batch করে।
account.doBanking(2, 5000)হয়ে যাবেaccount.withdraw(5000)। Literal দেখেই বুঝবে কোন method substitute করতে হবে। প্রতিটা batch-এর পরে test করো। - Runtime-value caller থাকলে সামলাও। যদি কোনো caller user input বা file থেকে action পড়ে, সেই boundary-তে একটামাত্র thin dispatcher রাখো — সেটা runtime value-কে explicit method-এ map করবে। Dispatch হবে একবার, edge-এ — domain logic-এর ভেতরে না।
- কোনো caller না থাকলে পুরানো method মুছে ফেলো (বা শুধু boundary dispatcher রাখো)। Unknown code-এর test সরাও; প্রতিটা method-এর জন্য আলাদা test যোগ করো।
Classic ভুল: check না করেই ধরে নেওয়া সব caller literal pass করে। যদি একটাও caller account.doBanking(codeFromFile, amt) করে আর তুমি dispatcher মুছে ফেলো, সেই caller-কে নিজের switch লিখতে হবে — আর পরের এমন caller আরেকটা লিখবে। ধাপ 7-এর আগে সব call site grep করো। Compile-time selector cleanly split হয়; runtime selector-এর জন্য ঠিক একটা boundary dispatcher দরকার।
বড় real-life উদাহরণ
ধরো তোমার school-এর library software-এ একই সমস্যা, আর সাথে dragged-along parameters-এর ঝামেলা:
// 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... nothingপ্রতিটা branch আলাদা জগত: issue করা loan limit check করে, return fine compute করে, renew reservation check করে, reserve queue manage করে। এরা কোনো logic share করে না — শুধু একটা ছাদের নিচে আছে। আর দেখো caller-কে দুটো zero দিতে হচ্ছে শুধু signature-এর জন্য।
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 mistypeলাভের হিসাব করো: switch উধাও; প্রতিটা signature শুধু সত্যিকারের parameter বহন করছে; "isue" (typo) এখন runtime-এ চমক দিতে পারবে না; আর প্রতিটা method আলাদাভাবে test করা যাচ্ছে। একটু লক্ষ্য রাখো: memberId, bookId প্রতিটা method-এ একসাথে ঘুরছে — আরো বন্ধু জুড়লে সেটা Data Clumps-এর ইঙ্গিত।
Dummy argument-এর সমস্যাটা chart-এ দেখো। আগের design-এ প্রতিটা call পাঁচটা argument দিতে হত যেকোনো action-এর জন্য; split করার পরে প্রতিটা call শুধু তার নিজের কাজের argument দেয়:
পাঁচটা argument — দুটো অর্থহীন zero — থেকে দুটো সত্যিকারের argument। প্রতিটা call site আর প্রতিটা future reader যে "কেন zero?" ভাবে — সেই cost গুলো যোগ করো, আর গোপন কোডের কাউন্টারের দাম বুঝতে পারবে।
একটু গভীরে: "কখন split না করবে"-র তালিকাটা আসলে Open-Closed Principle। প্রতিটা case-এর জন্য explicit method সহ class ভুল call-এর বিরুদ্ধে closed, কিন্তু modification-এর জন্য open — প্রতিটা নতুন case class edit করতে হয়। অনেক বেশি case বা বার বার চেঞ্জ হওয়া menu-র জন্য Replace Conditional with Polymorphism বা registry/strategy map ব্যবহার করো। Replace Parameter with Explicit Methods সবচেয়ে ভালো কাজ করে ছোট, স্থিতিশীল, policy-মতো menu-তে — ব্যাংক counter service, library desk action।
Python-এ একই refactoring
Python-এ compiler নেই code police করার জন্য, তাই secret-code method আরো বিপজ্জনক — আর নামের বোর্ড আরো বেশি দরকারি:
# 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 runtimeSplit করার পরে:
# 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.যদি app-এর কোনো boundary-তে সত্যিই channel data হিসেবে আসে — যেমন user-এর saved preference "email" — সেখানে একটামাত্র dispatcher রাখো: একটা dictionary যেটা "sms"-কে service.send_sms-এ, "email"-কে service.send_email-এ, "push"-কে service.send_push-এ map করে। Edge-এ একটা lookup; বাকি সব জায়গায় clean named call।
IDE-তে কী কী সাহায্য পাওয়া যায়
কোনো mainstream IDE-তে এক-click "Replace Parameter with Explicit Methods" নেই — split করতে নাম আর case boundary নিয়ে মানুষের judgement লাগে। কিন্তু surrounding move গুলো ভালোভাবে automate হয়:
- IntelliJ IDEA / PyCharm / Rider / WebStorm — Extract Method দিয়ে প্রতিটা branch body আলাদা method-এ pull করো, তারপর Find Usages দিয়ে প্রতিটা literal-passing caller খোঁজো, তারপর Safe Delete দিয়ে confirm করো কেউ আর পুরানো method call করছে না কিনা। Change Signature (Ctrl+F6) dead selector আর dummy parameter সরাতে সাহায্য করে।
- ReSharper / Visual Studio (C#) — Extract Method, Change Signature, আর Inline Method মিলে কাজ হয়; ReSharper-এর usage analysis সব call site update বা flag করে।
- VS Code (TypeScript / C# extensions) — Extract to method/function quick action আর rename আর reference search দিয়ে একটু বেশি manual stitching-এ একই কাজ হয়।
- একটা সুন্দর ব্যাপার: split করার পরে autocomplete নিজেই documentation হয়ে যায় —
account.টাইপ করলেdeposit,withdraw,printPassbookদেখায় নামের বোর্ডের সারির মতো।
সুবিধা আর ঝুঁকি
| দিক | সুবিধা | ঝুঁকি / খরচ |
|---|---|---|
| Safety | Invalid code অসম্ভব; "unknown code" branch মুছে যায় | Runtime-value caller থাকলে dispatcher মুছলে সেই switch প্রতিটা caller-এ ছড়িয়ে পড়ে |
| Readability | withdraw(5000) sentence-এর মতো পড়ায়; legend লাগে না | Class-এ বেশি method — 12-case split explosion; সেখানে polymorphism বা strategy map ভালো |
| Signatures | প্রতিটা method শুধু তার behaviour-এর parameter চায়; dummy argument উধাও | — |
| Performance | Per-call branch চলে যায় (micro-win; clarity টাই আসল পুরস্কার) | — |
| Evolution | প্রতিটা behaviour স্বাধীনভাবে বদলায়, আলাদাভাবে test হয় | প্রতিটা নতুন case class edit করতে হয়; case যদি প্রায়ই চেঞ্জ হয়, ভালো fit না |
Seesaw: Replace Parameter with Explicit Methods আর Parameterize Method
এই refactoring আর Parameterize Method exact inverse — একটা seesaw-এর দুই দিক। কোনো দিকটাই "সঠিক" না; variation-এর shape সিদ্ধান্ত নেয়:
| জিজ্ঞেস করো | Explicit methods (এই post) দিকে | Parameterize Method দিকে |
|---|---|---|
| Parameter কী select করে? | একটা behaviour — deposit বনাম withdraw আলাদা action | একটা value — 200 ml বনাম 300 ml একই formula-তে যায় |
| Case-set কি ছোট, fixed, discrete? | হ্যাঁ — service-এর ছোট menu | না — continuum-এর যেকোনো value আসতে পারে |
| Body কি parameter-এ unrelated block-এ branch করে? | হ্যাঁ — switch-এর প্রতিটা branch আলাদা জগত | না — value সরাসরি একটা computation-এ যায় |
| Caller কি coding time-এ জানা literal pass করে? | হ্যাঁ — সবসময় 1, "height", true | কখনো কখনো runtime value, যেমন user-chosen size |
| Parameter কি cryptic flag বা code? | হ্যাঁ — doBanking(2) legend ছাড়া বোঝা যায় না | না — makeJuice(300) স্বাভাবিকভাবে পড়া যায় |
তোমার method টা একই map-এ plot করো। Banking code গুলো split corner-এ — ভেতরে আলাদা behaviour, বাইরে cryptic flag:
দুই দিকের failure smell: যদি তোমার freshly split method গুলো twins হয়ে যায় — একই body, একটা constant আলাদা — তুমি ভুল দিকে গেছ; Parameterize Method দিয়ে merge করো। আর যদি freshly merged method-এ সাথে সাথে if (mode === ...) branch গজায়, variants আসলে behaviour ছিল; আবার split করো। Healthy codebase এই seesaw-এ বারবার চড়ে।
Type theory-তে বললে: selector parameter actionCode: number-এর লক্ষ লক্ষ possible value আছে, যেখানে মাত্র তিনটা legal; সেই gap-এই bugs বাস করে। Explicit method-এ split করলে প্রতিটা "type" ঠিক তার legal inhabitant-এ সঙ্কুচিত হয়: method হয় আছে নয় নেই। TypeScript union type "issue" | "return", C# enum, Rust-এর exhaustive sum — এগুলো middle path, কিন্তু এমনকি perfect union type দিয়েও readability argument টা থাকে: type ভুল code থামাতে পারে, কিন্তু performAction("return", ...) কে returnBook(...)-এর মতো natural করতে পারে না।
কোন smell গুলো সারায়?
| Smell | Replace Parameter with Explicit Methods কীভাবে সাহায্য করে |
|---|---|
| Long Method | ভেতরের switch — প্রায়ই সবচেয়ে বড় অংশ — ছোট single-purpose method-এ ভেঙে যায় |
| Primitive Obsession | Magic number/string/boolean selector মুছে যায়, সাজিয়ে রাখার বদলে |
| Long Parameter List | Dragged-along parameter যেগুলো শুধু কিছু case-এ কাজে লাগে, অন্য case-এর signature থেকে চলে যায় |
| Data Clumps | Split করলে বোঝা যায় কোন parameter গুলো সত্যিই একসাথে চলে, clump visible হয় |
| Switch Statements | Type-code-এ central conditional source থেকে সরে যায় |
পুরো আইডিয়া একটা mindmap-এ
দ্রুত 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!) |
+======================================================+নিজে try করো
ধরো একটা railway station-এর enquiry kiosk এই method চালায় — তুমি নিজে refactor করো:
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");তোমার কাজ:
- Code-to-behaviour table বানাও: 1 → running status, 2 → platform, 3 → coach position।
runningStatus(trainNo),platformNumber(trainNo), আরcoachPosition(trainNo, coach)তৈরি করো — লক্ষ্য করোcoachশুধু একটাতে দরকার।handle-কে thin delegator বানাও, test চালাও, তারপর literal caller-দের migrate করো।- Kiosk-এর touch screen tapped option runtime-এ number হিসেবে পাঠায়। সিদ্ধান্ত নাও: একমাত্র dispatcher কোথায় থাকবে, আর
"INVALID OPTION"-এর কী হবে? - Stretch প্রশ্ন: ধরো কাল চতুর্থ option আসে — option 4, "দুটো station-এর মধ্যে fare" — আর এক বছরের মধ্যে option 5 থেকে 12 আসে। কোন point-এ তুমি explicit method যোগ করা বন্ধ করে অন্য design (option handler-এর map, বা polymorphism) নেবে? তোমার personal threshold আর কারণ লিখো।
- Seesaw check: একজন teammate propose করে
platformNumberআরcoachPositionমিলিয়ে একটা methodlocate(trainNo, what)বানাতে, কারণ "দুটোই কোথায় আছে খোঁজে"। এই post-এর decision table ব্যবহার করে argue করো seesaw কোন দিকে যাবে — আর কেন। - Chart করো: kiosk-এর
handlemethod টা Figure 10-এর quadrant chart-এ কোথায় পড়ে। তারপর teammate-এর proposedlocate(trainNo, what)কোথায় পড়ে। প্রতিটা কোন quadrant-এ, আর সেটা তোমাকে কী বলে?
সচরাচর জিজ্ঞাসা
- Replace Parameter with Explicit Methods আসলে কী পরিবর্তন করে?
- এটা এমন একটা method নেয় যেটা একটা code বা flag receive করে — যেমন doBanking(1, amount) যেখানে 1 মানে deposit আর 2 মানে withdraw — আর সেটাকে ভেঙে আলাদা আলাদা নামের method বানায়: deposit(amount) আর withdraw(amount)। সেই branching parameter আর থাকে না, আর প্রতিটা behaviour-এর নিজস্ব নামের বোর্ড সহ একটা আলাদা দরজা তৈরি হয়।
- এটা কি শুধু method rename করার মতো?
- না, rename মানে শুধু একটা label বদলানো। এই refactoring একটা runtime decision সরিয়ে দেয়। আগে method একটা value receive করে if বা switch দিয়ে সিদ্ধান্ত নিত কী করবে। পরে caller নিজেই সঠিক method call করে সিদ্ধান্ত নেয় — ফলে decision টা compile time-এ হয়, ভুল code অসম্ভব হয়ে যায়, আর প্রতিটা method body শুধু একটাই behaviour ধরে।
- কখন parameter-কে explicit method-এ split করা উচিত না?
- যখন value টা সত্যিকারের runtime data হিসেবে আসে — যেমন কোনো file parse করে বা user input থেকে action আসছে — তখন split করলে সেই switch টা সব caller-এর মধ্যে ছড়িয়ে পড়বে। আর যখন অনেক বেশি case আছে (method explosion হবে; সেক্ষেত্রে strategy map বা polymorphism ভালো), অথবা প্রতি মাসে নতুন case যুক্ত হচ্ছে — তখনও split না করাই ভালো।
- এই refactoring কি Fowler-এর second edition-এ আছে?
- এই নামে নেই। এটা প্রথম edition (1999) থেকে এসেছে আর Refactoring Guru আর SourceMaking-এ আছে। Second edition-এ এর সবচেয়ে common special case হিসেবে Remove Flag Argument আছে — যেটা boolean flag নেওয়া method split করার কথা বলে। আর বড় multi-case situation-এর জন্য Replace Conditional with Polymorphism আছে।
- Parameterize Method-এর সাথে এর সম্পর্ক কী?
- এরা একদম উল্টো — একটা seesaw-এর দুই দিক। যদি variants শুধু একটা value-এ আলাদা হয় — 5%, 10%, 20% — তাহলে Parameterize Method দিয়ে merge করো। যদি parameter একটা cryptic selector হয় যেটা সত্যিই আলাদা behaviour বেছে নেয়, তাহলে explicit method দিয়ে split করো। Codebase-এ design evolve হওয়ার সাথে সাথে দুই দিকেই যাওয়া স্বাভাবিক।
আরো দেখো
সম্পর্কিত পাঠ
Parameterize Method: একটাই জুসের রেসিপি, শুধু সাইজটা দিয়ে দাও
জুসের দোকানের গল্পের মাধ্যমে Parameterize Method রিফ্যাক্টরিং শেখো — TypeScript আর C# উদাহরণ সহ, নিরাপদ ধাপে ধাপে mechanics, আর সেই সিস্যার নিয়ম যেটা Replace Parameter with Explicit Methods-এর সাথে জুটি বাঁধে।
Replace Parameter with Method Call: দোকানদারকে তার নিজের দাম পড়ে শোনাতে যেও না
Replace Parameter with Method Call refactoring শেখো চায়ের দোকানের একটা মজার গল্পের মাধ্যমে — TypeScript আর C# উদাহরণসহ, নিরাপদ ধাপে ধাপে পদ্ধতি, আর testability-র সৎ হিসাব।
Preserve Whole Object: পুরো ID Card দেখাও
Preserve Whole Object refactoring শেখো একটা school ID card-এর গল্প দিয়ে — TypeScript আর C# example সহ, safe step-by-step mechanics, আর object pass করলে coupling বাড়ে কিনা সেটার সৎ আলোচনা।
Long Parameter List: দশটা নির্দেশনার চায়ের অর্ডার
Long Parameter List কোড স্মেল সহজ ভাষায় — কেন বেশি argument-এর method বাগ তৈরি করে, আর কীভাবে parameter object দিয়ে call ছোট, পরিষ্কার আর নিরাপদ করা যায়।