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

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

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

24 মিনিট আপডেট: June 11, 2026beginner
refactoringparameterize methodsimplifying method callsduplicate codeclean codeparameters

তিনটা রেসিপি কার্ডের গল্প

রহিম একজন নবম শ্রেণির ছাত্র। তার চাচা সালাম ভাই বাস স্ট্যান্ডের কাছে একটা ছোট্ট জুসের দোকান চালান। মিষ্টি লেবু, আনারস, তরমুজ — পুরো গলিতে দোকানটা বিখ্যাত, আর মে মাসে লাইন মুচির দোকান পেরিয়ে যায়।

এক গরমের ছুটিতে রহিম দোকানে সাহায্য করতে আসে। প্রথম দিনেই সে একটা মজার জিনিস দেখতে পায়। সালাম ভাই দেয়ালে তিনটা রেসিপি কার্ড পিন করে রেখেছেন, সবগুলো তার পরিষ্কার গোল হাতের লেখায়।

প্রথম কার্ডে লেখা "SMALL জুস কীভাবে বানাবেন": ২০০ মিলি ফলের pulp নাও, ৫০ মিলি পানি দাও, এক চামচ চিনি, চারটা বরফের টুকরো, blend করো, ঢালো, হাসিমুখে পরিবেশন করো। দ্বিতীয় কার্ডে "MEDIUM জুস কীভাবে বানাবেন": ৩০০ মিলি pulp, ৭৫ মিলি পানি, দেড় চামচ চিনি, ছয়টা বরফ, blend করো, ঢালো, হাসিমুখে পরিবেশন করো। তৃতীয় কার্ডে "LARGE জুস কীভাবে বানাবেন": ৪০০ মিলি pulp, ১০০ মিলি পানি, দুই চামচ চিনি, আটটা বরফ, blend করো, ঢালো, হাসিমুখে পরিবেশন করো।

রহিম তিনটা কার্ড দুবার করে পড়ে। তারপর জোরে হেসে ফেলে। "চাচা, এগুলো তিনটা রেসিপি না। এটা একটাই রেসিপি তিনবার লেখা! প্রতিটা ধাপ একই। শুধু পরিমাণগুলো সাইজের সাথে বদলায়।"

সালাম ভাই মাথা চুলকান। "কিন্তু small, medium আর large তো আলাদা জিনিস, না?"

"না, চাচা। method একই। ধোয়া, blend করা, ঢালা, পরিবেশন — সব একই। শুধু একটা জিনিস বদলায়: সাইজ। দেখো।" রহিম একটা নতুন কার্ড নিয়ে লেখে: "S মিলি সাইজের জুস কীভাবে বানাবেন: S মিলি pulp নাও, S-এর এক-চতুর্থাংশ পানি দাও, অনুপাতে চিনি, অনুপাতে বরফ, blend করো, ঢালো, হাসিমুখে পরিবেশন করো।" একটাই কার্ড। সাইজের জায়গায় একটা ফাঁকা। শেষ।

সালাম ভাই এখনো বিশ্বাস করতে পারছেন না। "তিনটা কার্ড দিয়ে দশ বছর ধরে কাজ চলেছে, রহিম।"

"সত্যিই, চাচা? গত মাসে চিনির সরবরাহকারী বদলে গেল আর তুমি মিষ্টি কমালে — কতটা কার্ড ঠিক করতে হয়েছিল?"

নীরবতা। তিনি small আর large কার্ড ঠিক করেছিলেন — medium-টা ভুলে গিয়েছিলেন। পুরো তিন সপ্তাহ ধরে প্রতিটা medium জুস একটু বেশি মিষ্টি ছিল, আর সালাম ভাই বুঝতেই পারছিলেন না কেন কিছু কাস্টমার মুখ বাঁকাচ্ছে। একটা রেসিপি তিনবার লেখা মানে একটা ভুল তিনবার হওয়ার অপেক্ষায়।

রহিমের একটাই কার্ডের সবচেয়ে ভালো দিকটা আসে এক সপ্তাহ পরে। বিবাহের হল থেকে একটা পরিবার "ফ্যামিলি জাম্বো" চায় — ৬০০ মিলি। পুরনো কার্ড দিয়ে সালাম ভাইকে সেখানে দাঁড়িয়ে স্ক্র্যাচ থেকে একটা চতুর্থ কার্ড বানাতে হতো। রহিমের কার্ড দিয়ে তিনি শুধু ৬০০ ফাঁকা জায়গায় বসিয়ে দেন। নতুন কার্ড নেই। নতুন ভুল নেই। দেয়ালে চতুর্থ কোনো জিনিস নেই যেটা পরের চিনি পরিবর্তনের সময় ভুলে যাওয়া যাবে।

চিত্র ১: জুসের দোকানে রহিমের সপ্তাহ — একটাই কার্ডের আগে ও পরের অবস্থা

কোডেও ঠিক এটাই হয়। আমরা makeSmallJuice() লিখি, তারপর copy করে makeMediumJuice() বানাই, তারপর আবার copy করে makeLargeJuice()। তিনটা method, একটাই ধারণা, আর প্রতিটা bug তিনবার ঠিক করতে হয়। এটা ঠিক করার refactoring-কে বলে Parameterize Method: copy গুলোকে একটা method-এ মেলাও, আর যে value-টা আলাদা সেটাকে parameter হিসেবে ঢুকতে দাও।

