মূল বিষয়বস্তুতে যান
Clean Code Mastery

Replace Parameter with Method Call: দোকানদারকে তার নিজের দাম পড়ে শোনাতে যেও না

Replace Parameter with Method Call refactoring শেখো চায়ের দোকানের একটা মজার গল্পের মাধ্যমে — TypeScript আর C# উদাহরণসহ, নিরাপদ ধাপে ধাপে পদ্ধতি, আর testability-র সৎ হিসাব।

23 মিনিট আপডেট: June 11, 2026beginner
refactoringreplace parameter with method callreplace parameter with querysimplifying method callslong parameter listclean code

দোকানদারকে তার নিজের দামের তালিকা পড়ে শোনানো ছেলের গল্প

ধরো রুবেল ক্লাস সেভেনে পড়ে। প্রতি রোববার তার মা তাকে করিম চাচার কিরানা দোকানে পাঠান একটা তালিকা নিয়ে: এক কেজি মসুর ডাল, আধা কেজি চিনি, এক প্যাকেট চা।

প্রথম রোববার রুবেল একটা আজব কাজ করে। বাড়ি থেকে বের হওয়ার আগে সে গত মাসের রেট কার্ড দেখে দাম লিখে নেয়। তারপর কাউন্টারে গিয়ে ঘোষণা করে: "চাচা, মসুর ডাল প্রতি কেজি ১৬০ টাকা — তাহলে ১৬০। চিনি প্রতি কেজি ৪৫, আধা কেজি মানে ২২ টাকা ৫০ পয়সা। চা বড় প্যাকেট ২৫০ টাকা। মোট ৪৩২ টাকা ৫০। এই নাও।"

করিম চাচা চশমার উপর দিয়ে তাকিয়ে একটু অবাক, একটু মজা পেয়ে বললেন: "বাবা — এটা কার দোকান?"

"তোমার, চাচা।"

"আর ওই দামের তালিকা কার?" তিনি পাশে ঝোলানো লেমিনেটেড শিটে আঙুল দিলেন। আজকের দাম তাজা কালিতে লেখা। "মসুর ডাল মঙ্গলবার দাম বাড়ল। এখন ১৭০। তুমি কেন আমাকে আমার নিজের দাম শোনাতে এসেছ? তুমি বলো কী লাগবে আর কতটুকু লাগবে। দাম আমার কাজ। আমি আমার নিজের তালিকা দেখব — সেটা সবসময় আপডেট।"

রুবেল লজ্জায় লাল। তাই তো। দোকানদার নিজেই দামের তালিকার মালিক। ওটা তার কনুইয়ের পাশেই ঝোলানো। সবসময় আপডেট।

রুবেল যখন বাড়ি থেকে দাম টুকে আনে, তিনটা সমস্যা হয়। প্রতিবার বাজারে যাওয়ার আগে বাড়তি কাজ করতে হয়। তার নোট করা দাম পুরনো হতে পারে — ডালের দাম মঙ্গলবার বাড়ল! আর যদি পাশের জামাল-ও এভাবে বাজার করে, তাহলে দুইজন দুটো আলাদা কপি রাখছে এমন একটা তালিকার যেটা আসলে একটাই, কাউন্টারে ঝোলানো, একদম সঠিক।

সেই রোববার থেকে রুবেল শুধু বলে: "এক কেজি মসুর ডাল, আধা কেজি চিনি, এক প্যাকেট চা।" চাচা নিজের তালিকা দেখেন, হিসাব করেন, শেষ। তথ্যটা যেখানে থাকে সেখানেই রইল।

চিত্র ১: রুবেলের রোববার — দাম বহন করা বনাম শুধু কেনার তালিকা বহন করা

দুটো কথোপকথন পাশাপাশি দেখো। প্রথমটায় রুবেল এমন তথ্য দিচ্ছে যেটা কাউন্টারে আগে থেকেই আছে। দ্বিতীয়টায় সে শুধু সেটাই দিচ্ছে যেটা শুধু সে জানে — তার মা যে items আর quantities লিখে দিয়েছেন:

চিত্র ২: caller শুধুই সেটা পাঠাবে যেটা receiver নিজে আনতে পারে না

কোড রুবেলের প্রথম রোববারের ভুল বারবার করে। একটা caller একটা মান হিসাব করে — discount level, tax rate, আজকের date band — তারপর সেটা parameter হিসেবে এমন একটা method-এ পাঠায় যেটা নিজেই একই উৎস থেকে দেখে নিতে পারত। প্রতিটা caller বারবার lookup করে। Callers পুরনো বা inconsistent মান পাঠাতে পারে। Signature বড় হতেই থাকে।

এর সমাধানকে বলে Replace Parameter with Method Call — Fowler-এর দ্বিতীয় সংস্করণে এটার নাম Replace Parameter with Query: parameter মুছে দাও আর method-কে নিজেই মান আনতে দাও।

Replace Parameter with Method Call আসলে কী?

সহজ কথায়, এটা হলো সেই refactoring যেখানে তুমি এমন একটা parameter সরিয়ে দাও যার মান method নিজেই পেতে পারে। Method তখন ভেতরেই সেই উৎস — একটা getter, একটা query, একটা lookup — call করে।

