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

Remove Parameter: স্কুলের ফর্ম থেকে 'টেলিগ্রাম ঠিকানা' ঘরটা বাদ দাও

Remove Parameter সহজ ভাষায় — method আর যে parameter ব্যবহার করে না সেটা নিরাপদে কীভাবে মুছবে, মরা parameter গুলো পাঠককে কীভাবে বিভ্রান্ত করে আর প্রতিটা caller-কে বাড়তি কাজ করায়, আর মুছে ফেলার আগে কী কী চেক করতে হবে (interface, override, reflection)।

21 মিনিট আপডেট: June 11, 2026beginner
refactoringremove parameterchange function declarationmethod signaturedead codesimplifying method callsclean code

📮 স্কুলের ফর্মে টেলিগ্রাম ঠিকানার ঘর

ধরো ভর্তির মৌসুম। স্কুলের অফিসের বাইরে লম্বা লাইন। প্রত্যেকের হাতে চার পাতার ভর্তি ফর্ম। জামাল সাহেব এসেছেন তার মেয়েকে ভর্তি করাতে। এক লাইন এক লাইন করে পূরণ করছেন — নাম, জন্মতারিখ, ঠিকানা। তারপর আসে ১৪ নম্বর ঘর: "টেলিগ্রাম ঠিকানা।"

টেলিগ্রাম? উনি থামলেন। বাংলাদেশে টেলিগ্রাম সার্ভিস বন্ধ হয়েছে অনেক বছর আগে — তার মেয়ে তখনো জন্মায়নি। লাইনের কোনো পরিবারেরই টেলিগ্রাম ঠিকানা নেই। পাশের ভদ্রমহিলাকে জিজ্ঞেস করলেন। তিনি কাঁধ ঝাঁকালেন আর ফর্ম দেখালেন — ওই ঘরে ফোন নম্বর লিখেছেন। পেছনের ভদ্রলোক লিখেছেন "N/A"। একজন সতর্ক বাবা লিখেছেন ইমেইল।

কাউন্টারে বসে আছেন রাহেলা আপা, বাইশ বছর ধরে অফিসের clerk। জামাল সাহেব সরাসরি জিজ্ঞেস করলেন। তিনি হাসলেন: "ওই ঘরটা? আমরা কখনো দেখি না ভাই। একটা ড্যাশ দিয়ে দিন। ফর্মটা অনেক বছর আগে বানানো, কেউ ঠিকমতো ছাপায়নি।"

কিন্তু একটু ভাবো — এই মরা ঘরটার কত খরচ, প্রতিটা ভর্তির মৌসুমে:

  • প্রতিটা অভিভাবক থামেন, ভুরু কোঁচকান, এক মিনিট নষ্ট হয় — চারশো অভিভাবক, চারশো মিনিট।
  • রাহেলা আপা প্রতিদিন দশবার একই প্রশ্নের উত্তর দেন, আসল কাজের বদলে।
  • যা লেখা হয় তা একদম আবর্জনা — ফোন নম্বর, ইমেইল, ড্যাশ — স্কুলের ফাইলে চিরকালের জন্য জমা অর্থহীন তথ্য।
  • সবচেয়ে খারাপ: এ বছর নতুন office সহকারী এসেছে, সালাম। অফিশিয়াল ফর্মে ঘর দেখে সে স্বাভাবিকভাবেই মনে করল এটা জরুরি — আর তিনটা ফর্ম বাতিল করে দিল কারণ ১৪ নম্বর ঘর খালি ছিল। সত্যিকারের পরিবার, সত্যিকারের দেরি, এমন একটা ঘরের কারণে যেটা এক দশকেরও বেশি সময় ধরে অর্থহীন।

আসলে, ফর্মের একটা মরা ঘর চুপচাপ বসে থাকে না। এটা ফর্ম স্পর্শ করা সবাইকে বিভ্রান্ত করে, সবার একটু একটু করে সময় নষ্ট করে, আর মাঝে মাঝে — সালামের মতো সৎ নতুন কারো মাধ্যমে — আসল ভুল করায়। সমাধান? পাঁচ মিনিট প্রিন্টারে: ১৪ নম্বর ঘর ছাড়া ফর্ম নতুন করে ছাপাও।

মেথডেরও ফর্ম আছে — সেটা হলো parameter list। মেথড আর যে parameter পড়ে না সেটা হলো টেলিগ্রাম ঠিকানার ঘর। প্রতিটা caller নিষ্ঠার সাথে একটা মান হিসাব করে পাঠায় যা শূন্যে মিলিয়ে যায়, আর প্রতিটা পাঠক মনে করে এটা নিশ্চয়ই গুরুত্বপূর্ণ। Remove Parameter হলো ফর্ম নতুন করে ছাপানো: মরা ঘর মুছে ফেলো, caller-দের ঠিক করো, আর signature-কে আবার সত্যি কথা বলতে দাও।

চিত্র ১: ফর্মের যাত্রা — একটা মরা ঘর লাইনের প্রতিটা অভিভাবককে কষ্ট দেয়, যতক্ষণ না কেউ নতুন করে ছাপায়।

Remove Parameter কী? 🧐

Remove Parameter হলো Martin Fowler-এর Refactoring বই থেকে নেওয়া একটা refactoring:

method body আর যে parameter ব্যবহার করে না সেটা মুছে ফেলো।