একটাই কার্ড লাগানোর পর দোকানে কীভাবে অর্ডার আসে তা এখানে দেখানো হলো। লক্ষ্য করো customer শুধু সাইজটা বলে — সাইজ হলো data যেটা request-এর সাথে আসে, আলাদা রেসিপি না।

চিত্র ২: Refactoring-এর পরে, সাইজ plain data হিসেবে অর্ডারের সাথে ভ্রমণ করে

Parameterize Method কী?

Parameterize Method হলো সেই refactoring যেখানে তুমি একই কাজ করা কয়েকটা method — যেগুলো শুধু body-তে বেক করা একটা value-তে আলাদা — সেগুলোকে একটা method দিয়ে replace করো যেটা সেই value-টাকে parameter হিসেবে নেয়

"Value" যেকোনো literal হতে পারে: একটা সংখ্যা (২০০ মিলি, ১০%), একটা string ("small"), একটা rate, একটা limit। মূল পরীক্ষা হলো: যদি method body গুলো পাশাপাশি রাখলে লাইনে লাইনে মিলে শুধু সেই একটা value বাদে, তাহলে তুমি একটাই method দেখছ যেটা তিনটা নেমট্যাগ পরে আছে।

Martin Fowler-এর Refactoring, দ্বিতীয় সংস্করণে (২০১৮) এই কৌশলটা Parameterize Function নামে আসে — বইয়ের উদাহরণগুলো plain function ব্যবহার করে — আর refactoring.com-এর catalog-এ নোট করা আছে যে Parameterize Method হলো প্রথম সংস্করণের নাম। Fowler-এর ক্লাসিক উদাহরণ হলো দশ শতাংশ এবং পাঁচ শতাংশ বেতন বৃদ্ধির একজোড়া salary function যেগুলো factor-কে input হিসেবে নেওয়া একটা raise function-এ মিলে যায়। Refactoring Guru একই কৌশলকে Simplifying Method Calls-এর আওতায় রাখে।

সালাম ভাইয়ের তিনটা কার্ড আসলে কতটা কাছের ছিল? রহিম আসলে লাইন গুনেছে। নির্দেশনার প্রতি বিশটা লাইনের মধ্যে সতেরোটা হুবহু একই ছিল। শুধু পরিমাণের লাইনগুলো আলাদা ছিল।

চিত্র ৩: তিনটা রেসিপি কার্ডের বেশিরভাগ একই কথা তিনবার copy করা

যখন তিনটা method-এর ৮৫ শতাংশ একই text, তখন তুমি তিনটা method maintain করছ না। তুমি একটা method আর দুটো photocopy maintain করছ যেগুলো চুপচাপ পুরনো হয়ে যেতে পারে।

মেলানো কেন এত লাভজনক?

  1. Duplication মরে যায়। একটাই body মানে bug ঠিক করার একটাই জায়গা, feature যোগ করার একটাই জায়গা, পড়ার একটাই জায়গা।
  2. Class-এর API ছোট হয়। তিনটা বা পাঁচটা বা নয়টা method হয়ে যায় একটা। কম শিখতে হয়, কম scroll করতে হয়।
  3. নতুন cases বিনামূল্যে পাওয়া যায়। ৬০০ মিলি জাম্বো জুস বা ১৫% বেতন বৃদ্ধির জন্য কোনো নতুন code লাগে না — call site-এ শুধু নতুন argument।
  4. Drift অসম্ভব হয়ে যায়। চিনি-পরিবর্তনের bug — দুটো copy ঠিক করে তৃতীয়টা ভুলে যাওয়া — structurally হতেই পারে না যখন শুধু একটাই copy আছে।
💡

মনে রাখার একটাই লাইন: যদি body গুলো যমজ হয় আর শুধু একটা value আলাদা হয়, value-টা বের করে একটাই method দিয়ে অনেকের কাজ করাও। যে value-টা আলাদা ছিল সেটা সবসময়ই disguise পরা input ছিল — Parameterize Method শুধু তাকে parameter list-এ তার যোগ্য আসন দেয়।

কলেজ কর্নার: রহিম যা প্রয়োগ করেছে সেটা হলো DRY principleDon't Repeat Yourself, Hunt আর Thomas-এর The Pragmatic Programmer থেকে। সঠিক বাক্যটা গুরুত্বপূর্ণ: জ্ঞানের প্রতিটা অংশের system-এ একটাই authoritative representation থাকা উচিত। এখানে জ্ঞান হলো "জুস কীভাবে বানাতে হয়"; তিনটা কার্ড ছিল এর তিনটা প্রতিযোগী representation, আর চিনি-পরিবর্তনের bug হলো violated DRY-এর textbook failure mode — representations আলাদা হয়ে যাওয়া। কিন্তু equally গুরুত্বপূর্ণ fine print মনে রেখো: DRY হলো জ্ঞান নিয়ে, text-এর লাইন নিয়ে না। দুটো method যেগুলো আজ একই রকম দেখতে কিন্তু আলাদা business rule encode করে (ধরো একটা tax formula আর একটা discount formula যেগুলো coincidentally দুটোই x * 0.1) মেলানো উচিত না, কারণ সেগুলো আলাদা কারণে পরিবর্তিত হবে। মেলালে দুটো unrelated সিদ্ধান্ত couple হয়ে যায়। পরীক্ষাটা কখনো "লাইনগুলো কি মিলছে?" না — পরীক্ষা হলো "এটা কি একটাই ধারণা দুবার লেখা?"