চেনা চেহারাটা হলো এরকম: প্রতিটা call site আগে একটা ছোট কাজ করে —

const level = order.discountLevel();      // the errand
const price = order.discountedPrice(base, level);

— তারপর ফলাফল হাতে দেয়। কিন্তু discountedPrice থাকে সেই একই object-এ যেখানে discountLevel আছে। Method দামের তালিকার পাশে দাঁড়িয়ে ভদ্রভাবে অপেক্ষা করছে — যাতে callers সেটা পড়ে শোনায়।

নামের ব্যাপারে একটু জেনে রাখো, কারণ দুটো নামই দেখতে পাবে। Martin Fowler-এর Refactoring বইয়ের প্রথম সংস্করণে (১৯৯৯) এটার নাম ছিল Replace Parameter with Method Call। Refactoring Guru আর SourceMaking এখনো সেই নাম ব্যবহার করে। দ্বিতীয় সংস্করণ (২০১৮) নাম বদলে Replace Parameter with Query রাখে — এর সরাসরি বিপরীত Replace Query with Parameter-এর পাশে। কারণ কখনো কখনো তুমি উল্টো দিকে যাও — flexibility বা testability-র জন্য একটা lookup parameter-এ টেনে বের করো। দুটো দিকই মাথায় রাখো।

এই refactoring-এর মূল প্রশ্ন হলো: প্রতিটা parameter-এর জন্য, এই তথ্যের স্বাভাবিক মালিক কে? রুবেল যখন তার রোববারের ঘোষণাগুলো খতিয়ে দেখল, ছবিটা ছিল এরকম:

চিত্র ৩: প্রতিটা argument তার সত্যিকারের মালিক অনুযায়ী সাজাও — শুধু caller-এর জ্ঞান parameter হওয়ার যোগ্য

সে যা বলেছিল তার অর্ধেকেরও বেশি ছিল দোকানদারের নিজের তথ্য ফিরিয়ে দেওয়া। এভাবে ফিরিয়ে দেওয়া প্রতিটা মান একটা parameter যেটা এই refactoring মুছে দিতে পারে।

কেন করব? কারণ —

  1. Signature ছোট হয়, ভুল call কমে। একটা কম parameter মানে পাঠানোর, ভুল ক্রমে দেওয়ার, বা fake করার জন্য একটা কম ঝামেলা। মূল নিয়ম: parameter list শুধুই সেটা চাইবে যেটা method সৎভাবে নিজে আনতে পারে না।
  2. Duplicate কাজ নেই। Lookup একবার হবে, ভেতরে — প্রতিটা call-এর আগে copy-paste করার বদলে।
  3. পুরনো বা inconsistent মান থাকে না। Callers আর গত মাসের discount level পাঠাতে পারবে না। Method সবসময় তাজা তালিকা পড়বে।
  4. Call করা সহজ হয়। নতুন callers-এর কোনো preparation step শিখতে হয় না।
💡

মনে রাখার একটাই কথা: কখনো caller-দের এমন method-এ তথ্য পাঠাতে বলো না যেটা method তার নিজের shelf থেকে আনতে পারে। একটা parameter শুধুই সেটা বহন করবে যেটা শুধু caller জানে। বাকি সবটা receiver-এর কাজ — ঠিক যেমন দোকানদারের কাউন্টারে দামের তালিকা।

এক বাক্যে eligibility test, আর এটা কড়া: মান-হিসাব করার কোড caller-এর ভেতরে শুধু বিদ্যমান কোনো কিছুর উপর নির্ভর করতে পারবে না। যদি সেই কোড caller-এর নিজস্ব parameters বা local variables ব্যবহার করে, সেটা receiver-এ যেতে পারবে না। Parameter রাখতেই হবে।

একটু deep ভাবো: এই refactoring আসলে একটা বড় নীতির ছোট প্রকাশ — demand-driven মান pushed copies-এর চেয়ে ভালো। যখন কোনো মান আগে হিসাব করে বহন করা হয় (pushed), প্রতিটা ধাপে সেটা পুরনো হওয়ার সুযোগ থাকে। যখন ব্যবহারের মুহূর্তে আনা হয় (pulled), ঠিক একটাই read হয়, যতটা সম্ভব তাজা। Computer science জুড়ে একই ধারণা দেখা যায়: UI framework-এ derived state হিসাব করা উচিত, store না। Database view recompute করে, duplicate করে না। Cache invalidation বিখ্যাতভাবে কঠিন কারণ cache হলো pull করা সত্যের pushed copy। যে parameter-এর মান receiver query করতে পারত সেটা একটা ক্ষুদ্র, নিরীহ দেখতে cache — সব invalidation সমস্যা নিয়ে, কিন্তু performance justification ছাড়া। কিন্তু যখন তোমার সত্যিই frozen-in-time মান দরকার — যেমন ২৪ ঘণ্টার জন্য quote করা দাম — সেটা আর derivable data না। সেটা genuine caller-knowledge, এবং তার parameter পাওয়ার অধিকার আছে।

কখন দরকার হয়?