যুক্তিটা সহজ। প্রতিটা parameter একসাথে দুটো কাজ করে:

  1. caller-কে একটা প্রশ্নের উত্তর দিতে হয়। "কোন অঞ্চল? কোন মুদ্রা? কোন তারিখ?" প্রতিবার প্রতিটা parameter-এর জন্য caller-কে একটা মান খুঁজে বের করতে বা হিসাব করতে হয়।
  2. পাঠকের কাছে একটা প্রতিশ্রুতি। signature-এ একটা parameter মানে: এই মান আমি যা করি তাকে প্রভাবিত করে। পাঠক এই ধরে নিয়ে পড়তে থাকে।

একটা অব্যবহৃত parameter দুটোই ভাঙে। Caller একটা প্রশ্নের উত্তর দেয় যা কেউ জিজ্ঞেস করেনি। পাঠক একটা মিথ্যা প্রতিশ্রুতিতে বিশ্বাস করে। প্রতি ঘটনায় খরচ ছোট, কিন্তু সেটা কখনো থামে না। আর মাঝে মাঝে একটা সালাম তৈরি হয় — যে মরা ঘরটাকে গুরুত্বের সাথে নেয় আর সত্যিকারের কিছু ভেঙে দেয়।

এই zombie-রা কীভাবে আসে? প্রায় কখনো ইচ্ছাকৃতভাবে না:

  • Refactoring-এর বাকি পড়া। body একসময় ট্যাক্স রেট বেছে নিতে region পড়ত। তারপর ট্যাক্স flat rate হয়ে গেল, region logic মুছে গেল — কিন্তু parameter রয়ে গেল। কারণ সেটা মুছলে সব caller ছুঁতে হত, আর কেউ তাড়ায় ছিল।
  • Speculative generality। "চলো currency parameter রাখি — একদিন হয়তো আন্তর্জাতিক বাজারে যাব।" পাঁচ বছর পরেও: শুধু টাকায় কাজ হচ্ছে, আর parameter কখনো ব্যবহার হয়নি। এটা parameter-এর পোশাকে Speculative Generality smell।
  • Copy-paste inheritance। কোনো sibling থেকে copy করা method সেই sibling-এর পুরো signature রেখেছে, যদিও এই version-এর কম দরকার।
💡

নামের একটা নোট: Refactoring-এর ২য় সংস্করণে, Fowler Remove Parameter-কে (Add Parameter আর Rename Method-সহ) একটা umbrella refactoring-এ মিলিয়েছেন যার নাম Change Function Declaration। নাম, parameter, signature — এগুলো একটা method-এর public চেহারা। পরিবর্তনের mechanics একই। পুরনো বই আর refactoring.guru আলাদা নাম রাখে, কিন্তু দুটোই একই কাজ বোঝায়। আর দাড়িপাল্লার কথা মনে রেখো: Remove Parameter হলো Add Parameter-এর সরাসরি উল্টো — সেই article টিফিন স্লিপে একটা দরকারী column যোগ করে, এটা স্কুলের ফর্ম থেকে একটা মরা ঘর সরায়।

একটা মানসিকতার পরিবর্তন এই refactoring-কে clear করে দেয়: code মুছে ফেলা হলো অগ্রগতি। শিক্ষার্থীরা মনে করে জিনিস সরানো "নেতিবাচক কাজ"। কিন্তু professionals জানে প্রতিটা লাইন, প্রতিটা ঘর, প্রতিটা parameter যা আছে সেটা চিরকাল পড়তে, বুঝতে, test করতে আর maintain করতে হবে। সবচেয়ে সস্তায় maintain করা যায় এমন code হলো যে code নেই।

পুরো বিষয়টা এক নজরে:

চিত্র ২: Remove Parameter এক নজরে — zombie-রা কীভাবে জন্মায়, মৃত্যুর চার-পথ প্রমাণ, stop sign, আর tools।

কলেজের কোণা: একটা published API থেকে parameter সরানো হলো textbook অনুযায়ী breaking change — যোগ করার চেয়ে কঠিন। যোগ করা backward compatible হতে পারে, কারণ পুরনো caller নতুন optional field উপেক্ষা করতে পারে। কিন্তু সরানো কখনোই না, কারণ বিদ্যমান call site এখন এমন argument পাঠাচ্ছে যা function আর accept করে না। এ কারণেই public API-রা removal একটা deprecation pipeline-এর মধ্য দিয়ে চালায়: ঘোষণা করো, deprecated mark করো, একটা compatibility shim রাখো, আর পরবর্তী major version-এ বাদ দাও। REST API-রাও একই কাজ করে — server সাধারণত বছরের পর বছর পুরনো বা বন্ধ field accept and ignore করে, যাতে পুরনো client সেদিন না ভাঙে যেদিন কোনো field মরে যায়।

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