পাশে একটা সতর্কতা রাখো। এই refactoring-এর একটা exact opposite আছে, Replace Parameter with Explicit Methods, আর দুটোর মধ্যে বেছে নেওয়া একটা আসল দক্ষতা। এই post-এর পরে আমরা এটার জন্য পুরো decision table আর quadrant chart বানাব। সংক্ষিপ্ত version: variants শুধু একটা value-তে আলাদা হলে parameterize করো; variants behaviour-এ আলাদা হলে explicit methods-এ ভাগ করো।

কখন এটা দরকার?

Parameterize Method তখন ব্যবহার করো যখন এই চিহ্নগুলো দেখবে:

  1. Copy-paste sibling। তুমি chargeForHalfKg(), chargeForOneKg(), chargeForTwoKg() দেখছ আর যেকোনো দুটোর মধ্যে পার্থক্য শুধু একটা সংখ্যা। এটাই textbook trigger। এটা আসলে Duplicate Code smell-এর একটা special case — duplication যেখানে copies শুধু একটা constant-এ আলাদা।
  2. নতুন case মানে নতুন method। Product team আরেকটা সাইজ, আরেকটা discount band, আরেকটা speed level চায় — আর তোমার পরিকল্পনা হলো "কাছের method copy করে সংখ্যা বদলাও"। থামো। আগে Parameterize করো; তারপর নতুন case হবে শুধু একটা নতুন argument।
  3. একটা fix কয়েক জায়গায় apply করতে হয়েছিল। গত সপ্তাহের bug medium-juice method-এ ছিল যেটা small আর large-এও ছিল, আর কেউ একটা ভুলে গিয়েছিল। তিনগুণ করা body গ্যারান্টি দেয় এটা আবার হবে — সালাম ভাইকে জিজ্ঞেস করো।
  4. Class প্রায়-যমজ method দিয়ে ফুলে উঠছে। দশটা একই রকম method একটা class-কে বিশাল মনে করায় যদিও আসল logic ছোট। মেলালে surface নাটকীয়ভাবে ছোট হয়।

এই refactoring দুটো smell-এর সাথেও সংযুক্ত যেগুলো তোমার জানা উচিত:

  • Long Parameter List — এখানে সাবধান। Parameterize Method একটা parameter যোগ করে। তিনটা duplicate method মারতে একটা সৎ parameter যোগ করা দারুণ trade। কিন্তু কোনো method যদি একের পর এক flag পেতে থাকে, তুমি এই smell-এর দিকে হাঁটছ, আর হয়তো উল্টো refactoring-এর সময় হয়েছে।
  • Data Clumps — কখনো কখনো যে "একটা value" বের করছ সেটা আসলে দুটো বা তিনটা value যেগুলো সবসময় একসাথে ভ্রমণ করে (pulp ml, water ml, sugar spoons)। তাহলে তিনটা loose parameter যোগ করো না; সেগুলোকে JuiceSize-এর মতো একটা ছোট object-এ বাঁধো, যাতে clump একটা পরিষ্কার parcel হিসেবে ভ্রমণ করে।

আর জানো কখন উত্তর হবে না:

  • Variants আলাদা কাজ করে — একটায় বাড়তি validation, আরেকটায় আলাদা calculation branch, তৃতীয়টায় কোনো side effect। মেলালে একটাই method পাবে যেটা if (size === ...) check-এ ভরা। এটা ভালো না, বরং খারাপ।
  • Named method-এর fixed set একটা feature। Call site-এ raiseTenPercent() company policy document করে; raiseBy(0.10) কাউকে ভুলে 0.45 pass করার সুযোগ দেয়। যদি menu ছোট, fixed, আর policy-এর মতো হয়, তাহলে named method হয়তো ভালো API।

পুরো সিদ্ধান্তটা একটা ছবিতে — রহিমের কার্ডের পাশে পিন করে রাখো:

চিত্র ৪: প্রথম প্রশ্ন সবসময় — method গুলো কি একটা value-তে আলাদা, নাকি behaviour-এ?

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

TypeScript-এ রহিম যেমন পেয়েছিল ঠিক তেমন সালাম ভাইয়ের দোকান:

// BEFORE: three methods, one idea
class JuiceStall {
  makeSmallJuice(fruit: string): Juice {
    const pulp = this.blend(fruit, 200);
    const water = 200 / 4;
    const sugar = 200 / 200;          // spoons
    const ice = 200 / 50;             // cubes
    return this.pour(pulp, water, sugar, ice);
  }
 
  makeMediumJuice(fruit: string): Juice {
    const pulp = this.blend(fruit, 300);
    const water = 300 / 4;
    const sugar = 300 / 200;
    const ice = 300 / 50;
    return this.pour(pulp, water, sugar, ice);
  }
 
  makeLargeJuice(fruit: string): Juice {
    const pulp = this.blend(fruit, 400);
    const water = 400 / 4;
    const sugar = 400 / 200;
    const ice = 400 / 50;
    return this.pour(pulp, water, sugar, ice);
  }
}

ভালো করে দেখো। প্রতিটা লাইন একই শুধু 200, 300, 400 সংখ্যা বাদে। সেই সংখ্যাটাই সাইজ। এটা একটা input যেটা তিনটা method name হওয়ার ভান করছে। রহিম যে কার্ড লিখেছিল:

// AFTER: one method; the size walks in as a parameter
class JuiceStall {
  makeJuice(fruit: string, sizeMl: number): Juice {
    if (sizeMl <= 0 || sizeMl > 1000) {
      throw new Error(`Juice size must be 1-1000 ml, got ${sizeMl}`);
    }
    const pulp = this.blend(fruit, sizeMl);
    const water = sizeMl / 4;
    const sugar = sizeMl / 200;
    const ice = sizeMl / 50;
    return this.pour(pulp, water, sugar, ice);
  }
}
 
// callers: makeJuice("pineapple", 200); makeJuice("watermelon", 600); — jumbo is free!

নতুন version-এ দুটো ছোট সদয়তা লক্ষ্য করো। প্রথমত, শুরুতে একটা validation guard — পুরনো named method গুলো কখনো ভুল size পেত না, তাই merged method-কে নিজেকে রক্ষা করতে হবে। দ্বিতীয়ত, parameter-এর নাম sizeMl, s না, যাতে call site গুলো এখনো পরিষ্কার পড়া যায়।

Class structure-ও উপর থেকে একই গল্প বলে। আগে, দোকানে তিনটা দরজা ছিল; পরে, একটাই দরজা একটা সাইজ knob সহ, আর সাইজ যে Juice তৈরি হয় সেটায় প্রবাহিত হয়:

চিত্র ৫: Class তিনটা একই রকম দরজা থেকে একটাই parameterized দরজায় সংকুচিত হয়

আর এখানে সেই একই সংকোচন body গুলো মেলার flow হিসেবে:

চিত্র ৬: তিনটা যমজ body একটাই shared body-তে মিলে যায় parameter সহ

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

কখনোই একটানে তিনটা method মুছে দিয়ে merge করো না। Refactoring মানে ছোট ছোট ধাপ আর প্রতিটার মাঝে সবুজ test। Codebase-কে পরিষ্কার, named state-এর মধ্যে দিয়ে যেতে দেখো — কখনো প্রথম থেকে শেষে লাফ দিও না:

চিত্র ৭: Codebase নিরাপদ state-এর মধ্যে দিয়ে যায়; forwarder গুলো হলো bridge state

একই সিঁড়ি কথায়:

  1. যমজদের সারি করো আর যে value-টা আলাদা সেটা খোঁজো। Body গুলো পাশাপাশি রাখো। সৎভাবে নিশ্চিত করো যে একমাত্র পার্থক্য literal। যদি একটা variant-এ চুপিচুপি বাড়তি if পাও, থামো এবং সিদ্ধান্ত নাও সেটা সব variant-এ থাকা উচিত কিনা বা এই method গুলো আদৌ যমজ কিনা।
  2. নতুন parameterized method তৈরি করো, পুরনোগুলো অছুঁয়া রাখো। একটা body copy করো, সব জায়গায় literal-কে parameter দিয়ে replace করো, আর নতুন open range-এর জন্য validation যোগ করো।
// Intermediate state: new method exists, old ones still standing
class JuiceStall {
  makeJuice(fruit: string, sizeMl: number): Juice { /* shared body using sizeMl */ }
 
  makeSmallJuice(fruit: string): Juice { /* old body, untouched for now */ }
  makeMediumJuice(fruit: string): Juice { /* old body, untouched for now */ }
  makeLargeJuice(fruit: string): Juice { /* old body, untouched for now */ }
}
  1. প্রতিটা original value দিয়ে নতুন method test করো। makeJuice("mango", 200) ঠিক সেটাই produce করতে হবে যা makeSmallJuice("mango") করেছিল। ৩০০ আর ৪০০-এর জন্যও একই করো। যদি test suite থাকে, এগুলো explicit case হিসেবে যোগ করো।
  2. প্রতিটা পুরনো method-কে এক লাইনের forwarder বানাও। এটাই যাদুকরী intermediate step — পুরনো নামগুলো এখনো কাজ করে, কিন্তু duplication ইতিমধ্যে চলে গেছে:
// Intermediate state 2: old methods become thin forwarders
makeSmallJuice(fruit: string): Juice  { return this.makeJuice(fruit, 200); }
makeMediumJuice(fruit: string): Juice { return this.makeJuice(fruit, 300); }
makeLargeJuice(fruit: string): Juice  { return this.makeJuice(fruit, 400); }
  1. Compile করো আর সব test চালাও। Behaviour byte-for-byte অপরিবর্তিত থাকতে হবে। এখানে কোনো test ব্যর্থ হলে, body গুলো সত্যিকারের যমজ ছিল না — ধাপ ১-এ ফিরে যাও আর আবার দেখো।
  2. Caller গুলো একটা একটা করে migrate করো। stall.makeSmallJuice("mango") কে stall.makeJuice("mango", 200) দিয়ে replace করো। প্রতিটা batch of call sites-এর পরে test চালাও।
  3. Forwarder গুলো delete করো যখন কোনো caller সেগুলো ব্যবহার করছে না। Class এখন তিনটা কম method।
⚠️