এই লক্ষণগুলো দেখলে বুঝবে:

  1. Pre-call কাজ। প্রতিটা call-এর আগে এক বা দুটো লাইন শুধু একটা argument হিসাব করার জন্য আছে। আর সেই হিসাব এমন জিনিস ব্যবহার করছে যেটা called method-ও দেখতে পায় — একই object-এর fields, একই service, একই configuration। সেই কাজটা ভেতরে যাওয়া উচিত।
  2. Long Parameter List এই refactoring সেই smell-এর সহজ সমাধানগুলোর একটা। Parameters bundle করা বা object পাঠানোর আগে, শুধু মুছে দাও যেগুলো method নিজে derive করতে পারে। কোনো নতুন type নেই, কোনো নতুন structure নেই — শুধু কম।
  3. Copy-pasted derivations। তিনটা আলাদা caller সবাই call করার আগে const level = loyaltyYears > 2 ? 2 : 1; লিখছে। একই জ্ঞান তিন জায়গায়। একদিন কেউ দুটো আপডেট করবে, তৃতীয়টা ভুলে যাবে।
  4. Inconsistency bug। Bug report দেখাচ্ছে দুটো screen ভিন্ন total দেখাচ্ছে কারণ দুটো caller একটু আলাদাভাবে "একই" tax band হিসাব করেছে। Parameter private arithmetic-এর সুযোগ দেয়। Internal query-তে ঠিক একটাই উত্তর।
  5. Data Clumps-এর বন্ধু। কখনো কখনো সরানো যায় এমন parameter একটা দলে চলে — (subtotal, discountLevel, taxRate) — যেখানে দুটো derivable আর একটা real data। Derivable গুলো এই refactoring দিয়ে সরাও। যদি genuine clump থেকে যায়, Preserve Whole Object আর Introduce Parameter Object বাকিটা সামলাবে।

আর না-এর উত্তর — এই তালিকাটাও সমান গুরুত্বপূর্ণ:

  • শুধু caller জানে। ব্যবহারকারীর টাইপ করা quantity, caller-specific override, caller-এর নিজের parameters থেকে তৈরি মান — receiver এগুলো আনতে পারবে না। এগুলো সৎ parameters। রাখো।
  • আনলে নতুন dependency তৈরি হবে। যদি method-কে একটা service import করতে হয়, global ধরতে হয়, বা এর আগে কখনো ছোঁয়নি এমন singleton নিতে হয় — তাহলে তুমি একটা দৃশ্যমান parameter-এর বদলে লুকানো coupling নিচ্ছ। দৃশ্যমান জিনিসটাই স্বাস্থ্যকর।
  • Callers ইচ্ছা করে ভিন্ন মান দেয়। যদি কোনো caller একটা ভিন্ন discount level পাঠায় — manager override — বা একবার মান হিসাব করে পুরো loop-এ সেটা ব্যবহার করে, তাহলে parameter আসল কাজ করছে। এটা মুছলে একটা feature মুছে যাবে।
  • হিসাব ব্যয়বহুল বা side effect আছে। Hot loop-এ চলা method-এর ভেতরে slow query নিলে cost গুণ হয়। Side effect নিলে behaviour বদলায়। আগে পরিমাপ করো।
  • Testability খারাপভাবে ক্ষতিগ্রস্ত হবে। Test-এ parameter trivially fake করা যায়। কিন্তু clock, database, বা network পড়ে এমন internal call fake করা ঝামেলার। এই কারণেই Replace Query with Parameter আছে — তুমি উল্টো দিকে গেলে সেটা ব্যবহার করো।

একটা flow-এ সিদ্ধান্তটা দেখো:

চিত্র ৪: রাখব না মুছব? Signature ছোঁয়ার আগে মানটা আসলে কোথায় থাকে সেটা trace করো

এক নজরে আগে আর পরে

TypeScript-এ উৎসব-মৌসুমের একটা order class। আগে — প্রতিটা caller দামের তালিকা জোরে পড়ে শোনাচ্ছে:

class Order {
  constructor(
    private readonly quantity: number,
    private readonly itemPrice: number,
    private readonly customerLoyaltyYears: number,
  ) {}
 
  discountLevel(): number {
    return this.customerLoyaltyYears > 2 ? 2 : 1;
  }
 
  // BEFORE: the method asks callers to deliver values it owns
  finalPrice(basePrice: number, discountLevel: number): number {
    return discountLevel === 2 ? basePrice * 0.9 : basePrice * 0.95;
  }
}
 
// every caller runs the same errand first:
const base = order.quantity * order.itemPrice;     // hmm, also derivable...
const level = order.discountLevel();               // the errand
const payable = order.finalPrice(base, level);

finalPrice থাকে discountLevel-এর মতো একই object-এ। দুটো parameter-ই দোকানদারকে তার নিজের দাম পড়ে শোনানো। Refactoring-এর পরে:

class Order {
  constructor(
    private readonly quantity: number,
    private readonly itemPrice: number,
    private readonly customerLoyaltyYears: number,
  ) {}
 
  private discountLevel(): number {
    return this.customerLoyaltyYears > 2 ? 2 : 1;
  }
 
  private basePrice(): number {
    return this.quantity * this.itemPrice;
  }
 
  // AFTER: self-sufficient — reads its own shelf
  finalPrice(): number {
    return this.discountLevel() === 2
      ? this.basePrice() * 0.9
      : this.basePrice() * 0.95;
  }
}
 