এই লক্ষণগুলো খোঁজো:

  1. Body কখনো parameter পড়ে না। সবচেয়ে সহজ ক্ষেত্র। তোমার linter সম্ভবত ইতিমধ্যে এটা underline করেছে — TypeScript-এর noUnusedParameters, ESLint-এর no-unused-vars, আর .NET analyzer সবাই এটা flag করে।
  2. Parameter পড়া হয় কিন্তু কিছু পরিবর্তন হয় না। আরো লুকানো: মান log করা হয় আর ভুলে যাওয়া হয়, বা কেউ ব্যবহার করে না এমন variable-এ assign করা হয়। কার্যত মৃত, শুধু ভালো ছদ্মবেশে।
  3. Caller-রা স্পষ্টতই এটা supply করতে কষ্ট পাচ্ছে। যখন call site null, undefined, "", বা /* not used? */-এর মতো comment পাঠায়, caller-রা ইতিমধ্যে আবিষ্কার করেছে ঘরটা মরা — signature শুধু এটা স্বীকার করেনি। এটাই হলো লাইনের অভিভাবকরা ১৪ নম্বর ঘরে উত্তর উদ্ভাবন করছে।
  4. একটা refactoring এইমাত্র ব্যবহারকারী code সরিয়েছে। সবচেয়ে ভালো মুহূর্ত: দুই মিনিট আগে body সরল করেছ। এখনই signature পরিষ্কার করো, যখন মনে আছে কেন।
  5. Method নিজেই মান হিসাব করতে পারে। বিশেষ ক্ষেত্র: যদি method তার অন্য argument বা নিজের state থেকে মান derive করতে পারে, caller-কে সেটা পাঠাতে বলো না — সেটা Replace Parameter with Method Call, এর কাছের cousin।

আর stop sign — যেসব পরিস্থিতিতে "অব্যবহৃত" parameter রাখতে হবে:

  • Interface বা base class এটা দাবি করে। ধরো process(order, context) কোনো interface implement করছে। এই implementation context উপেক্ষা করতে পারে, কিন্তু signature contract-এর অন্তর্গত। contract থেকে সরাও — পুরো পরিবারসহ — নইলে একদমই না।
  • Sibling override-রা এটা ব্যবহার করে। template-method design-এ, base এমন data পাঠায় যা শুধু কিছু subclass ব্যবহার করে। তোমার subclass এটা উপেক্ষা করলেই এটা মরা না — পরিবার চেক করো।
  • একটা framework এটাকে নাম বা position অনুযায়ী bind করে। Route handler, event callback, DI-injected factory method — framework প্রায়ই fixed argument shape দিয়ে তোমাকে call করে। একটা parameter সরালে position সরে যায় আর binding ভাঙে।
  • Delegate আর callback। যদি method এমন জায়গায় assign করা হয় যেখানে নির্দিষ্ট function type আশা করা হয় — (event, index) => void — shape বাইর থেকে নির্ধারিত।

একটা মরা ঘর শুধু আবর্জনা তৈরি করে — এর প্রমাণ চাও? রাহেলা আপা একবার হিসাব করলেন চারশো অভিভাবক টেলিগ্রাম ঘরে আসলে কী লিখেছে:

চিত্র ৩: একটা মরা ঘরের জন্য caller-রা যা supply করে — একদম আবর্জনা, কারণ কোনো সৎ উত্তর নেই।

দেখো, code-এ এই chart মানে হলো সেই null, empty string, আর /* whatever */ comment যা zombie parameter সহ method-এর call site-এ জমা হয়। Caller-রা ইতিমধ্যে vote দিয়েছে: ঘরটা মরা।

একবার নিশ্চিত হলে, কতটা আক্রমণাত্মকভাবে মুছতে পারবে? দুটো প্রশ্ন ঠিক করে: caller কতজন, আর signature কি কোনো contract-এর অংশ?

চিত্র ৪: Deletion strategy — কম caller সহ private signature এক keystroke-এ মরে; contract-bound public signature-এর দরকার staged shim।

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

TypeScript-এ টেলিগ্রাম ঘর দেখো:

// BEFORE — "region" হলো টেলিগ্রাম ঠিকানা
function priceWithTax(amount: number, currency: string, region: string): number {
  // একসময় region ট্যাক্স রেট বেছে নিত।
  // ২০২৪-এর flat-tax পরিবর্তনের পর, এটা আর কখনো ব্যবহার হয় না।
  return amount * 1.18;
}
 
// প্রতিটা caller এখনো region হিসাব করার বোঝা বহন করছে:
const region = lookupRegionFromPincode(customer.pincode); // নষ্ট কাজ!
const total = priceWithTax(amount, "BDT", region);

দ্বিগুণ ক্ষতি লক্ষ্য করো: caller একটা আসল lookup (lookupRegionFromPincode) করছে শুধু একটা মরা ঘর ভরাতে, আর priceWithTax-এর পাঠক স্বাভাবিকভাবেই ধরে নেয় কোথাও আঞ্চলিক ট্যাক্স logic আছে।

// AFTER — মরা ঘর ছাড়া নতুন করে ছাপানো ফর্ম
function priceWithTax(amount: number): number {
  return amount * 1.18;
}
 
// Caller নষ্ট lookup থেকেও মুক্তি পায়:
const total = priceWithTax(amount);

দুটো parameter চলে গেল (currency-ও অব্যবহৃত ছিল — তুমি কি ধরেছিলে?), একটা অপচয়ী lookup মুছে গেল, আর signature অবশেষে সত্যি কথা বলছে: এই মূল্য শুধু amount-এর উপর নির্ভর করে, আর কিছু না।

ধাপে ধাপে, নিরাপদ উপায়ে 🪜

মুছে ফেলা সহজ মনে হয় — ঠিক এ কারণেই মানুষ check বাদ দেয় আর ঝামেলায় পড়ে। পুরো pipeline:

চিত্র ৫: Removal pipeline — প্রথমে মৃত্যু প্রমাণ করো, public হলে stage করো, তারপর compiler-কে caller checklist দিতে দাও।