সবচেয়ে সাধারণ ভুল: body গুলো যমজ মনে হয় কিন্তু একটা variant-এ একটা ছোট্ট behavioural পার্থক্য আছে — একটা rounding নিয়ম, একটা বাড়তি log line, একটা আলাদা error message। সেই পার্থক্যের উপর দিয়ে merge করলে কিছু caller-এর জন্য behaviour চুপচাপ বদলে যায়। ধাপ ২-এর আগে সবসময় body গুলো mechanically diff করো (তোমার চোখ না, editor-এর compare tool), আর forwarder stage রাখো যাতে test গুলো যেকোনো drift ধরতে পারে যখন উভয় path এখনো বিদ্যমান।

চিত্র ৮: নিরাপদ সিঁড়ি — forwarder গুলো পুরনো নামগুলো জীবিত রাখে যতক্ষণ না প্রতিটা caller সরে গেছে

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

রহিমের বন্ধু করিম একটা courier company-তে কাজ করে। তাদের billing class-এ একই রোগ ধরেছে, কিন্তু তিনটার বদলে পাঁচটা যমজ:

// BEFORE: five charge methods that differ only by a weight baked into each
class CourierBilling {
  chargeForHalfKg(distanceKm: number): number {
    const base = 40;
    const perKm = 0.8;
    return Math.round(base + distanceKm * perKm * 0.5);
  }
 
  chargeForOneKg(distanceKm: number): number {
    const base = 40;
    const perKm = 0.8;
    return Math.round(base + distanceKm * perKm * 1.0);
  }
 
  chargeForTwoKg(distanceKm: number): number {
    const base = 40;
    const perKm = 0.8;
    return Math.round(base + distanceKm * perKm * 2.0);
  }
 
  chargeForFiveKg(distanceKm: number): number {
    const base = 40;
    const perKm = 0.8;
    return Math.round(base + distanceKm * perKm * 5.0);
  }
 
  chargeForTenKg(distanceKm: number): number {
    const base = 40;
    const perKm = 0.8;
    return Math.round(base + distanceKm * perKm * 10.0);
  }
}

প্রতিটা body একই formula। একমাত্র পার্থক্য weight: ০.৫, ১, ২, ৫, ১০। আর কষ্টটা বাস্তব: গত ঈদে base charge ৪০ থেকে ৪৫ টাকায় উঠলে, একজন developer চারটা method update করে chargeForFiveKg বাদ দিয়ে গেল। পাঁচ কেজি পাঠানো কাস্টমাররা দুই সপ্তাহ পুরনো rate দিয়েছে কেউ বুঝার আগে। সালাম ভাইয়ের চিনির bug, courier uniform পরে।

Parameterize Method-এর পরে:

// AFTER: one method; weight is an honest input
class CourierBilling {
  private static readonly BASE_CHARGE = 45;
  private static readonly RATE_PER_KM = 0.8;
  private static readonly MAX_WEIGHT_KG = 25;
 
  chargeFor(weightKg: number, distanceKm: number): number {
    if (weightKg <= 0 || weightKg > CourierBilling.MAX_WEIGHT_KG) {
      throw new Error(`Weight must be 0-${CourierBilling.MAX_WEIGHT_KG} kg`);
    }
    return Math.round(
      CourierBilling.BASE_CHARGE +
      distanceKm * CourierBilling.RATE_PER_KM * weightKg
    );
  }
}
 
// chargeFor(0.5, 12); chargeFor(7.3, 12); — any weight in range, no new method

একসাথে তিনটা জিনিস উন্নত হয়েছে। Base charge ঠিক একটাই জায়গায় আছে, তাই ঈদের bug আর কখনো হতে পারবে না। Company এখন ৭.৩ কেজির পার্সেলের bill করতে পারে — পুরনো design সহজভাবে এর পাঁচটা fixed method-এর মাঝের weight express করতেই পারত না। আর class API পাঁচটা entry থেকে একটায় নেমে গেছে।

Maintenance-এর হিসাবটা নিষ্ঠুর এবং chart হিসেবে দেখার যোগ্য। প্রতিটা rate পরিবর্তনের মানে ছিল পাঁচটা edit, পাঁচটা সুযোগ একটা ভুলে যাওয়ার:

চিত্র ৯: প্রতিটা rate পরিবর্তনে maintain করার surface আগে ও পরে — পাঁচটা body বনাম একটা

লক্ষ্য করো আমরা কী করিনি। আমরা chargeFor("half-kg")-এর মতো একটা string parameter যোগ করিনি যেটার উপর method switch করত। Weight হলো একটা continuum-এ সত্যিকারের সংখ্যা, তাই সেটা সংখ্যা হিসেবে ভ্রমণ করে। যদি company-র তিনটা plan থাকত — "express", "standard", "economy" — প্রতিটায় আলাদা promise date, আলাদা insurance, আলাদা routing, সেগুলো হতো আলাদা behaviour, আর seesaw explicit methods-এর দিকে হেলে পড়ত।

কলেজ কর্নার: এখানে একটা দরকারী theoretical lens আছে — binding time। Before code-এ, weight compile time-এ bound ছিল, method name-এ জমাট বাঁধা; after code-এ, এটা call time-এ bound, data হিসেবে সরবরাহ করা। একটা সিদ্ধান্তকে পরের binding time-এ নিয়ে যাওয়া সবসময় flexibility কেনে (যেকোনো weight!) আর সবসময় কিছু static safety-র মূল্য দেয় (compiler আর গ্যারান্টি দিতে পারে না weight পাঁচটা blessed value-র একটা)। এজন্যই guard clause decoration না — এটা তুমি যে compile-time guarantee ছেড়ে দিয়েছ তার runtime replacement। পরিপক্ক API প্রায়ই মাঝের পথ নেয়: flexibility-র জন্য open chargeFor(weight), আর common sanctioned value-র জন্য typed enum বা named constant। কোন guarantees কোন binding time-এ trade করছ সেটা জানা হলো refactoring আর rearranging-এর মধ্যে পার্থক্য।