// callers shrink to one honest call:
const payable = order.finalPrice();

দুটো parameter-ই চলে গেল, কারণ দুটোই order থেকে derive করা যায়। আর discountLevel এমনকি private হয়ে গেল, কারণ বাইরের কেউ আর এটা সরাসরি চায় না। Signature দুটো parameter থেকে শূন্যে নামল। ভুলভাবে call করা এখন অসম্ভব — কারণ ভুল করার মতো কিছুই নেই।

Class diagram দেখাচ্ছে queries ভেতরের দিকে ভাঁজ হচ্ছে — public errand-helpers private shelf-read হয়ে যাচ্ছে:

চিত্র ৫: Public helper queries private internals হয়; signature খালি হয়ে যায়
চিত্র ৬: কাজটা ভেতরে চলে যায় — callers আর method-এর নিজের জিনিস deliver করে না

নিরাপদে ধাপে ধাপে

কাজের ক্রমটাই তোমাকে রক্ষা করে। আগে parameter-টাকে অব্যবহৃত করো, প্রমাণ করো কিছু বদলায়নি — তারপরই মুছো। Codebase এই অবস্থাগুলোর মধ্য দিয়ে যায়:

চিত্র ৭: মুছে ফেলার আগে মরা — ignored-parameter অবস্থাটাই built-in bug detector
  1. সৎভাবে eligibility চেক করো। Method কি কোনো নতুন dependency ছাড়া callers যে source ব্যবহার করছে সেখানে পৌঁছাতে পারে? কাজের কোড কী ছোঁয় সেটা trace করো। যদি caller-local variable বা caller parameter ব্যবহার করে, থামো। এই parameter রাখতেই হবে।
  2. কাজটা যদি raw inline code হয়, আগে সেটাকে একটা query method-এ extract করো। Callers যারা loyaltyYears > 2 ? 2 : 1 inline লিখছে তারা আগে Extract Method-এর মাধ্যমে একটা shared discountLevel() পাবে। এখন caller আর receiver দুজনই একটা named source call করতে পারে।
  3. Method-এর ভেতরে parameter-এর ব্যবহার query call দিয়ে replace করো — কিন্তু signature-এ parameter রাখো। এটাই safety trick: signature অপরিবর্তিত, প্রতিটা caller compile হচ্ছে, তবু parameter এখন dead weight।
// Intermediate state: parameter still accepted, silently ignored
finalPrice(basePrice: number, discountLevel: number): number {
  const level = this.discountLevel();        // internal fetch
  return level === 2 ? basePrice * 0.9 : basePrice * 0.95;
}
  1. পুরো test suite চালাও। যদি এখানে কিছু fail করে, মূল্যবান কিছু জেনেছ: কোনো caller এমন মান পাঠাচ্ছিল যেটা internal query-র result থেকে ভিন্ন — একটা override, cached copy, বা পুরনো read। এগিয়ে যাওয়ার আগে তদন্ত করো। সেই পার্থক্য হয় একটা bug যেটা তুমি এইমাত্র পেলে, অথবা এমন একটা feature যেটা মুছতে পারবে না।
  2. Parameter সরাও — IDEs এটা Change Signature-এর অংশ হিসেবে automate করে — আর প্রতিটা caller-এ এখন অর্থহীন errand line মুছে দাও। Compiler আর unused-variable warning তোমাকে প্রতিটা site-এ নিয়ে যাবে।
  3. পরবর্তী derivable parameter-এর জন্য আবার করো — আমাদের উদাহরণে basePrice — তারপর সবকিছু আরেকবার চালাও। শেষে visibility কড়া করো: যে queries আর বাইরে কেউ call করে না সেগুলো private হতে পারে।
⚠️

মাঝের "গৃহীত কিন্তু উপেক্ষিত" অবস্থাটাই তোমার bug detector — এটা skip করো না। যদি এক ঝটকায় parameter মুছে দাও আর কোনো caller manager-override discount level পাঠাচ্ছিল, সেই override নীরবে মরে যাবে। কেউ জানবে না যতক্ষণ না কোনো গ্রাহক কাউন্টারে অভিযোগ করে। দুই ধাপের পদ্ধতিতে, ধাপ ৪-এর test run mismatch ধরে ফেলে — যখন দুটো path এখনো বেঁচে আছে। Performance-ও মাথায় রাখো: যদি internal query আগের field read-এর চেয়ে ব্যয়বহুল হয় আর method hot loop-এ চলে, আগে ও পরে পরিমাপ করো।

চিত্র ৮: মুছে ফেলার আগে parameter-টাকে মৃত করো

বড় বাস্তব উদাহরণ

ধরো তোমার স্কুল একটা ছোট fee-counter app বানিয়েছে। দেখো কীভাবে প্রতিটা caller-কে bill চাওয়ার আগে অফিসের কাজ নিজে করতে হচ্ছে:

class FeeCounter {
  constructor(
    private readonly feeTable: FeeTable,        // the office's own rate list
    private readonly calendar: SchoolCalendar,  // the office's own calendar
  ) {}
 
  lateFinePerDay(): number {
    return this.feeTable.lateFineRate();
  }
 
  daysLate(dueDate: Date): number {
    return this.calendar.schoolDaysBetween(dueDate, this.calendar.today());
  }
 