ধাপ ১ — চার উপায়ে প্রমাণ করো parameter মরা।

  1. Body: parameter পড়া হচ্ছে না (IDE ধূসর করে দেয় বা linter flag করে)।
  2. পরিবার: কোনো override, sibling implementation, বা interface/base declaration এটা ব্যবহার করে না।
  3. Framework: কোনো DI, serializer, router, বা test harness এই method-এ নাম/position অনুযায়ী argument bind করে না।
  4. Dynamic call: method-এর নাম text হিসেবে search করো — reflection বা dynamic dispatch হয়তো positional ভাবে argument পাঠাচ্ছে।

ধাপ ২ — Method polymorphic হলে, পরিবারের পরিবর্তন পরিকল্পনা করো। Parameter অবশ্যই interface/base আর প্রতিটা implementation থেকে একসাথে সরাতে হবে, নইলে একদমই না। পরিবারের signature গুলো compatible থাকতে হবে।

ধাপ ৩ — Public API-র জন্য, delegating overload দিয়ে stage করো। অন্য বিভাগ এখনো যে ফর্ম ছাপাচ্ছে সেখান থেকে হঠাৎ ঘর টেনে তুলো না। পরিষ্কার signature যোগ করো, পুরনোটা forward করতে রাখো, আর deprecate করো:

// INTERMEDIATE — দুটো shape কাজ করে; পুরনোটা সতর্ক করে
function priceWithTax(amount: number): number {
  return amount * 1.18;
}
 
/** @deprecated currency এবং region উপেক্ষিত; priceWithTax(amount) call করো। */
function priceWithTaxLegacy(amount: number, currency: string, region: string): number {
  return priceWithTax(amount);
}