C#-এ একই refactoring

C#-এও রোগ আর চিকিৎসা একই দেখতে। এখানে salary module আগে:

// BEFORE
public class Salary
{
    private readonly decimal _base;
    public Salary(decimal baseAmount) => _base = baseAmount;
 
    public decimal RaiseFivePercent()   => _base * 1.05m;
    public decimal RaiseTenPercent()    => _base * 1.10m;
    public decimal RaiseTwentyPercent() => _base * 1.20m;
}

আর merge-এর পরে:

// AFTER
public class Salary
{
    private readonly decimal _base;
    public Salary(decimal baseAmount) => _base = baseAmount;
 
    public decimal RaiseBy(decimal fraction)
    {
        if (fraction <= 0m || fraction > 0.5m)
            throw new ArgumentOutOfRangeException(nameof(fraction),
                "Raise must be between 0 and 50 percent.");
        return _base * (1m + fraction);
    }
}
 
// salary.RaiseBy(0.05m); salary.RaiseBy(0.15m);  // 15% needed no new method

একটা C#-specific সদয়তা: যদি company সত্যিই মাত্র কয়েকটা sanctioned raise level allow করে, তুমি open method রাখতে এবং expression-bodied forwarder হিসেবে named convenience offer করতে পারো — public decimal RaiseTenPercent() => RaiseBy(0.10m);। Logic এখনো একটাই জায়গায় থাকে; নামগুলো শুধু policy document করে। এই hybrid প্রায়ই এই refactoring-এর সবচেয়ে readable endpoint।

IDE support