  // BEFORE: three parameters — and two of them are the office's own information
  termBill(studentClass: string, fine: number, lateDays: number): number {
    const tuition = this.feeTable.tuitionFor(studentClass);
    return tuition + fine * lateDays;
  }
}
 
// every caller, everywhere:
const fine = counter.lateFinePerDay();             // errand 1
const lateDays = counter.daysLate(dueDate);        // errand 2
const bill = counter.termBill("7B", fine, lateDays);

Fee table আর calendar counter-এর নিজস্ব। তবু callers fine rate আর late days counter থেকে আনছে — শুধু সরাসরি ফিরিয়ে দিতে। আরও খারাপ: parents-এর portal login সময়ে fine cache করেছিল, অফিসের desk তাজা আনছিল। April-এ rate revision হওয়ার পর দুটো screen একই student-এর জন্য ভিন্ন bill দেখাল। একটা বাস্তব inconsistency bug — অপ্রয়োজনীয় parameter থেকে জন্ম।

Refactoring-এর পরে:

class FeeCounter {
  constructor(
    private readonly feeTable: FeeTable,
    private readonly calendar: SchoolCalendar,
  ) {}
 
  private lateFinePerDay(): number {
    return this.feeTable.lateFineRate();
  }
 
  private daysLate(dueDate: Date): number {
    return this.calendar.schoolDaysBetween(dueDate, this.calendar.today());
  }
 
  // AFTER: callers state only what THEY know — class and due date
  termBill(studentClass: string, dueDate: Date): number {
    const tuition = this.feeTable.tuitionFor(studentClass);
    return tuition + this.lateFinePerDay() * this.daysLate(dueDate);
  }
}
 
// every caller:
const bill = counter.termBill("7B", dueDate);

কোন parameters টিকে রইল দেখো। studentClass আর dueDate রইল, কারণ শুধু caller জানে কোন student কাউন্টারে দাঁড়িয়ে আছে। Fine rate আর late-day count চলে গেল, কারণ counter নিজেই rate list আর calendar-এর মালিক। এই বিভাজনটাই — caller-knowledge থাকে, receiver-knowledge যায় — এই refactoring-এর পুরো বিচার। আর April-এর bug কাঠামোগতভাবে মৃত: rate পড়ার ঠিক একটাই জায়গা, billing-এর মুহূর্তে।

termBill-এর সম্পূর্ণ audit, parameter by parameter:

Parameterএর সত্যিকারের মালিক কে?রায়কারণ
studentClassCaller — কাউন্টারে থাকা ব্যক্তিরাখোCounter কোন student-কে bill করা হচ্ছে অনুমান করতে পারে না
fineReceiver — counter-এর নিজস্ব feeTable-এ আছেমুছোCounter তার নিজের rate list পড়ে, সবসময় তাজা
lateDaysReceiver — calendar আর due date থেকে derivableমুছোএকটা internal computation ছয়টা caller copy replace করে
dueDateCaller — কোন term, কোন student তার উপর নির্ভর করেরাখোInternal daysLate query-র input হিসেবে দরকার

স্কুল app-এ termBill call করা ছয়টা screen ছিল, প্রতিটায় নিজস্ব দুটো errand line। আগে ও পরে গণনা করো:

চিত্র ৯: ছয়টা calling screen জুড়ে errand lines — বারোটা copy শূন্যে নেমে আসে

একটা সৎ cost-ও দেখা দিল: termBill এখন ভেতরে calendar.today() call করে, যেটা tests-কে time-dependent করে। Team-টা tests-এ fake SchoolCalendar inject করে সামলাল — dependency constructor-এ আগে থেকেই ছিল, তাই কোনো নতুন coupling তৈরি হয়নি। কিন্তু যদি method global clock ধরতে বাধ্য হত, সেটাই থামার সংকেত হত — তখন বরং parameter রাখাই ভালো।

আরেকটু গভীরে: এই শেষ অনুচ্ছেদটাই একটা বড় design বিষয়ের দরজা — purity বনাম self-sufficiency। যে function-এর output শুধু তার arguments-এর উপর নির্ভর করে সেটা referentially transparent: trivially testable, cacheable, parallelizable। প্রতিটা parameter যেটা তুমি internal query দিয়ে replace করো সেটা function-কে সেই আদর্শ থেকে দূরে নেয়, কারণ output এখন ambient state-এর উপর নির্ভর করে। এটা automatically খারাপ না। কিন্তু এটাই ব্যাখ্যা করে কেন Fowler-এর catalog-এ inverse refactoring, Replace Query with Parameter, এর পাশেই বসে। যখন সিদ্ধান্ত নিতে পারছ না কোন দিকে যাবে, জিজ্ঞেস করো function কোন layer-এ: core functions parameter পায়, shell methods query পায়।

C#-এ একই refactoring

ধরো একটা electricity bill calculator। আগে:

// BEFORE
public class ElectricityAccount
{
    private readonly int _unitsConsumed;
    private readonly Tariff _tariff;   // the account's own tariff plan
 
    public ElectricityAccount(int unitsConsumed, Tariff tariff)
    {
        _unitsConsumed = unitsConsumed;
        _tariff = tariff;
    }
 