(C# বা Java-তে এটা একই নামের true overload আর [Obsolete]/@Deprecated; নিচে দেখব।)

ধাপ ৪ — Declaration থেকে parameter সরাও (internal caller-রা)। Typed ভাষায় এখন magic আসে: compile করো, আর compiler একটা সম্পূর্ণ to-do list দেয়। প্রতিটা লাল error হলো এমন caller যে argument পাঠাচ্ছে যেটা মুছতে হবে।

ধাপ ৫ — প্রতিটা caller ঠিক করো, আর upstream waste পরিষ্কার করো। প্রতিটা call site-এ মরা argument মুছো — আর এক লাইন উপরে তাকাও। প্রায়ই caller শুধু সেটা পাঠাতে সেই মান হিসাব করেছিল। হিসাবটাও মুছো। মাঝে মাঝে cleanup সুন্দরভাবে cascade করে: lookup function তার শেষ caller হারায় আর নিজেও মুছে ফেলা যায়।

// caller, আগে:
const region = lookupRegionFromPincode(customer.pincode);
const total = priceWithTax(amount, "BDT", region);
 
// caller, পরে — lookup-ও চলে যায়:
const total = priceWithTax(amount);

ধাপ ৬ — পুরো test suite চালাও। Behaviour byte-for-byte একই হতে হবে। শুধু সততাই পরিবর্তন হয়েছে।

Staged period-এ, legacy caller-এর argument shim-এর মধ্য দিয়ে প্রবাহিত হয় আর চুপচাপ পড়ে যায়:

চিত্র ৬: Compatibility shim পুরনো shape accept করে, চুপচাপ মরা argument ফেলে দেয়, আর forward করে — migration চলার সময় কোনো caller ভাঙে না।

আর codebase-এর সামগ্রিক যাত্রায় প্রতিটা Change Function Declaration পদক্ষেপের মতো একই নিরাপদ মধ্যবর্তী অবস্থা আছে:

চিত্র ৭: Staged removal-এর তিনটা অবস্থা — মধ্যবর্তী অবস্থায় দুটো shape চলে, তাই বাইরের দল নিজেদের সময়মতো migrate করতে পারে।
⚠️

এই refactoring-এর সবচেয়ে বিপজ্জনক মুহূর্ত হলো ধাপ ১ অলসভাবে করা। "IDE ধূসর করে দিয়েছে, তাই এটা মরা" — এটা প্রমাণ না। ধূসর মানে এই body এটা পড়ে না। Sibling override, interface contract, reflection, বা position অনুযায়ী argument inject করা test framework সম্পর্কে কিছুই বলে না। একটা route handler বা event callback থেকে parameter সরালে পরের প্রতিটা argument এক position সরে যায়। Dynamic ভাষায় এই bug perfectly type-check করতে পারে। চারটা check করো, তারপর পুরো test suite চালাও — শুধু এই একটা method-এর test না, কারণ ভাঙাভাঙি, যদি থাকে, দূরের caller-এ থাকে।

কলেজের কোণা: সেই position-shifting bug-এর একটা নাম আছে যা তুমি আবার দেখবে: positional coupling। যেকোনো caller যে position অনুযায়ী argument পাঠায় — reflection invocation, JavaScript callback, Python *args forwarding, serialized RPC frame — সে parameter-এর ক্রম আর সংখ্যার উপর নির্ভর করে, নামের উপর না। দুই নম্বর parameter সরালে চুপচাপ পুরনো argument তিন নতুন argument দুই হয়ে যায়। Typed ভাষা count mismatch ধরে; dynamic ভাষা আর binary protocol প্রায়ই ধরে না। এ কারণেই আধুনিক API style positional-এর বদলে named field পছন্দ করে (keyword argument, JSON body, protobuf field number) — এগুলো signature evolution দুই দিকেই নাটকীয়ভাবে নিরাপদ করে।

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

ধরো একটা hospital appointment system, চার বছর পুরনো। booking method zombie parameter জমিয়েছে যেভাবে একটা পুরনো ড্রয়ারে মরা কলম জমে:

// BEFORE — টেলিগ্রাম ঠিকানাগুলো গণনা করো
class AppointmentService {
  bookAppointment(
    patientId: string,
    doctorId: string,
    slot: TimeSlot,
    faxNumber: string,        // fax confirmation ২০২২-এ বন্ধ হয়েছে
    insuranceCode: string,    // insurance module তার নিজস্ব service-এ চলে গেছে
    isLegacyPatient: boolean, // migration শেষ; সবাই এখন "new system"
  ): Appointment {
    const appointment = new Appointment(patientId, doctorId, slot);
    this.repo.save(appointment);
    this.sms.sendConfirmation(patientId, slot);
    return appointment;
  }
}

ছয়টার মধ্যে তিনটা parameter মরা। এখন দেখো একজন caller-কে আজ কী করতে হচ্ছে:

// একজন caller, নীরবে কষ্ট পাচ্ছে:
const fax = patient.faxNumber ?? "";                    // নষ্ট lookup
const insurance = await insuranceApi.getCode(patient);  // নষ্ট NETWORK CALL!
const appointment = service.bookAppointment(
  patient.id,
  doctor.id,
  chosenSlot,
  fax,
  insurance,
  false, // এখানে false মানে কী? নতুন পাঠকের কোনো ধারণা নেই
);

একটা insurance code fetch করতে প্রতিটা booking-এ একটা network call হচ্ছে যা তারপর ফেলে দেওয়া হচ্ছে। এটা telegram field-এর সর্বনাশা রূপ — শুধু confusing না, সক্রিয়ভাবে সময় আর অর্থ নষ্ট করছে।

চারটা check চালাও: body — কোনো read নেই (linter নিশ্চিত করেছে); পরিবার — AppointmentService একটা concrete class, কোনো interface এই method declare করে না; framework — controller থেকে সরাসরি call হয়, shape অনুযায়ী bound না; dynamic — text search-এ কোনো reflection নেই। সব পরিষ্কার। ফর্ম নতুন করে ছাপাও:

// AFTER — সৎ signature
class AppointmentService {
  bookAppointment(patientId: string, doctorId: string, slot: TimeSlot): Appointment {
    const appointment = new Appointment(patientId, doctorId, slot);
    this.repo.save(appointment);
    this.sms.sendConfirmation(patientId, slot);
    return appointment;
  }
}
 
// Caller আরো ছোট, দ্রুত, আর স্পষ্ট হয়:
const appointment = service.bookAppointment(patient.id, doctor.id, chosenSlot);

দুটো class shape, পাশাপাশি:

চিত্র ৮: একই behaviour, অর্ধেক ফর্ম — প্রতিটা remaining parameter সত্যিকারের booking পরিচালনা করে।

লাভ, গণনা করা:

  1. প্রতিটা booking-এ একটা network call — চলে গেল। Code মুছে আসল performance লাভ।
  2. রহস্যময় false — চলে গেল। কোনো পাঠক আর কখনো ভাববে না এর মানে কী।
  3. Test ছোট হলো। পুরনো test শুধু signature satisfy করতে fax number আর insurance code বানাত। নতুন test শুধু যা গুরুত্বপূর্ণ তা বলে।
  4. Signature এখন সত্যিকারের document। Booking নির্ভর করে patient, doctor, slot-এর উপর। ঠিক যা একজন নতুন মানুষ অনুমান করত, আর এখন ঠিক যা code বলছে।

আর caller-রা upstream-এর আসল কাজ ঝরিয়ে ফেলায় (সেই insurance lookup!), পুরস্কার শুধু সৌন্দর্যের না, পরিমাপযোগ্য:

চিত্র ৯: মরা parameter আসল millisecond খরচ করে — তাদের খাওয়ানো নষ্ট lookup-রা field-এর সাথে একসাথে অদৃশ্য হয়।

C#-এ একই Refactoring 🎯

C# staged, public-API-safe version সবচেয়ে ভালো দেখায়। এখানে একটা report generator যার format parameter মরে গেছে যখন PDF একমাত্র supported output হয়ে গেছে:

// BEFORE — "format" মরা; PDF ২০২৪ সালে জিতেছে
public class ReportGenerator
{
    public byte[] Generate(int reportId, string format, bool useLegacyEngine)
    {
        // format উপেক্ষিত — সবসময় PDF; legacy engine অনেক আগেই মুছা
        var data = _repo.LoadReport(reportId);
        return _pdfEngine.Render(data);
    }
}

ধাপ ১ — পরিষ্কার overload পরিচয় করিয়ে দাও; পুরনোটাকে delegate করাও আর সতর্ক করাও:

public class ReportGenerator
{
    // NEW — সৎ signature
    public byte[] Generate(int reportId)
    {
        var data = _repo.LoadReport(reportId);
        return _pdfEngine.Render(data);
    }
 
    // OLD — বাইরের caller-দের জন্য সাময়িকভাবে রাখা
    [Obsolete("format এবং useLegacyEngine উপেক্ষিত। Generate(reportId) call করো।")]
    public byte[] Generate(int reportId, string format, bool useLegacyEngine)
        => Generate(reportId);
}

প্রতিটা বিদ্যমান caller compile হয়, একই আচরণ করে, আর পরিষ্কার method-এ pointing warning দেখে। Internal caller-রা সাথে সাথে migrate করে। বাইরের দল একটা release cycle-এ migrate করে। তারপর:

// AFTER — চূড়ান্ত অবস্থা
public class ReportGenerator
{
    public byte[] Generate(int reportId)
    {
        var data = _repo.LoadReport(reportId);
        return _pdfEngine.Render(data);
    }
}

C#-নির্দিষ্ট সতর্কতা:

  • Interface member: যদি Generate IReportGenerator-এ থাকে, interface আর সব implementation একসাথে পরিবর্তন করো — একটা implementation নিজে থেকে parameter drop করতে পারে না।
  • ইচ্ছাকৃতভাবে অব্যবহৃত parameter যেগুলো রাখতেই হবে (contract-bound), সেগুলো C#-এ _ নাম দিতে পারো (discard naming convention)। এটা পাঠককে বলে "ইচ্ছাকৃতভাবে উপেক্ষিত, দুর্ঘটনাবশত না।"
  • Analyzer সাহায্য করে: .NET code-quality rule CA1801 / IDE0060 ("Review unused parameters") build-এ স্বয়ংক্রিয়ভাবে এই zombie-দের flag করে। চালু করো আর telegram field-রা নিজেরাই প্রকাশ পাবে।

Python-এ দ্রুত একটু দেখা 🐍

Python-এ compiler নেই যে ভাঙা caller-দের তালিকা দেবে। তাই staged shim আর runtime warning হলো standard পদ্ধতি:

import warnings
 
def price_with_tax(amount: float) -> float:
    """পরিষ্কার, সৎ signature।"""
    return amount * 1.18
 
def price_with_tax_legacy(amount: float, currency: str, region: str) -> float:
    """Deprecated: currency এবং region উপেক্ষিত।"""
    warnings.warn(
        "currency এবং region উপেক্ষিত; price_with_tax(amount) call করো",
        DeprecationWarning,
        stacklevel=2,
    )
    return price_with_tax(amount)

একটা Python-নির্দিষ্ট ফাঁদ: keyword argument ব্যবহার করা caller — price_with_tax(amount=100, region="south")region অদৃশ্য হওয়ার সাথে সাথে TypeError তোলে। কিন্তু *args-এর মাধ্যমে positional ভাবে forward করা caller চুপচাপ mis-bind হতে পারে। জয় ঘোষণার আগে function-এর নাম আর parameter-এর নাম — দুটোই text হিসেবে search করো।

IDE সাপোর্ট 🛠️

Signature surgery প্রতিটা বড় IDE-তে automated — একই "Change Signature" tooling যেটা parameter যোগ করে সেটা সরিয়েও দেয়:

ToolShortcutকী করে
JetBrains IDE (IntelliJ IDEA, Rider, WebStorm, PyCharm)Ctrl+F6 (macOS-এ Cmd+F6) — Change Signatureতালিকা থেকে parameter untick করো আর IDE declaration এবং প্রতিটা call site থেকে সেটা মুছে দেয়, আগে সব পরিবর্তনের preview সহ।
Visual StudioCtrl+R, Ctrl+V — Change SignatureParameter select করো, Remove চাপো, solution-wide edit-এর preview দেখো, apply করো।
VS Codeভাষা-নির্ভর: C#, Java, আর TypeScript extension "Change Signature" code action দেয়; নইলে manually parameter মুছো আর compiler error caller-দের গণনা করুক।
Linter / analyzerTypeScript noUnusedParameters, ESLint no-unused-vars, .NET IDE0060/CA1801এগুলো refactoring করে না, কিন্তু candidate খোঁজে — telegram field-এর জন্য তোমার radar।

Automation সম্পর্কে একটা সৎ সতর্কতা: IDE প্রতিটা call site-এ argument expression সরায়, কিন্তু লক্ষ্য করবে না যে caller দুই লাইন আগে সেই মান শুধু এখানে পাঠাতেই হিসাব করেছে। নষ্ট lookupRegionFromPincode() লাইনটা থেকে যাবে যদি না তুমি সেটা দেখো। Automated removal-এর পরে, প্রতিটা পরিবর্তিত call site একবার মানুষের চোখ দিয়ে দেখো।

সুবিধা ও ঝুঁকি ⚖️

সুবিধাঝুঁকি / খরচ
Signature সৎ হয় — প্রতিটা remaining parameter সত্যিই গুরুত্বপূর্ণ।Interface, base class, বা delegate shape দাবি করা parameter সরালে contract ভাঙে — প্রথমে পরিবার চেক করো।
Caller-রা আসল নষ্ট কাজ (lookup, এমনকি network call) থেকে মুক্তি পায় যা শুধু মরা ঘর ভরাতে ছিল।এখানে অব্যবহৃত parameter polymorphic design-এ sibling override-এ ব্যবহৃত হতে পারে — "একটা body-তে অব্যবহৃত" মানে "মরা" না।
Test সরল হয় — উপেক্ষিত মান আর বানাতে হয় না।Public API removal হলো breaking change; delegating overload + [Obsolete] দিয়ে stage করো।
তালিকা ছোট হয় — Long Parameter List-এর সরাসরি প্রাথমিক চিকিৎসা।Framework/reflection binding নাম বা position অনুযায়ী চুপচাপ ভাঙে, বিশেষত dynamic ভাষায় — মুছার আগে text-search করো।
Add Parameter-এর সরাসরি উল্টো: যে দাড়িপাল্লা signature-কে বাস্তবতার সাথে মেলায়।পরের sprint-এ সত্যিই দরকার হলে (সত্যিই scheduled, "someday" না), সরানো আর ফিরিয়ে আনা প্রতিটা caller-কে দুইবার ঘুরায়।

কোন Smell-গুলো সারায়? 🧹

SmellRemove Parameter কীভাবে সাহায্য করে
Long Parameter Listসবচেয়ে সরাসরি trim: মরা entry-গুলো প্রথমে তালিকা ছাড়ে।
Speculative Generality"Someday" parameter যারা কোনো উদ্দেশ্য খুঁজে পায়নি, সেগুলো সেই someday-এর সাথে মুছে যায়।
Dead Codeএকটা অব্যবহৃত parameter signature-এ dead code — আর সেটা সরালে প্রায়ই caller-এ dead computation প্রকাশ পায়।
রহস্যময় argument (call site-এ false, null, "")পাঠক decode করতে পারে না এমন magic value অদৃশ্য হয় যখন সেগুলো দাবি করা ঘর অদৃশ্য হয়।

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

+----------------------------------------------------------------+
|                REMOVE PARAMETER — CHEAT SHEET                   |
+----------------------------------------------------------------+
| Story    : Delete "Telegram Address" from the school form      |
| Goal     : Signature lists ONLY values the method really uses  |
| 2nd ed.  : Part of "Change Function Declaration" (Fowler)      |
| Inverse  : Add Parameter (same seesaw, other direction)        |
|                                                                |
| PROVE IT IS DEAD (all four):                                   |
|   1. Body never reads it                                       |
|   2. No override / interface / sibling needs it                |
|   3. No framework binds by name or position                    |
|   4. No reflection / dynamic call passes it                    |
|                                                                |
| SAFE STEPS: stage with delegating overload if public ->        |
|   delete from declaration -> compiler lists callers ->         |
|   fix each caller AND delete upstream wasted work -> ALL tests |
|                                                                |
| IDE      : JetBrains Ctrl+F6 | VS Ctrl+R,Ctrl+V                |
| Radar    : noUnusedParameters / IDE0060 / CA1801               |
+----------------------------------------------------------------+

অনুশীলনের প্রশ্ন ✍️

ধরো একটা food-delivery app-এ এই restaurant rating function আছে। দল মনে করছে zombie parameter আছে:

class RatingService {
  submitRating(
    orderId: string,
    stars: number,
    comment: string,
    deviceImei: string,      // ২০২৩-এ পরিকল্পিত "fraud check" এর জন্য, কখনো বানানো হয়নি
    appVersion: string,      // একসময় log হতো; log লাইনটা পরে মুছে গেছে
    restaurantTier: string,  // হুম... সাবধানে চেক করো!
  ): Rating {
    const rating = new Rating(orderId, stars, comment);
    this.repo.save(rating);
    this.notifier.informRestaurant(orderId, stars);
    return rating;
  }
}
 
// Codebase-এ অন্য জায়গায়:
class PremiumRatingService extends RatingService {
  override submitRating(
    orderId: string, stars: number, comment: string,
    deviceImei: string, appVersion: string, restaurantTier: string,
  ): Rating {
    if (restaurantTier === "premium" && stars <= 2) {
      this.escalation.alertAccountManager(orderId);  // restaurantTier ব্যবহার করছে!
    }
    return super.submitRating(orderId, stars, comment, deviceImei, appVersion, restaurantTier);
  }
}

কাজ:

  1. প্রতিটা সন্দেহজনক parameter-এ four-check test চালাও। deviceImei, appVersion, restaurantTier-এর মধ্যে কোনগুলো সত্যিকারের মরা, আর কোনটা ফাঁদ? (Hint: subclass মনোযোগ দিয়ে পড়ো।)
  2. সত্যিকারের মরা parameter গুলো stage-এ সরাও: intermediate state দেখাও (clean signature + deprecated delegating method) আর উভয় class-এর final state। চিত্র ৭-এর কোন state প্রতিটা snapshot represent করে mark করো।
  3. একজন caller এখন submitRating call করার আগে const imei = await device.readImei(); করছে। সেই লাইনের কী হওয়া উচিত, আর কেন এই ধরনের cleanup হলো Remove Parameter-এর লুকানো bonus?
  4. দলের lead বলছে: "deviceImei রাখো — আমরা পরের বছর fraud system বানাতে পারি।" speculative generality শব্দটা ব্যবহার করে দুটো বাক্যে উত্তর দাও, আর বলো system সত্যিই বানানো হলে পরে parameter ফিরিয়ে আনতে কী খরচ হবে। (মনে রেখো: Add Parameter ঠিক সেদিনের জন্যই আছে, তার নিজস্ব safe staging সহ।)
  5. Bonus: restaurantTier deletion থেকে বেঁচে যায় — কিন্তু design কি উন্নত হতে পারে যাতে base class signature premium-only data না বহন করে? একটা বিকল্প sketch করো। (Hint: PremiumRatingService কি orderId থেকে নিজেই tier fetch করতে পারে? সেটা হলো Replace Parameter with Method Call।)

যদি সঠিকভাবে restaurantTier-কে deletion থেকে বাঁচিয়ে থাকো, অভিনন্দন — তুমি এই refactoring-এর সবচেয়ে গভীর শিক্ষা শিখেছ: একটা body-তে অব্যবহৃত মানে পরিবারে অব্যবহৃত না। ফর্ম নতুন করে ছাপাও, কিন্তু আগে প্রতিটা copy পড়ো। রাহেলা আপা ১৪ নম্বর ঘর বাদ দেওয়ার আগে রেকর্ড রুম চেক করেছিলেন; তোমারও করা উচিত।

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

parameter টা ব্যবহার হচ্ছে না — কিন্তু রেখে দিলে কী সমস্যা? এতে কি আসলেই কারো ক্ষতি হচ্ছে?
প্রতিদিন সবার একটু একটু করে ক্ষতি হচ্ছে। প্রতিটা caller-কে একটা মান হিসাব করে পাঠাতে হয় যা কোথাও যায় না। প্রতিটা পাঠক ধরে নেয় এটা গুরুত্বপূর্ণ আর সময় নষ্ট করে এটা trace করে। প্রতিটা test-কে এর জন্য একটা মান বানাতে হয়। আর এটা বোঝার পথ আটকে দেয়: signature দাবি করে একটা dependency আছে যা আসলে নেই। Fowler সরাসরি বলেছেন: যে parameter আর দরকার নেই সেটা সরিয়ে দাও।
মুছে ফেলার আগে কীভাবে নিশ্চিত হব যে parameter টা সত্যিই ব্যবহার হচ্ছে না?
চারটা জায়গা চেক করো, শুধু একটা না। প্রথম, method body — parameter টা কোথাও পড়া হচ্ছে না। দ্বিতীয়, polymorphic পরিবার — sibling override বা interface implementation হয়তো এটা ব্যবহার করছে, এই method না করলেও। তৃতীয়, framework — DI container, serializer, আর route binder কখনো কখনো parameter-এর নাম বা position অনুযায়ী argument match করে। চতুর্থ, reflection আর dynamic call যেগুলো positional ভাবে argument পাঠায়। চারটা পরিষ্কার হলেই মুছে ফেলা নিরাপদ।
যদি কোনো interface বা base class আমাকে parameter রাখতে বাধ্য করে?
তাহলে এই implementation-কে সেটা রাখতেই হবে, ব্যবহার না হলেও। signature টা contract-এর সম্পত্তি, একটা implementation-এর না। তোমার কাছে দুটো সৎ পছন্দ আছে: রেখে দাও (অনেক ভাষায় _ নাম দিয়ে 'ইচ্ছাকৃতভাবে উপেক্ষিত' বোঝানো যায়), অথবা — যদি কোনো implementation আর এটা ব্যবহার না করে — contract থেকেই সরিয়ে দাও আর পুরো পরিবারকে একসাথে আপডেট করো।
Remove Parameter কি শুধু Add Parameter-এর undo বাটন?
ধারণাগতভাবে হ্যাঁ — এরা একই দাড়িপাল্লার দুই পাশ। Add Parameter method-এর সত্যিকারের দরকারী context নিয়ে আসে; Remove Parameter সেই context পরিষ্কার করে যেটা মরে গেছে। Fowler-এর ২য় সংস্করণ দুটোকেই, আর Rename Method-সহ, একটা refactoring-এ মিলিয়ে দিয়েছেন যার নাম Change Function Declaration, কারণ সবগুলোই signature edit যাদের একই নিরাপদ mechanics আছে।
অব্যবহৃত parameter গুলো প্রথমে কীভাবে আসে?
দুটো প্রধান উপায়ে। Refactoring-এর বাকি পড়া: কেউ body সরল করেছিল আর parameter পড়ার code উধাও হয়ে গেছে, কিন্তু signature পরিষ্কার হয়নি কারণ সব caller ঠিক করতে হত আর কেউ তাড়ায় ছিল। আর speculative generality: 'একদিন দরকার হতে পারে' ভেবে যোগ করা parameter — যেদিন কখনো আসেনি। Linter দুটোই ধরে: TypeScript-এর noUnusedParameters আর .NET analyzer স্বয়ংক্রিয়ভাবে অব্যবহৃত parameter flag করে।

আরো দেখো

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

Add Parameter: অর্ডার স্লিপে একটা নতুন কলাম

Add Parameter সহজ ভাষায় — কীভাবে method-কে নতুন একটা তথ্য দিতে হয় যেটা সে এখন চাইছে, কেন explicit parameter গ্লোবাল state লুকিয়ে রাখার চেয়ে ভালো, overload দিয়ে নিরাপদে কীভাবে করবে, আর কখন থামতে হবে যাতে parameter list বেশি বড় না হয়।

আরও পড়ুন

Rename Method: দোকানের সাইনবোর্ড যেন সত্যি কথা বলে

Rename Method সহজভাবে বোঝানো — কেন method-এর নাম সত্যিটা বলতে হবে, কীভাবে নিরাপদে নাম বদলাতে হয় delegate পদ্ধতিতে, আর কীভাবে VS Code (F2) ও JetBrains (Shift+F6) দিয়ে এটা এক কীতেই সারা যায়।

আরও পড়ুন

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 বাড়ে কিনা সেটার সৎ আলোচনা।

আরও পড়ুন