কোনো IDE-তে "Parameterize Method" লেবেলের একটাই বোতাম নেই, কিন্তু building block গুলো প্রতিটা major tool-এ first-class refactoring:

  • IntelliJ IDEA / Rider / WebStormExtract Parameter refactoring (Windows/Linux-এ Ctrl+Alt+P, macOS-এ ⌥⌘P) core move করে: method body-র ভেতরের literal select করো, parameter হিসেবে extract করো, আর IDE declaration আর প্রতিটা call update করে পুরনো value pass করতে। JetBrains এটা Java, Kotlin, C#, এবং JavaScript/TypeScript-এর জন্য document করেছে। পরে parameter reorder বা rename করতে হলে Change Signature (Ctrl+F6) combine করো।
  • ReSharper (Visual Studio)Introduce Parameter offer করে, যেটা method implementation থেকে একটা expression caller-দের কাছে নিয়ে যায় নতুন parameter যোগ করে আর solution-এর সব call-এ matching argument দিয়ে update করে।
  • Visual Studio (built-in C# refactorings) এবং VS Code with the C# extensionChange method signature এবং Introduce parameter quick action provide করে; literal-কে parameter-এ extract করো তারপর এই post-এর forwarder ladder ব্যবহার করে হাতে যমজ method গুলো merge করো।
  • পুরনো যমজদের নিরাপদ deletion — IntelliJ-এর Safe Delete আর ReSharper-এর usage search নিশ্চিত করে যে কোনো caller এখনো makeSmallJuice ব্যবহার করছে না remove করার আগে।

Body গুলো merge করা তোমারই কাজ — IDE জানতে পারে না যে তিনটা method spiritually একটা। নির্ভরযোগ্য recipe: একটা twin-এ Extract Parameter চালাও, তারপর বাকি twin গুলোকে forwarder-এ convert করো, তারপর IDE-এর safe tool দিয়ে inline বা delete করো।

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

দিকসুবিধাঝুঁকি / মূল্য
Duplicationপড়ার, ঠিক করার, উন্নত করার একটাই bodyBody গুলো সত্যিকারের যমজ না হলে, merge করা চুপচাপ behaviour বদলে দেয়
FlexibilityRange-এর মধ্যে যেকোনো value কোনো নতুন code ছাড়াই কাজ করেOpen range এমন invalid value admit করে যেটা named method গুলো অসম্ভব করে রেখেছিল — validation যোগ করো
API sizeClass surface ছোট হয়; কম শিখতে হয়raiseTenPercent()-এর মতো named method intent document করেছিল; raiseBy(0.10) call site-এ magic number expose করে
MaintenanceRate পরিবর্তনে পাঁচটার বদলে একটা method-এ touch করেএকটা method এখন সব caller-কে serve করে; এতে bug হলে সবার ক্ষতি হয় একসাথে
Evolutionনতুন size, weight, rate বিনামূল্যে আসেভবিষ্যতের "cases" যদি আলাদা logic চায়, method-এর ভেতরে conditional বাড়বে — উল্টো refactoring-এর সময় হলো

Seesaw: Parameterize Method ↔ Replace Parameter with Explicit Methods

এই দুটো refactoring exact inverse, seesaw-এর দুই মাথার মতো। কোনোটাই "ভালোটা" না। Variation-এর আকার ঠিক করে কোন দিকে হেলতে হবে:

জিজ্ঞেস করার প্রশ্নParameterize Method-এর দিকে হেলায়Replace Parameter with Explicit Methods-এর দিকে হেলায়
Variants কি শুধু একটা value-তে আলাদা?হ্যাঁ — ২০০ মিলি বনাম ৩০০ মিলি বনাম ৪০০ মিলিনা — deposit বনাম withdraw আলাদা action
Value কি একটা মসৃণ continuum-এ?হ্যাঁ — যেকোনো weight, যেকোনো percent আসতে পারেনা — discrete case-এর একটা ছোট fixed menu
Method body কি parameter-এ branch করে?না — value সরাসরি formula-তে প্রবাহিত হয়হ্যাঁ — switch সম্পূর্ণ আলাদা code path বেছে নেয়
Caller রা কি runtime data হিসেবে value pass করে?হ্যাঁ — size আসে user input থেকেনা — প্রতিটা caller "height"-এর মতো literal লেখে
Parameter কি cryptic flag বা code?না — sizeMl: 300 naturally পড়া যায়হ্যাঁ — doBanking(2) legend ছাড়া কিছুই বোঝায় না

যেকোনো গোষ্ঠীর সন্দেহজনক method-কে দুই-অক্ষের map-এ রাখতে পারো। অনুভূমিক অক্ষ জিজ্ঞেস করে ভেতরটা কতটা আলাদা; উল্লম্ব অক্ষ জিজ্ঞেস করে সম্ভাব্য parameter কতটা cryptic। সালাম ভাইয়ের juice method গুলো parameterize corner-এ গভীরে বসে আছে:

চিত্র ১০: তোমার method গুলো কোথায় বসে? Juice stall দৃঢ়ভাবে parameterize corner-এ

একটা practical চিহ্ন যে তুমি ভুল দিকে হেলেছ: যদি তোমার সদ্য parameterized method-এ তখনই if (size === "small") { ... } else { ... } দরকার হয়, variants ছিল behaviour, value না — undo করো আর explicit method-এ ভাগ করো। আর অন্য দিকে: যদি তুমি চতুর্থ, পঞ্চম, ষষ্ঠ explicit method যোগ করছ যেগুলো শুধু একটা constant-এ আলাদা, সেগুলো আবার মেলাও।

চিত্র ১১: Seesaw — value-driven variant মেলে; behaviour-driven variant ভাগ হয়

কলেজ কর্নার: seesaw হলো আসলে DRY আর explicitness-এর মধ্যে classic tension, আর এটা কখনো পুরোপুরি সমাধান হয় না। Merge করা reuse maximize করে কিন্তু প্রতিটা call site-কে এমন একটা value বহন করায় যার অর্থ অন্য কোথাও থাকে (raiseBy(0.10) — ০.১০ কি policy না accident?)। Split করা call site-এ readability maximize করে কিন্তু API বাড়ায় আর নতুন code ছাড়া নতুন case forbid করে। Professional অভ্যাস হলো বর্তমান অবস্থানকে provisional মনে করা: codebase গুলো legitimately এই seesaw-এ কয়েকবার ওঠানামা করে যখন requirement স্পষ্ট হয়। একটা payroll system হয়তো raise গুলো raiseBy(f)-এ merge করে, তারপর বছর পরে applyAnnualIncrement() আলাদা করে যখন আইন সেই একটা case-কে বিশেষ audit নিয়ম দেয়। Refactoring design করা হয়েছে reversible হতে — ঠিক এজন্যই Fowler উভয় দিককে winner ঘোষণার বদলে first-class move হিসেবে catalog করেন।

কোন smell গুলো এটা সারায়?

SmellParameterize Method কীভাবে সাহায্য করে
Duplicate Codeপ্রাথমিক লক্ষ্য — প্রায়-একই body একটায় মিলে যায়
Large Classপাঁচটা একই রকম method হয় একটা; class surface ছোট হয়
Long Parameter Listদিক লক্ষ্য করো: এই refactoring একটা parameter যোগ করে, তাই সাবধানে apply করো; extracted value গুলো clump করলে সেগুলোকে একটা object-এ bundle করো
Data Clumpsযখন যে "value" আলাদা সেটা আসলে ২-৩টা value যেগুলো একসাথে ভ্রমণ করে, loose parameter-এর বদলে একটা ছোট parameter object introduce করো
Shotgun SurgeryRate পরিবর্তনে আর পাঁচ জায়গায় edit লাগে না

পুরো ধারণাটা এক mindmap-এ

চিত্র ১২: এক নজরে Parameterize Method — trigger, move, guard, seesaw

দ্রুত রিভিশন বক্স

+================ PARAMETERIZE METHOD ================+
|                                                      |
|  SMELL : makeSmallJuice / makeMediumJuice /          |
|          makeLargeJuice - twin bodies, one value     |
|          differs (200 / 300 / 400)                   |
|                                                      |
|  MOVE  : one method, value becomes a parameter       |
|          makeJuice(fruit, sizeMl)                    |
|                                                      |
|  LADDER: 1 diff the twins  2 write merged method     |
|          3 test each old literal  4 old methods ->   |
|          forwarders  5 migrate callers  6 delete     |
|                                                      |
|  GUARD : open range needs validation at the top      |
|                                                      |
|  SEESAW: value continuum  -> parameterize            |
|          behaviour switch -> explicit methods        |
|          (exact inverse refactoring!)                |
+======================================================+

অনুশীলন exercise

একটা স্কুল canteen system-এ এই method গুলো আছে — নিজে refactoring চেষ্টা করো:

class CanteenBilling {
  priceForSamosaPlate(qty: number): number {
    return qty * 15 + (qty >= 4 ? -5 : 0);   // bulk discount
  }
  priceForIdliPlate(qty: number): number {
    return qty * 30 + (qty >= 4 ? -5 : 0);
  }
  priceForDosaPlate(qty: number): number {
    return qty * 45 + (qty >= 4 ? -5 : 0);
  }
}

তোমার কাজ:

  1. তিনটা body diff করো। নিশ্চিত করো একমাত্র পার্থক্য unit price (15, 30, 45)।
  2. Shared body সহ priceFor(unitPrice: number, qty: number) লেখো, এবং validation যোগ করো যে unitPrice positive।
  3. তিনটা পুরনো method-কে এক লাইনের forwarder বানাও এবং হাতে বা test দিয়ে check করো যে প্রতিটা পুরনো call একই total দেয়।
  4. Caller গুলো migrate করো, forwarder গুলো delete করো।
  5. Stretch question: canteen একটা নিয়ম যোগ করে — শনিবার dosa plate-এ কোনো bulk discount নেই। এই নিয়ম কি তোমার উত্তর বদলে দেয়? (Hint: এখন একটা variant আলাদাভাবে behave করে। seesaw কোন দিকে হেলে সেটা ভাবো, আর priceFor-এ if আসা উচিত কিনা, নাকি dosa-র আবার নিজস্ব method থাকা উচিত।)
  6. Bonus: unit price 15/30/45 এখনো call site-এ magic number হিসেবে আছে। Item price-এর menu কোথায় রাখবে যাতে caller item name pass করে আর price একটাই জায়গায় lookup হয়? (সেই ধারণা — method-কে নিজেই value fetch করতে দেওয়া — হলো পাশের refactoring Replace Parameter with Method Call।)
  7. Chart it: চিত্র ১০-এর নিজস্ব version আঁকো এবং canteen method গুলো সেখানে রাখো। শনিবারের নিয়মের আগে এগুলো কোথায় বসে? নিয়মের পরে কোন দিকে drift করে, আর কোন point-এ এগুলো "split" অর্ধেক পেরিয়ে যাবে?

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

Parameterize Method আসলে কী করে?
এটা কয়েকটা প্রায়-একই রকম method-কে একটাতে মিলিয়ে ফেলে। method গুলো শুধুমাত্র একটা fixed value-তে আলাদা হতে হবে যেটা তাদের body-র ভেতরে লুকিয়ে থাকে — একটা সংখ্যা, একটা string, একটা rate। সেই value-কে বের করে parameter বানাও, আর একটাই shared body রেখে দাও। তিনটা রেসিপি কার্ড হয়ে যায় একটাই রেসিপি কার্ড — শুধু একটা ঘর ফাঁকা থাকে পূরণ করার জন্য।
কীভাবে বুঝব method গুলো মেলানোর মতো যথেষ্ট কাছের?
body গুলো পাশাপাশি রাখো। যদি লাইনে লাইনে মিলে যায় শুধু একটা বা দুটো literal value বাদে, তাহলে মেলাও। কিন্তু যদি ধাপগুলোতেও পার্থক্য থাকে — বাড়তি check, আলাদা branch, আলাদা side effect — তাহলে এগুলো একই রেসিপি না। জোর করে একটাতে মেলালে duplication কমানোর বদলে conditional-এর জঞ্জাল তৈরি হবে।
Parameterize Method কি অন্য কোনো refactoring-এর উল্টো?
হ্যাঁ। Replace Parameter with Explicit Methods হলো এর ঠিক উল্টো। Value-এর মসৃণ scale-এ variants থাকলে Parameterize করো, যেমন ৫%, ১০%, ২০%। আর যখন parameter হলো একটা রহস্যময় switch যেটা সত্যিই আলাদা আলাদা behaviour বেছে নেয়, তখন উল্টো দিকে যাও। ভালো code এই seesaw-এ এদিক-ওদিক যায় যখন design পরিবর্তন হয়।
Fowler-এর দ্বিতীয় সংস্করণে কি এই refactoring-এর অন্য নাম আছে?
Refactoring-এর দ্বিতীয় সংস্করণে এটাকে Parameterize Function বলা হয়েছে, কারণ বইয়ের উদাহরণগুলো JavaScript function ব্যবহার করে। ধারণাটা একদম একই, আর refactoring.com-এর catalog-এ Parameterize Method নামটাকে প্রথম সংস্করণের alias হিসেবে রাখা আছে।
Parameter দিলে কি caller রা ভুল value পাঠাতে পারবে না?
পারবে, আর এটাই এর সৎ মূল্য। makeLargeJuice() কখনো ভুল size পেতে পারত না; makeJuice(-500) পারে। তাই parameterize করার সময় merged method-এর শুরুতে validation যোগ করো, অথবা allowed value-এর union-এর মতো সরু type ব্যবহার করো, যাতে ভুল input চুপ করে না থেকে জোরে জোরে ব্যর্থ হয়।

আরো দেখো

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

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

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

আরও পড়ুন

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 ছোট, পরিষ্কার আর নিরাপদ করা যায়।

আরও পড়ুন