    public int SlabFor(int units) => units <= 100 ? 1 : units <= 300 ? 2 : 3;
 
    public decimal MonthlyBill(int units, int slab)
        => units * _tariff.RatePerUnit(slab) + _tariff.FixedCharge(slab);
}
 
// caller — reading the meter's own dials back to it:
var units = account.UnitsConsumed;
var slab = account.SlabFor(units);
var bill = account.MonthlyBill(units, slab);

পরে:

// AFTER
public class ElectricityAccount
{
    private readonly int _unitsConsumed;
    private readonly Tariff _tariff;
 
    public ElectricityAccount(int unitsConsumed, Tariff tariff)
    {
        _unitsConsumed = unitsConsumed;
        _tariff = tariff;
    }
 
    private int Slab() => _unitsConsumed <= 100 ? 1 : _unitsConsumed <= 300 ? 2 : 3;
 
    public decimal MonthlyBill()
        => _unitsConsumed * _tariff.RatePerUnit(Slab()) + _tariff.FixedCharge(Slab());
}
 
// caller:
var bill = account.MonthlyBill();

দুটো parameter শূন্য হলো। SlabFor account-এর নিজস্ব field পড়া private Slab()-এ ভাঁজ হয়ে গেল। আর caller এখন এক মাসের units আর অন্য মাসের হিসাব করা slab পাঠানোর classic mismatch bug আর করতে পারবে না। C#-এ, properties চূড়ান্ত form-কে বিশেষভাবে সুন্দর করে — MonthlyBill cheap আর side-effect-free থাকলে এটা get-only property-ও হতে পারত।

IDE support

