Add Parameter: অর্ডার স্লিপে একটা নতুন কলাম
Add Parameter সহজ ভাষায় — কীভাবে method-কে নতুন একটা তথ্য দিতে হয় যেটা সে এখন চাইছে, কেন explicit parameter গ্লোবাল state লুকিয়ে রাখার চেয়ে ভালো, overload দিয়ে নিরাপদে কীভাবে করবে, আর কখন থামতে হবে যাতে parameter list বেশি বড় না হয়।
অর্ডার স্লিপে একটা নতুন কলাম
ধরো তোমাদের পাড়ায় ফাতেমা আপা টিফিন সার্ভিস চালান। প্রতিদিন চল্লিশটা ডাব্বা, একটা ব্যস্ত রান্নাঘরে রান্না, সাইকেলে ডেলিভারি। প্রতি সকালে তার সাহায্যকারী রাহিম যায় বাড়ি বাড়ি — হাতে ছোট একটা অর্ডার স্লিপের প্যাড। প্রতিটা স্লিপে চারটা কলাম: নাম, রুটির সংখ্যা, আজকের সবজি, ডেলিভারি ঠিকানা। রাহিম স্লিপ ভরে, রান্নাঘর স্লিপ দেখে রান্না করে, সাইকেলে পৌঁছে দেওয়া হয়। দুই বছর ধরে সিস্টেমটা দুর্দান্তভাবে চলছিল।
তারপর গত মাসে কাছের college হোস্টেল থেকে নতুন customer এলো। প্রথম দিনেই সুমাইয়া আর তার রুমমেট অর্ধেক খেয়ে টিফিন ফেরত দিল। অভিযোগ? "আপা, বেশি ঝাল! আমরা পাহাড়ের মানুষ, এতটা পারি না।" অথচ সেই সন্ধ্যাতেই বি-ব্লকের জামাল চাচা ফোন করলেন একই ডালের ব্যাপারে — "একদম পানসা হয়ে গেছে, হাসপাতালের খাবারের মতো!"
ফাতেমা আপা রান্নাঘরে দাঁড়িয়ে সত্যিই বুঝতে পারছিলেন না। একই ডাল নিয়ে দুটো উল্টো অভিযোগ! তারপর বুঝলেন আসল সমস্যাটা। রান্নায় না। তার রান্নাঘর সবার জন্য একটাই spice level বানাচ্ছিল, কারণ অর্ডার স্লিপে customer-এর পছন্দ লেখার কোনো জায়গাই ছিল না। রান্নাঘর মনের কথা পড়তে পারে না। সে শুধু স্লিপে যা লেখা আছে তাই দেখে রান্না করে। সুমাইয়া জানত সে কী চায়। জামাল চাচা জানতেন তিনি কী চান। রাহিম এমনকি তাদের মুখে শুনেছিলও! কিন্তু স্লিপে কোনো কলাম ছিল না, তাই তথ্যটা দরজায়ই মরে গেল।
সমাধান ছিল একটা ছোট পরিবর্তন। ফাতেমা আপা স্লিপ প্যাড হাতে নিয়ে একটা নতুন কলাম যোগ করলেন: "ঝাল / কম ঝাল"। এখন রাহিম প্রতিটা customer-কে একটা বাড়তি প্রশ্ন করে, উত্তর লিখে রাখে, আর রান্নাঘর স্লিপ পড়ে সেই মতো রান্না করে। একই রান্নাঘর, একই রেসিপি, একই রাঁধুনি — কিন্তু এখন স্লিপ সেই একটু বাড়তি তথ্য বহন করে। এক সপ্তাহের মধ্যে অভিযোগ বন্ধ। সুমাইয়া পুরো হোস্টেল ফ্লোরে টিফিনের সুপারিশ করতে শুরু করল।
কোডের একটা method হলো সেই রান্নাঘর। তার parameter list হলো অর্ডার স্লিপ। method যখন কাজ করার জন্য একটা তথ্য দরকার পড়ে — আর সেই তথ্য caller-এর কাছেই আছে — তখন সৎ সমাধান হলো স্লিপে একটা কলাম যোগ করা: একটা নতুন parameter। এটাই Add Parameter নামের পুরো refactoring।
Add Parameter কী?
Add Parameter হলো Martin Fowler-এর Refactoring বই থেকে একটা refactoring:
একটা method-কে নতুন parameter দাও যাতে সে আগে যে তথ্য পেত না সেটা এখন পেতে পারে।
পরিস্থিতি সবসময় একই রকম। একটা method-কে কোনো data-র উপর ভিত্তি করে আচরণ বদলাতে হবে — কিন্তু data-টা method-এর ভেতরে না, caller-এর কাছে। হয়তো method এখন একটা মান hard-code করে রেখেছে (প্রতিটা customer-এর জন্য "en-US"), অথবা global variable থেকে চুপচাপ নিয়ে আসছে, অথবা নতুন কাজটা একদমই করতে পারছে না। সমাধান একটাই — সততার সাথে দরজা খুলে দাও। একটা parameter যোগ করো, আর প্রতিটা caller-কে তার নিজের context থেকে মান পাঠাতে দাও।
শুনতে খুব সহজ মনে হচ্ছে, কিন্তু এই নিয়মটা গুরুত্বপূর্ণ। কারণ method-কে বাড়তি data দেওয়ার বেশ কয়েকটা লোভনীয় ভুল পথ আছে:
- Global বা static variable পড়া। লুকানো dependency। Signature মিথ্যা বলে — দাবি করে method-এর শুধু তার তালিকার parameter দরকার, কিন্তু চুপচাপ world state-ও পড়ছে। Test কষ্টকর হয়, আর দুটো caller একই global নিয়ে ঝামেলা করতে পারে। টিফিনের ভাষায়: রান্নাঘর স্লিপ পড়ার বদলে গুজব শুনে ঝাল অনুমান করছে।
- মান hard-code করা। দ্বিতীয় use case আসার আগ পর্যন্ত কাজ করে — যেমন ফাতেমা আপার এক-ঝালে-সবার-চলে রান্নাঘর।
- Call করার আগে মানটা একটা field-এ ঢুকিয়ে দেওয়া। এটা "temporal coupling" তৈরি করে। method তখনই কাজ করে যদি তুমি মনে রেখে আগে সঠিক ক্রমে field সেট করেছ। এটা একটা টাইম বোমা।
Parameter হলো সৎ পথ। প্রয়োজনটা signature-এ দৃশ্যমান হয়, compiler নিশ্চিত করে প্রতিটা caller মান সরবরাহ করছে, আর একটা unit test পুরো দুনিয়া সেট না করেই সরাসরি যেকোনো মান পাঠাতে পারে।
নামকরণের একটা নোট: Refactoring-এর ২য় সংস্করণে Fowler Add Parameter (এবং Remove Parameter ও Rename Method) মিলিয়ে একটা umbrella refactoring-এ ঢুকিয়েছেন, নাম Change Function Declaration — catalog স্পষ্টভাবে "Add Parameter"-কে তার একটা alias হিসেবে উল্লেখ করেছে। ব্যাপারটা হলো: method-এর নাম আর তার parameter list একটাই "public face", আর সেই face-এর যেকোনো অংশ বদলাতে একই সাবধানী পদ্ধতি লাগে। তাই refactoring.com-এ Add Parameter পাবে Change Function Declaration-এর ভেতরে। পুরনো সূত্রগুলো এবং refactoring.guru এটাকে নিজের নামে শেখায়। দুটো নামই একই কাজ বোঝায়।
একটা গুরুত্বপূর্ণ বিষয়: Add Parameter আর Remove Parameter হলো ঠিক বিপরীত — একটা দোলনার দুই দিক। Method-এর সত্যিই বেশি context দরকার হলে যোগ করো। Parameter বোঝা হয়ে গেলে সরাও। সুস্থ code সারাজীবন দুদিকেই যায়: এই মাসে স্লিপে ঝালের কলাম যোগ হলো, আগামী বছর হোম ডেলিভারি বন্ধ হলে ঠিকানার কলাম উঠে যাবে।
পুরো বিষয়টা এক পাতায়:
কলেজ কর্নার: Add Parameter হলো backward compatibility-এর অধীনে API evolution-এর প্রাত্যহিক চেহারা। একটা published function যখন নতুন required input পায়, প্রতিটা বিদ্যমান consumer technically ভেঙে যায় — যদি না publisher একটা compatibility bridge দেয়: একটা overload, একটা default value, বা একটা versioned endpoint। এজন্যই mature HTTP API নতুন field যোগ করে optional হিসেবে — পুরনো client কাজ করতে থাকে, নতুন client বেশি পাঠায়। Protocol Buffers-এর মতো format ডিজাইন করা হয়েছে যাতে নতুন field যোগ করলেও পুরনো reader ভাঙে না। নিচে শিখবে default-value trick। এটা একই ধারণা ছোট পরিসরে: নতুন সুবিধা আসে, কারো বিদ্যমান code বুঝতেই পারে না, যতক্ষণ না নিজে থেকে opt in করে।
কখন দরকার হয়?
এই পরিস্থিতিগুলো দেখলে Add Parameter-এর কথা ভাবো:
- Hard-code করা মান নমনীয় করতে হবে।
sendReceipt(order)সবসময় ইংরেজিতে render করে। এখন ফরাসি customer এসেছে। locale-টা বাইরে থেকে আসতে হবে — যে customer চেনে তার কাছ থেকে। - Method চুপচাপ global পড়ছে।
calculateTax()চুপচাপglobalRegionপড়ে। লুকানো dependency-কে স্পষ্ট করো:calculateTax(region)। এখন signature সত্যি কথা বলে আর test এক লাইনে হয়। - নতুন আচরণ caller-এর জানা data-র উপর নির্ভর করে। একটা method extend করছ, আর নতুন logic এমন কিছুর উপর branch করছে যা শুধু caller জানে — ঝালের পছন্দ, user-এর role, চাওয়া currency।
- Testing বিরক্তিকর। Static state বা environment variable না বদলিয়ে method test করতে পারছ না? সাধারণত একটা missing parameter-ই কারণ। মানটা pass করলে method আরো pure হয়, test সহজ হয়।
- দুটো প্রায় একই method শুধু একটা মানের জন্য আলাদা।
applyTenPercentDiscount()আরapplyFifteenPercentDiscount()একটাapplyDiscount(rate)-এ মিলে যেতে পারে। যখন duplication-ই মূল বিষয়, Fowler এটাকে Parameterize Method বলেন — Add Parameter হলো তার ভেতরের হাতিয়ার।
আর বিপরীত সংকেত — যে মুহূর্তগুলোতে parameter যোগ করা ভুল কাজ:
- Method নিজেই মানটা হিসাব করতে পারে সে ইতোমধ্যে পাওয়া data থেকে। তখন Replace Parameter with Method Call ব্যবহার করো। Caller-কে এমন কাজ করাতে যেও না যা method নিজেই করতে পারে।
- Boolean flag যোগ করছ যেমন
send(order, true)যেটা আচরণ পাল্টায়। Flag argument একটা smell — reader বুঝতে পারে নাtrueমানে কী। বরং দুটো নাম দেওয়া method-এ ভাগ করো যেমনsendNowআরqueueForLater। - এটা হবে পঞ্চম parameter। থামো। বারবার যোগ করলে Long Parameter List smell তৈরি হয়। সম্পর্কিতগুলো Introduce Parameter Object দিয়ে bundle করো, বা Preserve Whole Object দিয়ে পুরো object পাঠাও।
ফাতেমা আপাকে স্লিপ বদলাতে বাধ্য করা সপ্তাহের অভিযোগগুলো দেখো। Missing কলামটা শুধু কিছু customer-কে বিরক্ত করছিল না — বেশিরভাগকেই একসাথে দুদিক থেকে ভুল করছিল:
কোডে সবচেয়ে ভয়ের অংশ হলো সেই "happy by accident" টুকরো। যেসব caller-এর জন্য hard-code করা মান আজকে ঘটনাক্রমে ঠিক আছে — আগামীকাল তাদের কিছুই রক্ষা করছে না।
আর এই হলো প্রতিটা developer-এর সামনে আসা সিদ্ধান্ত যখন signature বাড়াতে হয় — একটা parameter যোগ করবে, নাকি পিছিয়ে bundle করবে?
ঝালের স্তর আরামে নিচের-বামে বসে: একটা মান, স্থিতিশীল প্রয়োজন, simple parameter। পাঁচটা boundary number দিয়ে একটা grading scheme যা campus-campus বছর-বছর বদলায়, সেটা উপরে-ডানে — bundle করো।
আগে ও পরে এক নজরে
TypeScript-এ ঝালবিহীন রান্নাঘর দেখো:
// BEFORE — the order slip has no spice column
type Order = { customer: string; rotis: number; sabzi: string };
function cookTiffin(order: Order): Tiffin {
// spice level is hard-coded — every customer gets "medium"
return kitchen.prepare(order.sabzi, order.rotis, "medium");
}
// Callers cannot express what they know:
cookTiffin(pgStudentOrder); // wanted mild — gets medium
cookTiffin(uncleOrder); // wanted spicy — gets mediumআর স্লিপে কলাম যোগ করার পরে:
// AFTER — the slip carries the new detail
type SpiceLevel = "mild" | "medium" | "spicy";
function cookTiffin(order: Order, spice: SpiceLevel): Tiffin {
return kitchen.prepare(order.sabzi, order.rotis, spice);
}
// Each caller passes what it knows:
cookTiffin(pgStudentOrder, "mild");
cookTiffin(uncleOrder, "spicy");রান্নাঘরের logic খুব একটা বদলায়নি। যা বদলেছে সেটা হলো তথ্যের প্রবাহ। Caller-এ আটকে থাকা জ্ঞান এখন সেই method-এ যেতে পারছে যার সেটা দরকার — খোলাখুলিভাবে, signature-এর সামনের দরজা দিয়ে।
কাঠামো হিসেবে, উন্নত অর্ডার স্লিপটা এরকম দেখতে:
ধাপে ধাপে, নিরাপদ পদ্ধতিতে
Signature বদলালে একসাথে সব caller ভাঙে — যদি না পর্যায়ক্রমে করো। এই সাবধানী ক্রমটা দেখো, একটা receipt-emailing service-এ দেখানো হচ্ছে যেটাকে ভাষা শিখতে হবে।
ধাপ ০ — শুরুর অবস্থা:
function sendReceipt(order: Order): void {
const body = renderReceipt(order, "en-US"); // hard-coded locale
email.send(order.customerEmail, body);
}ধাপ ১ — Type আর role বোঝানো নাম বেছে নাও। s: string না, locale: Locale। নামটা বলবে এই method-এর কাছে মানটা কী মানে, শুধু তার type না। রাহিমের কলামে "টেক্সট ফিল্ড" লেখা নেই, লেখা আছে "ঝাল / কম ঝাল"।
ধাপ ২ — Parameter-কে সাময়িক নিরাপত্তা জালসহ যোগ করো। তিনটা caller থাকা private method-এর জন্য, সরাসরি signature বদলাও আর compiler-কে guide করতে দাও। Public বা বহুল-ব্যবহৃত method-এর জন্য, migrate করার সময় পুরনো shape কাজ করতে রাখো। TypeScript-এ default value হলো সবচেয়ে হালকা জাল:
// INTERMEDIATE — both old and new callers work
function sendReceipt(order: Order, locale: Locale = "en-US"): void {
const body = renderReceipt(order, locale);
email.send(order.customerEmail, body);
}Overload সহ ভাষায় যেমন C# বা Java, সমতুল্য জাল হলো পুরনো signature-এর overload যেটা forward করে:
// old shape forwards to new shape with a sensible default
public void SendReceipt(Order order)
=> SendReceipt(order, CultureInfo.GetCultureInfo("en-US"));ধাপ ৩ — Body-তে parameter ব্যবহার করো। Hard-code করা মান বা global read-কে parameter দিয়ে replace করো। এই মুহূর্তেই method আসলে নতুন ক্ষমতা পায়।
ধাপ ৪ — Compile আর test করো। Default থাকলে, সব বিদ্যমান caller ঠিক আগের মতো আচরণ করে — তারা implicitly "en-US" পায়। আচরণ অপরিবর্তিত — এটাই নিরাপদ intermediate state-এর সংজ্ঞা।
ধাপ ৫ — একে একে caller-দের migrate করো। প্রতিটা call site দেখো আর তার নিজের context থেকে আসল মান পাঠাও:
// checkout flow knows the customer's language preference:
sendReceipt(order, customer.preferredLocale);
// admin resend tool defaults to the original order locale:
sendReceipt(order, order.locale);প্রতিটা migration-এর পরে test চালাও।
ধাপ ৬ — নিরাপত্তার জাল সরাও (যদি চাও)। সব caller যখন স্পষ্টভাবে মান পাঠায়, সিদ্ধান্ত নাও। Default রাখবে যদি "en-US" সত্যিই একটা sensible default হয়। অথবা মুছে দেবে যাতে compiler প্রতিটা ভবিষ্যৎ caller-কে locale নিয়ে ভাবতে বাধ্য করে। Overload-style জালের জন্য, forwarding overload-এর caller শূন্য হলে সেটা মুছো।
Bridge period-এ, migrate না হওয়া caller-এর request এভাবে flows করে — পুরনো shape-এ ঢোকে, default fill হয়, নতুন shape serve করে:
দূর থেকে দেখলে, codebase তিনটা অবস্থার মধ্য দিয়ে যায় — ঠিক gradual rename-এর তিনটা অবস্থার মতো, কারণ দুটোই মূলে Change Function Declaration:
কব্জিতে উল্কি আঁকার মতো দুটো সতর্কতা। প্রথমত: default-value নিরাপত্তার জাল ভুল লুকিয়ে রাখে। Caller যদি locale পাঠানো উচিত হয় কিন্তু ভুলে যায়, চুপচাপ চিরকাল ইংরেজি পাবে, আর কোনো compiler অভিযোগ করবে না। Parameter যদি সঠিকতার জন্য সত্যিই required হয়, শেষে default সরিয়ে দাও যাতে ভুলে যাওয়া compile error হয়। দ্বিতীয়ত: প্রতিটা ধাপের পরে test suite চালাও, বিশেষত ধাপ ৩-এর পরে। সেই ধাপটা আসল data flow বদলায়। ভুলক্রমে swap — order.locale যেখানে customer.preferredLocale মানে ছিল — এটা ঠিক সেই ধরনের bug যা type ধরে না কিন্তু test ধরে।
কলেজ কর্নার: সেই প্রথম সতর্কতার বিপদের একটা আনুষ্ঠানিক নাম আছে — silent default drift। C#-এ optional-parameter default বেক হয় caller-এর compiled code-এ, method-এর না। Library v2 যদি default "en-US" থেকে "auto"-তে বদলায়, v1-এর বিপরীতে compile করা প্রতিটা consumer recompile না করা পর্যন্ত "en-US" পাঠাতেই থাকবে। এজন্য public library author-রা optional parameter-এর বদলে explicit overload পছন্দ করেন: default এক জায়গায় থাকে, তাই পরে বদলালে সবার জন্য সামঞ্জস্যপূর্ণভাবে আচরণ বদলায়। Binary compatibility আর source compatibility আলাদা প্রতিশ্রুতি — এই পার্থক্য শুধু বড় scale-এ কামড়ায়, আর তখনই সবচেয়ে বেশি ব্যথা দেয়।
একটা বড় বাস্তব উদাহরণ
ধরো একটা school-এর report card generator তৈরি হয়েছিল যখন school-এর একটাই campus আর একটাই grading style ছিল। এখন দ্বিতীয় campus খুলেছে, সেটা আলাদা grade boundary table ব্যবহার করে। দেখো কীভাবে missing তথ্যটা সততার সাথে thread করা হচ্ছে।
// BEFORE — one campus baked into the logic
class ReportCardService {
generate(studentId: string): ReportCard {
const marks = this.marksRepo.findByStudent(studentId);
const grades = marks.map((m) => ({
subject: m.subject,
// grade boundaries HARD-CODED for the old campus
grade: m.score >= 91 ? "A1" : m.score >= 81 ? "A2" : m.score >= 71 ? "B1" : "B2",
}));
return new ReportCard(studentId, grades);
}
}নতুন campus চায় ৯০ থেকে A1, ৯১ না, আর একটা C band যোগ করে। অলস সমাধানগুলো সব ফাঁদ: একটা global currentCampus variable মানে hidden state, ভেতরে if (schoolName === ...) মানে method এখন দুটো campus hard-code করে, আর পুরো class copy-paste করা মানে চিরকাল duplication।
সৎ সমাধান: caller জানে student কোন campus-এর। অর্ডার স্লিপে রাখো।
// AFTER — the grading scheme arrives as a parameter
interface GradingScheme {
gradeFor(score: number): string;
}
class ReportCardService {
generate(studentId: string, scheme: GradingScheme): ReportCard {
const marks = this.marksRepo.findByStudent(studentId);
const grades = marks.map((m) => ({
subject: m.subject,
grade: scheme.gradeFor(m.score),
}));
return new ReportCard(studentId, grades);
}
}
// Each campus's code passes its own scheme:
const cityCampusScheme: GradingScheme = {
gradeFor: (s) => (s >= 91 ? "A1" : s >= 81 ? "A2" : s >= 71 ? "B1" : "B2"),
};
const newCampusScheme: GradingScheme = {
gradeFor: (s) => (s >= 90 ? "A1" : s >= 80 ? "A2" : s >= 65 ? "B1" : s >= 50 ? "B2" : "C"),
};
reportService.generate(studentId, cityCampusScheme);
reportService.generate(studentId, newCampusScheme);তিনটা সুবিধা লক্ষ্য করো:
- Service campus সম্পর্কে কিছুই জানে না। হাতে দেওয়া যেকোনো scheme সে apply করে। আগামীকাল তৃতীয় campus এলে এখানে কোনো পরিবর্তন নেই।
- Test তুচ্ছ হয়ে গেছে। একটা ছোট fake scheme পাঠাও —
{ gradeFor: () => "A1" }— আর report assembly logic আলাদাভাবে test করো। কোনো global নেই, কোনো setup নেই। - Parameter হলো একটা object, পাঁচটা number না। আমরা
boundary1, boundary2, boundary3, ...যোগ করতে পারতাম — পাঁচটা নতুন parameter Long Parameter List-এর দিকে এগোত। একটাGradingScheme-এ bundle করলে slip ছোট থাকে। এটাই Add Parameter আর Introduce Parameter Object একসাথে কাজ করছে — ঠিক Figure 4-এর উপরে-ডানের quadrant।
একই Refactoring C#-এ
C# আমাদের সবচেয়ে পরিষ্কার staged migration দেয়, overload আর [Obsolete]-এর কল্যাণে। এখানে একটা courier-charge calculator যেটাকে ডেলিভারি speed শিখতে হবে।
// BEFORE — every parcel priced as "standard" delivery
public class CourierService
{
public decimal CalculateCharge(Parcel parcel)
{
var baseCharge = parcel.WeightKg * 40m;
return baseCharge; // speed? what speed? everyone waits 5 days
}
}ধাপ ১ — রিচার signature যোগ করো, আর পুরনোটা forwarding overload হিসেবে রাখো:
public enum DeliverySpeed { Standard, Express, SameDay }
public class CourierService
{
// NEW — the real method
public decimal CalculateCharge(Parcel parcel, DeliverySpeed speed)
{
var baseCharge = parcel.WeightKg * 40m;
return speed switch
{
DeliverySpeed.Standard => baseCharge,
DeliverySpeed.Express => baseCharge * 1.5m,
DeliverySpeed.SameDay => baseCharge * 2.5m,
_ => baseCharge
};
}
// OLD — forwards with the historical default; warns callers to migrate
[Obsolete("Pass a DeliverySpeed explicitly. Will be removed in v4.")]
public decimal CalculateCharge(Parcel parcel)
=> CalculateCharge(parcel, DeliverySpeed.Standard);
}সব বিদ্যমান caller compile হয় আর আগের মতোই আচরণ করে — standard delivery, same price। নতুন code দুই-argument form call করে। [Obsolete] warning পুরনো caller-দের এগিয়ে আসতে উৎসাহিত করে। Build log-এ শূন্য warning দেখলে পুরনো overload মুছে ফেলো।
// AFTER — final state, single honest signature
public class CourierService
{
public decimal CalculateCharge(Parcel parcel, DeliverySpeed speed)
{
var baseCharge = parcel.WeightKg * 40m;
return speed switch
{
DeliverySpeed.Standard => baseCharge,
DeliverySpeed.Express => baseCharge * 1.5m,
DeliverySpeed.SameDay => baseCharge * 2.5m,
_ => baseCharge
};
}
}C#-নির্দিষ্ট সতর্কতা:
- Optional parameter বনাম overload:
DeliverySpeed speed = DeliverySpeed.Standardoverload-এর চেয়ে সুন্দর দেখায়, কিন্তু default compile time-এ caller-এর মধ্যে বেক হয় — পরে default বদলালে ইতোমধ্যে-compile-হওয়া assembly-গুলো প্রভাবিত হয় না। Public library-র জন্য overload নিরাপদ। - Interface: যদি
CalculateChargeinterface-এ থাকে, interface আর সব implementation একসাথে বদলাতে হবে। Forwarding-overload trick তবুও কাজ করে: interface-এ নতুন member যোগ করো, পুরনো implementation-কে default behaviour দাও, migrate করো, তারপর সরাও। - Enum bool-এর চেয়ে ভালো: লক্ষ্য করো আমরা
DeliverySpeedযোগ করেছি,bool isExpressনা।CalculateCharge(parcel, true)প্রতিটা call site-এ অপাঠযোগ্য হত।CalculateCharge(parcel, DeliverySpeed.Express)যে অর্ডার স্লিপ সেটার মতোই পড়া যায়।
দ্রুত Python স্বাদ
Python-এর keyword argument staged addition-কে বিশেষভাবে graceful করে — আর keyword-only parameter (*-এর পরে সবকিছু) classic positional-argument mix-up আটকায়:
from enum import Enum
class Spice(Enum):
MILD = "mild"
MEDIUM = "medium"
SPICY = "spicy"
def cook_tiffin(order, *, spice: Spice = Spice.MEDIUM):
"""Old callers keep working; new callers must name the argument."""
return kitchen.prepare(order.sabzi, order.rotis, spice.value)
# old caller — unchanged, gets the historical default:
cook_tiffin(priya_order)
# new caller — must write the name, so the call documents itself:
cook_tiffin(priya_order, spice=Spice.MILD)
cook_tiffin(verma_order, spice=Spice.SPICY)Call site-এ spice=Spice.MILD হলো পাঠযোগ্য অর্ডার স্লিপ। তৃতীয় argument হিসেবে খালি কিছু মানে কী সেটা আর কাউকে অনুমান করতে হয় না।
স্লিপে পরিবর্তন কি কাজে লেগেছে?
ফাতেমা আপা তার অভিযোগের নোটবুক রেখেছিলেন, তাই আমরা plot করতে পারি। নতুন কলামের আগের সপ্তাহে অভিযোগ শীর্ষে ছিল। যে সপ্তাহে কলাম এলো, সেটা একদম কমে গেল — আর যেকটা বাকি আছে সেগুলো আসল রান্নার সমস্যা, তথ্যের সমস্যা না:
Software team-এ hard-code করা assumption থাকা যেকোনো method-এর আশেপাশে "wrong-output bug report" হিসেবে এই একই plot দেখা যায়। সমাধান খুব কমই আরো চালাক logic — সাধারণত এটা স্লিপে আরো একটা সৎ কলাম।
IDE সাপোর্ট
প্রতিটা caller হাতে edit করতে হয় না তোমাকে। "signature বদলানো" অপারেশন major IDE-গুলোতে সম্পূর্ণ automated:
| Tool | Shortcut | কী করে |
|---|---|---|
| JetBrains IDEs (IntelliJ IDEA, Rider, WebStorm, PyCharm) | Ctrl+F6 (Cmd+F6 macOS-এ) — Change Signature | Parameter যোগ করা, সরানো, সাজানো আর rename করার একটা dialog। নতুন parameter-এর জন্য default value দাও, IDE প্রতিটা call site rewrite করে সেটা pass করতে। |
| Visual Studio | Ctrl+R, Ctrl+V — Change Signature বা Quick Actions bulb | পুরো solution জুড়ে preview সহ parameter যোগ/সরানো/সাজানো। |
| VS Code | একটা built-in "add parameter" command নেই — কিন্তু F2 rename করে, আর language extension (C#, Java, TypeScript) "Change Signature" code action অফার করে। নাহলে declaration বদলাও আর compiler error-গুলো প্রতিটা caller-কে তোমার to-do list হিসেবে তালিকাভুক্ত করতে দাও। |
সেই শেষ technique-এর একটা নাম আছে: compiler-driven refactoring। Statically-typed ভাষায়, শুধু parameter যোগ করো আর build করো। প্রতিটা লাল error হলো একটা caller যাকে নতুন মান দিতে হবে — বিনামূল্যে তৈরি একটা সুনির্দিষ্ট, সম্পূর্ণ checklist। সেটা কাজ করো, test চালাও, শেষ। এটা typed ভাষার একটা নিরব সুপারপাওয়ার। Python বা plain JavaScript-এ search আর test-এর উপর নির্ভর করতে হয়।
সুবিধা ও ঝুঁকি
| সুবিধা | ঝুঁকি / খরচ |
|---|---|
| Dependency explicit হয় — signature সততার সাথে তালিকা করে method-এর কী কী দরকার। | প্রতিটা addition signature লম্বা করে। বারবার addition Long Parameter List জন্মায়। ৪+ হলে Introduce Parameter Object দিয়ে bundle করো। |
| Method test করা সহজ — সরাসরি মান pass করো, কোনো global setup না। | Public signature বদলালে বাইরের caller ভাঙে। overload/default migration dance দরকার। |
| একটা method অনেক case serve করে, প্রতি case-এ duplicate না। | অসাবধান default value চুপচাপ সেই caller-গুলো লুকিয়ে রাখতে পারে যাদের উচিত ছিল real value pass করা। |
| Global/static-এর hidden read সরায় — কম "spooky" আচরণ পরিবর্তন। | Boolean flag parameter যোগ করা এক smell-এর বদলে আরেকটা আনে। দুটো named method বা enum পছন্দ করো। |
| Remove Parameter-এর সরাসরি inverse — একসাথে signature-কে দুদিকেই বাস্তবতার সাথে মেলায়। | Method নিজেই মান derive করতে পারলে নতুন parameter শুধু noise। Replace Parameter with Method Call ব্যবহার করো। |
কোন Smell ঠিক করে?
| Smell | Add Parameter কীভাবে সাহায্য করে |
|---|---|
| Hidden / global dependency (Insider Trading আর global-state coupling-এর একটা flavor) | Global-এর গোপন read দৃশ্যমান, testable parameter হয়। |
| Duplicated Code | শুধু একটা embedded মানের জন্য আলাদা দুটো method একটা parameterized method-এ মিলে যায়। |
| Divergent Change (hard-code করা মান প্রতিটা নতুন case-এর জন্য edit বাধ্য করে) | নতুন case-গুলো method edit করে না, আলাদা argument পাঠিয়ে handle হয়। |
| Shotgun Surgery (উল্টোভাবে — সাবধান!) | Public API-তে অসাবধানে করলে, parameter যোগ করা সবখানে edit ঘটায়। overload/default জাল এটা আটকায়। |
দ্রুত রিভিশন বক্স
+----------------------------------------------------------------+
| ADD PARAMETER — CHEAT SHEET |
+----------------------------------------------------------------+
| Story : Tiffin slip gets a new column: "Spicy / Mild" |
| Goal : Method receives caller-known info through signature |
| 2nd ed. : Part of "Change Function Declaration" (Fowler) |
| Inverse : Remove Parameter (the same seesaw, other direction) |
| |
| SAFE STEPS: |
| 1. Pick type + role-stating name (locale, not s) |
| 2. Add param with default / forwarding overload |
| 3. Use it in the body (replace hard-code / global) |
| 4. Test — old callers must behave identically |
| 5. Migrate callers one by one, testing each |
| 6. Remove the default/overload if value is required |
| |
| DON'T when: method can compute it itself | it's a bool flag | |
| it would be the 4th-5th param (bundle instead) |
| IDE : JetBrains Ctrl+F6 | VS Ctrl+R,Ctrl+V | compiler-led |
+----------------------------------------------------------------+অনুশীলন
ধরো একটা পাড়ার ফার্মেসি app-এ এই billing function আছে:
class PharmacyBilling {
// Today: every customer pays full price, bill prints in English,
// and home delivery is assumed to be FREE for everyone.
createBill(items: MedicineItem[]): Bill {
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
const labelText = "TOTAL AMOUNT"; // English only
const deliveryCharge = 0; // free for all — owner is losing money!
return new Bill(items, total + deliveryCharge, labelText);
}
}মালিক এখন চান: (ক) বয়স্ক নাগরিকরা ১০% ছাড় পাবেন, (খ) customer-এর পছন্দ অনুযায়ী বাংলায় বা ইংরেজিতে বিল, (গ) ৩ কিমির বেশি হলে ৳৩০ ডেলিভারি চার্জ, ভেতরে বিনামূল্যে।
কাজগুলো:
createBill-এর কোন নতুন parameter দরকার তা ঠিক করো। প্রতিটার জন্য type আর role বোঝানো নাম লেখো। একটু ভাবো:isSenior: booleanকি সবচেয়ে ভালো shape, নাকিdiscountRate: number— বা একটা ছোটCustomerProfileobject — app বাড়লে ভালো serve করবে? সিদ্ধান্ত নেওয়ার আগে প্রতিটা candidate Figure 4-এর quadrant-এ plot করো।- Refactoring পর্যায়ক্রমে করো। intermediate version দেখাও যেখানে নতুন parameter-এ default আছে যাতে বিদ্যমান caller কাজ করতে থাকে, তারপর সব caller migrate করার পরে final version। প্রতিটা version Figure 8-এর কোন state তা চিহ্নিত করো।
- এক teammate বলছে: "Function-এর ভেতরে
window.currentCustomerপড়ো — কোনো signature পরিবর্তন দরকার নেই!" তাদের দুটো বাক্যে বোঝাও কেন parameter নিরাপদ, hidden dependency আর testability শব্দ ব্যবহার করে। - এখন তুমি তিনটা parameter যোগ করেছ। মালিক আরো দুটো upcoming feature উল্লেখ করছেন — loyalty points আর GST category। কোন জায়গায় যোগ করা বন্ধ করে Introduce Parameter Object-এর দিকে হাত বাড়াবে? সেই bundle করা object কেমন দেখাবে sketch করো।
- Senior-citizen ছাড়ের জন্য একটা unit test লেখো যেটা global-variable পদ্ধতিতে পরিষ্কারভাবে লেখা সম্ভব ছিল না।
তোমার final createBill signature যদি ফাতেমা আপার উন্নত অর্ডার স্লিপের মতো পড়া যায় — প্রতিটা কলাম অর্থবহ, রান্নাঘরে কিছু লুকানো নেই, আর রাহিমকে কখনো অনুমান করতে না হয় — তাহলে তুমি Add Parameter সম্পূর্ণ বুঝে গেছ।
সচরাচর জিজ্ঞাসা
- method-এর ভেতরে global variable পড়ার বদলে parameter যোগ করব কেন?
- কারণ parameter দেখা যায়, global লুকানো থাকে। parameter থাকলে method-এর signature সৎভাবে জানায় তার কী কী দরকার, caller নিয়ন্ত্রণে থাকে, আর test-এ সরাসরি যেকোনো মান পাঠানো যায়। global দিয়ে করলে call site থেকে dependency দেখা যায় না, অন্য কেউ global বদলালে method চুপচাপ আচরণ পাল্টায়, আর test-এর আগে পুরো world state সেট করতে হয়। Explicit সবসময় hidden-এর চেয়ে ভালো।
- parameter যোগ করলে কি আমার সব caller ভেঙে যাবে?
- সরাসরি signature বদলালে হ্যাঁ — private method-এর জন্য এটা ঠিক আছে, কারণ compiler সাথে সাথে প্রতিটা caller দেখিয়ে দেবে। Public বা বহুল-ব্যবহৃত method-এর জন্য নিরাপদ পথে যাও: একটা overload বা default value যোগ করো, পুরনো signature থেকে একটা ভালো default নিয়ে নতুন signature-এ forward করো, ধীরে ধীরে caller-দের migrate করো, তারপর পুরনোটা সরাও।
- কখন parameter যোগ করা উচিত হবে না?
- তিনটা সাধারণ ক্ষেত্র। প্রথমত, method যদি ইতোমধ্যে পাওয়া data থেকে মানটা নিজেই হিসাব করতে পারে, তাহলে Replace Parameter with Method Call ব্যবহার করো। দ্বিতীয়ত, boolean flag যোগ করতে চাইলে যেটা method-কে দুটো আলাদা কাজ করাবে, দুটো আলাদা নাম দেওয়া method-এ ভাগ করো। তৃতীয়ত, এটা যদি চতুর্থ বা পঞ্চম parameter হয়, থামো আর সম্পর্কিতগুলো Introduce Parameter Object দিয়ে একত্রিত করো।
- Add Parameter কি আসলেই Remove Parameter-এর উল্টো?
- হ্যাঁ, ঠিক তাই। Add Parameter method-কে নতুন context দেয় যা সত্যিই দরকার। Remove Parameter সেই context মুছে দেয় যেটা বোঝা হয়ে গেছে। Fowler-এর ২য় সংস্করণে দুটোকেই Rename Method-সহ একটা refactoring-এ মিলিয়ে দেওয়া হয়েছে, নাম Change Function Declaration — কারণ তিনটোই method-এর public signature-এ একই নিরাপদ পদ্ধতিতে করা পরিবর্তন।
- নতুন parameter কি primitive হবে না object?
- যেটা role সবচেয়ে পরিষ্কারভাবে বলতে পারে সেটাই বেছে নাও। ভালো নাম দেওয়া primitive যেমন spiceLevel: SpiceLevel একটা মানের জন্য ঠিক আছে। কিন্তু দুই-তিনটা মান যদি সবসময় একসাথে চলে — রাস্তা, শহর, পিনকোড — সেগুলো data clump, একটা Address object পাঠানো উচিত। Fowler আরো পরামর্শ দেন ভবিষ্যতের কথা ভাবতে: একটু বড় object পাঠালে পরে বারবার signature বদলানো থেকে বাঁচা যায়।
আরো দেখো
সম্পর্কিত পাঠ
Remove Parameter: স্কুলের ফর্ম থেকে 'টেলিগ্রাম ঠিকানা' ঘরটা বাদ দাও
Remove Parameter সহজ ভাষায় — method আর যে parameter ব্যবহার করে না সেটা নিরাপদে কীভাবে মুছবে, মরা parameter গুলো পাঠককে কীভাবে বিভ্রান্ত করে আর প্রতিটা caller-কে বাড়তি কাজ করায়, আর মুছে ফেলার আগে কী কী চেক করতে হবে (interface, override, reflection)।
Rename Method: দোকানের সাইনবোর্ড যেন সত্যি কথা বলে
Rename Method সহজভাবে বোঝানো — কেন method-এর নাম সত্যিটা বলতে হবে, কীভাবে নিরাপদে নাম বদলাতে হয় delegate পদ্ধতিতে, আর কীভাবে VS Code (F2) ও JetBrains (Shift+F6) দিয়ে এটা এক কীতেই সারা যায়।
Introduce Parameter Object: পাঁচটা আলাদা উত্তর না দিয়ে একটা ঠিকানা কার্ড দাও
Introduce Parameter Object সহজ ভাষায় — একই parameter গ্রুপ বারবার বিভিন্ন method-এ ঘুরে বেড়ানো আসলে একটা লুকানো concept-এর চিহ্ন। সেগুলো একটা named object-এ bundle করলে signature ছোট হয়, order ভুল বন্ধ হয়, আর behaviour এসে জুটে যায়।
Preserve Whole Object: পুরো ID Card দেখাও
Preserve Whole Object refactoring শেখো একটা school ID card-এর গল্প দিয়ে — TypeScript আর C# example সহ, safe step-by-step mechanics, আর object pass করলে coupling বাড়ে কিনা সেটার সৎ আলোচনা।