কোনো IDE পুরো judgment-laden refactoring এক click-এ করে না। কিন্তু প্রতিটা mechanical ধাপ automated:

  • IntelliJ IDEA / Rider / WebStormExtract Method callers-এর inline errand-কে shared query-তে পরিণত করে। Inline Parameter / Change Signature (Ctrl+F6) dead parameter সরায় আর প্রতিটা call site আপডেট করে। Safe Delete confirm করে পুরনো public query-র আর বাইরের কোনো caller নেই, তারপর private করো। "parameter is never used" inspection স্বয়ংক্রিয়ভাবে dead-weight stage flag করে।
  • ReSharper / Visual Studio (C#)Change Signature solution-wide parameter সরায়। Unused parameter analyzer ধাপ ৩-এর intermediate state highlight করে। Inline Method trivial getter ভাঁজ করতে সাহায্য করে। ReSharper-এর Introduce Parameter ঠিক বিপরীত দিক — কাজে লাগে যখন সিদ্ধান্ত নাও একটা query testability-র জন্য আবার parameter হতে হবে।
  • VS Code (TypeScript / C#)Extract to method, rename, আর reference search সিঁড়িটা cover করে। TypeScript compiler-এর unused-parameter warnings (noUnusedParameters) cleanup-এর সময় escort হিসেবে কাজ করে।
  • একটা ছোট tip: শুরু করার আগে query method-এ IDE-এর Find Usages চালাও। যদি দেখো কোনো caller query-র result-এর বদলে অন্য কিছু পাঠাচ্ছে, তুমি একটা override path পেয়েছ — ধাপ ১-এ থামার সবচেয়ে শক্তিশালী সংকেত।

সুবিধা আর ঝুঁকি

বিষয়সুবিধাঝুঁকি / খরচ
Signature দৈর্ঘ্যReceiver derive করতে পারে এমন parameters সরাসরি চলে যায়; Long Parameter List-এ সরাসরি আঘাত
Duplicationকাজটা একবার লেখা হয়, ভেতরে — প্রতিটা call-এর আগে নয়
Consistencyসত্যের এক তাজা উৎস; পুরনো বা mismatched caller মান অসম্ভবযদি কোনো caller ইচ্ছা করে ভিন্ন মান পাঠাচ্ছিল, সরানো নীরবে সেই feature মুছে দেয় — dead-weight stage এটা ধরতে আছে
Couplingকোনো নতুন type নেই; প্রায়ই call site-এ coupling কমেযদি fetch নতুন dependency বা global দরকার করে, visible parameter-এর বদলে hidden coupling নিচ্ছ — করো না
Performanceপ্রায়ই neutralHot-loop method-এ সরানো expensive query cost গুণ করে
Testabilityপ্রতিটা test-এ arrange করার জন্য কম argumentsInternal fetch clock, database, বা state test-এ টেনে আনতে পারে। Inverse refactoring (Replace Query with Parameter) এর জন্যই আছে — এই দুটো তাদের নিজস্ব ছোট seesaw

দেখো, Parameterize Method আর Replace Parameter with Explicit Methods-এর মতো এই refactoring-এরও একটা named inverse আছে। পরিপক্ক codebase দুই দিকেই যায়: convenience আর consistency-র জন্য query-র দিকে, আর যখন function pure বা test করা সহজ হতে হবে তখন parameter-এর দিকে ফিরে। কোনো দিকটাই virtue নয়; fit-ই আসল।

দুটো axis-এ যেকোনো সন্দেহজনক parameter map করা যায় — মানটার মালিক কে, আর fetch কতটা ভারী:

চিত্র ১০: মালিকানা আর fetch-cost — এই দুটোই প্রতিটা parameter-এর ভাগ্য ঠিক করে

কোণগুলো পড়ো: discountLevel আর basePrice delete quadrant-এর গভীরে — receiver-owned, আনতে সস্তা। ব্যবহারকারীর টাইপ করা quantity চিরকালের parameter। Staff override হলো ডিজাইন অনুযায়ী caller-knowledge। আর today() call উপরে বসে কারণ এটা slow না — stateful। Time-dependent tests একটা real cost, injectable calendar দিয়ে পরিশোধ করতে হয়।

কোন smells সারায়?

SmellReplace Parameter with Method Call কীভাবে সাহায্য করে
Long Parameter Listপ্রাথমিক লক্ষ্য — derivable parameters সরাসরি মুছে যায়, সবচেয়ে সস্তায় ছোট করা
Duplicate Codeপ্রতিটা call site-এ copy-pasted pre-call errand একটাই internal query-তে ভেঙে পড়ে
Data ClumpsClump পাতলা হয়: derivable members দল ছেড়ে চলে যায়, যেটা থাকে সেটা bundle বা পাঠানো সহজ
Feature EnvyCallers আর argument তৈরি করতে receiver-এর data খুঁচিয়ে বেড়ায় না; জ্ঞান তার মালিকের কাছে ফিরে যায়
Shotgun Surgeryমান কীভাবে derive করা হয় তার পরিবর্তন এখন একটা query ছোঁয়, প্রতিটা caller-এর private copy না

পুরো ধারণা এক নজরে

চিত্র ১১: এক নজরে Replace Parameter with Method Call — মালিকানাই সব ঠিক করে

দ্রুত revision

+========= REPLACE PARAMETER WITH METHOD CALL =========+
|              (2nd ed: Replace Parameter with Query)  |
|                                                      |
|  SMELL : caller runs an errand first                 |
|          level = order.discountLevel()               |
|          order.finalPrice(base, level)               |
|          ...but the method owns the same shelf!      |
|                                                      |
|  MOVE  : method fetches the value itself             |
|          order.finalPrice()                          |
|                                                      |
|  LADDER: 1 eligibility - no caller-only data,        |
|            no new dependency                         |
|          2 extract errand into a query               |
|          3 body uses query; param kept but IGNORED   |
|          4 full test run  (catches overrides!)       |
|          5 remove param + delete errand lines        |
|                                                      |
|  KEEP THE PARAM when: only caller knows it,          |
|    callers override/cache it, fetch is costly,       |
|    or tests need to fake it easily                   |
|                                                      |
|  KIN   : whole object in caller's hand?              |
|          -> Preserve Whole Object instead            |
+======================================================+

অনুশীলন

ধরো একটা cinema-booking class প্রতিটা caller-কে theatre-এর কাজ করিয়ে নিচ্ছে। এটা refactor করো:

class TicketCounter {
  constructor(
    private readonly priceBoard: PriceBoard,     // the theatre's own board
    private readonly calendar: ShowCalendar,
  ) {}
 
  isWeekend(showDate: Date): boolean {
    return this.calendar.isWeekendOrHoliday(showDate);
  }
 
  baseRate(seatClass: "silver" | "gold" | "recliner"): number {
    return this.priceBoard.rateFor(seatClass);
  }
 
  totalFare(
    seatClass: "silver" | "gold" | "recliner",
    seats: number,
    rate: number,          // derivable!
    weekend: boolean,      // derivable!
  ): number {
    const surcharge = weekend ? 1.2 : 1.0;
    return seats * rate * surcharge;
  }
}
 
// caller:
const rate = counter.baseRate("gold");
const weekend = counter.isWeekend(showDate);
const fare = counter.totalFare("gold", 3, rate, weekend);

তোমার কাজ:

  1. totalFare-এর চারটা parameter-এর প্রতিটার জন্য এক লাইন লেখো: caller-knowledge নাকি receiver-knowledge? সাবধান: weekend derivable, কিন্তু শুধুমাত্র যদি showDate পাওয়া যায় — এর জন্য signature-এ কী হতে হবে?
  2. নিরাপদ ধাপগুলো follow করো: rate আর weekend-কে internally fetch করতে দাও কিন্তু এখনো accept করো, tests চালাও, তারপর সরাও। তোমার চূড়ান্ত signature হবে totalFare(seatClass, seats, showDate)
  3. দুটো বাক্যে ব্যাখ্যা করো কেন seatClass আর seats চিরকালের parameter।
  4. Override hunt: একটা caller — staff-discount screen — baseRate(...) এর বদলে rate * 0.5 পাঠাচ্ছিল। তোমার ধাপ ৪-এর test run এটা ধরে ফেলে। দুটো design প্রস্তাব করো যেগুলো discount কাজ করিয়ে রাখে। Hint: একটা explicit staffFare method, অথবা counter নিজেই query করতে পারে এমন discount policy।
  5. Testability প্রশ্ন: isWeekend calendar-কে জিজ্ঞেস করে, আর calendar real holidays দেখে। বাস্তব holiday-র জন্য অপেক্ষা না করে কীভাবে holiday show-র জন্য totalFare test করবে? আর বলো — যদি calendar constructor dependency-র বদলে hard-coded global হত তাহলে কোন refactoring ব্যবহার করতে?
  6. Kin check: একজন teammate লক্ষ্য করে caller-এর কাছে seatClass, seats, আর showDate সমন্বিত একটা Show object আছে, আর totalFare(show) suggest করে। ওটা কোন refactoring, আর এই refactoring-এর সাথে combine করবে? সংক্ষেপে justify করো।
  7. Chart করো: totalFare-এর চারটা মূল parameter চিত্র ১০-এর quadrant chart-এ রাখো, সাথে task ৪-এর staff-override rate। প্রতিটা কোন quadrant-এ পড়ে? তোমার placement কি task ২-এর চূড়ান্ত signature-এর সাথে মেলে?

সচরাচর জিজ্ঞাসা

Replace Parameter with Method Call সহজ ভাষায় কী করে?
এটা এমন একটা parameter সরিয়ে দেয় যেটা method নিজেই আনতে পারত। প্রতিটা caller যদি আগে কোনো মান বের করে — যেমন discount level — তারপর সেটা method-এ পাঠায়, কিন্তু method নিজেই একই জায়গা থেকে দেখে নিতে পারে, তাহলে parameter মুছে দাও। Method-কেই সেই মান আনতে দাও। দোকানদারকে এমন দাম বলতে যেও না যেটা সে নিজেই তার তালিকা থেকে পড়তে পারে।
এটা কি Fowler-এর Replace Parameter with Query-এর মতোই?
হ্যাঁ, একই জিনিস। Refactoring বইয়ের প্রথম সংস্করণ (১৯৯৯) এটাকে Replace Parameter with Method Call বলত। দ্বিতীয় সংস্করণ (২০১৮) নাম বদলে Replace Parameter with Query রাখে। Refactoring Guru আর SourceMaking পুরনো নামটাই রেখেছে। Mechanics একই, আর এর সরাসরি উল্টো হলো Replace Query with Parameter।
কখন parameter রাখতেই হবে?
যখন মানটা caller-এর নিজস্ব context থেকে আসে যেটা method দেখতে পায় না। যখন ভেতরে আনলে method-কে নতুন dependency বা global ধরতে হবে। যখন callers ইচ্ছা করে ভিন্ন বা cached মান পাঠায়। অথবা যখন হিসাবটা ব্যয়বহুল বা side effect আছে। যে parameter method সৎভাবে নিজে বের করতে পারে না — সেটা redundant না।
এটা কি testability নষ্ট করে?
করতে পারে — এটাই সৎ সতর্কবার্তা। একটা parameter test-এ fake করা অনেক সহজ। কিন্তু getDiscountLevel()-এ internal call থাকলে আগে state সেট আপ করতে হতে পারে। যদি internal source নিজেই simple আর deterministic হয়, trade-off ঠিক আছে। কিন্তু যদি clock, database, বা network ছোঁয়, তাহলে parameter রাখা বা dependency inject করা প্রায়ই বুদ্ধিমানের কাজ।
Preserve Whole Object-এর সাথে এর সম্পর্ক কী?
দুটোই parameter list ছোট করে, কিন্তু দুই দিক থেকে। Preserve Whole Object কাজে লাগে যখন arguments হলো caller-এর কাছে থাকা একটা object-এর field — তখন পুরো object পাঠাও। Replace Parameter with Method Call কাজে লাগে যখন argument এমন কিছু যেটা METHOD নিজেই বের করতে পারে — তখন কিছুই পাঠিও না। জিজ্ঞেস করো: এই মানটার স্বাভাবিক মালিক কে? সেই উত্তরই বলে দেবে কোন refactoring দরকার।

আরো দেখো

সম্পর্কিত পাঠ

Preserve Whole Object: পুরো ID Card দেখাও

Preserve Whole Object refactoring শেখো একটা school ID card-এর গল্প দিয়ে — TypeScript আর C# example সহ, safe step-by-step mechanics, আর object pass করলে coupling বাড়ে কিনা সেটার সৎ আলোচনা।

আরও পড়ুন

Parameterize Method: একটাই জুসের রেসিপি, শুধু সাইজটা দিয়ে দাও

জুসের দোকানের গল্পের মাধ্যমে Parameterize Method রিফ্যাক্টরিং শেখো — TypeScript আর C# উদাহরণ সহ, নিরাপদ ধাপে ধাপে mechanics, আর সেই সিস্যার নিয়ম যেটা Replace Parameter with Explicit Methods-এর সাথে জুটি বাঁধে।

আরও পড়ুন

Replace Parameter with Explicit Methods: গোপন কোড নয়, নামের বোর্ড লাগাও

Replace Parameter with Explicit Methods refactoring শেখো একটা ব্যাংক কাউন্টারের গল্পের মাধ্যমে — TypeScript আর Python উদাহরণ, safe mechanics, আর seesaw rule যেটা Parameterize Method-এর সাথে এর সম্পর্ক বোঝায়।

আরও পড়ুন

Long Parameter List: দশটা নির্দেশনার চায়ের অর্ডার

Long Parameter List কোড স্মেল সহজ ভাষায় — কেন বেশি argument-এর method বাগ তৈরি করে, আর কীভাবে parameter object দিয়ে call ছোট, পরিষ্কার আর নিরাপদ করা যায়।

আরও পড়ুন