Long Parameter List: দশটা নির্দেশনার চায়ের অর্ডার
Long Parameter List কোড স্মেল সহজ ভাষায় — কেন বেশি argument-এর method বাগ তৈরি করে, আর কীভাবে parameter object দিয়ে call ছোট, পরিষ্কার আর নিরাপদ করা যায়।
"ভাই, আমার সাধারণটা দাও!" — দুটো চায়ের অর্ডারের গল্প
ধরো ঢাকার মিরপুরে একটা software কোম্পানির অফিস আছে। অফিসের নিচে সালাম ভাইয়ের চায়ের দোকান — পনেরো বছর ধরে চা বানাচ্ছেন একই কোণে।
প্রতিদিন সন্ধ্যায় নতুন intern রুবেল এভাবে অর্ডার দেয়:
"ভাই, এক চা। কম চিনি। বেশি আদা। এলাচ নাই। ফুল মিল্ক। ছোট গ্লাস। অনেক গরম। কড়া পাতা। উপরে সর নাই। আর কাগজের কাপে নিয়ে যাব।"
দশটা নির্দেশনা। প্রতিদিন। একদিন একটা ভুলে গেল, চা ভুল এলো। একবার মুখে উল্টে গেল — "বেশি চিনি, কম আদা" বলে ফেলল — আর একটা মিষ্টি বিপর্যয় খেয়ে বাড়ি গেল।
এবার দেখো তারিক ভাইকে — সিনিয়র developer, রুবেলের mentor। সে এসে মাত্র দুটো কথা বলে: "ভাই, সাধারণটা!"
সালাম ভাই মাথা নাড়েন। তারিক ভাইয়ের পছন্দ মুখস্থ — সংরক্ষিত, নামকরণ করা, বিশ্বস্ত। একটা শব্দে দশটা নির্দেশনা বহন করে, নিখুঁতভাবে, প্রতিবার। আর গত মাসে তারিক ভাই চিনি থেকে গুড়ে বদলে যাওয়ার সিদ্ধান্ত নিলে, সালাম ভাইকে একবারই বললেন। তারপর থেকে প্রতিটা "সাধারণটা" আপনাআপনি আপডেট।
গল্পটা দোকানেই শেষ না। সালাম ভাইয়ের ছেলে দোকানটা computer-এ আনতে চাইছে, আর ordering app লিখতে স্বেচ্ছায় এগিয়ে গেছে কে? রুবেল। আর রুবেল main function কীভাবে লিখেছে? ঠিক যেভাবে সে অর্ডার দেয়: দশটা আলাদা parameter, নির্দিষ্ট ক্রমে, প্রতিটা call-এ। পরদিন সকালে তারিক ভাই code review করতে এসে জোরে হাসে। "রুবেল, তুই তোর চায়ের অর্ডারটাই codebase-এ লিখে দিয়েছিস। এই smell-এর একটা নাম আছে — Long Parameter List।"
কোডে, Long Parameter List হলো রুবেলের অর্ডার: এমন একটা function যেটা দশটা আলাদা value চায়, সঠিক ক্রমে, যতবারই ডাকা হোক। আর সমাধান হলো তারিক ভাইয়ের কৌশল: নির্দেশনাগুলো একটা নামকরণ করা জিনিসে বেঁধে ফেলো, আর সেটাই পাঠাও।
এই smell টা কী?
আমাদের সোনালি নিয়মটা একটু মনে করিয়ে দিই: code smell কোনো bug না। বারোটা parameter-এর function দিয়ে লেখা code ঠিকঠাক চলতে পারে। Smell হলো একটা সতর্কবার্তা — এই code call করা, পড়া, বদলানো আর test করা দিনদিন আরও কষ্টের হবে। Long Parameter List হলো Martin Fowler-এর Refactoring বইয়ের "Bloater" smell গুলোর একটা — এখানে method-এর signature-ই ফুলে গেছে।
Long Parameter List হলো এমন একটা method signature যেখানে এত বেশি parameter যে:
- caller-রা সেগুলো সঠিক ক্রমে দিতে হিমশিম খায়,
- declaration না খুলে পাঠকেরা বুঝতে পারে না একটা call কী বোঝায়, আর
- প্রতিটা নতুন data অনেক signature আর call site-এ পরিবর্তন করতে বাধ্য করে।
"এত বেশি" মানে কতটুকু? সাধারণ গাইড বলে চারটা বা তার বেশি হলেই সতর্কতার ঘণ্টা বাজে। Robert C. Martin Clean Code-এ আরও কঠোর: argument-এর আদর্শ সংখ্যা শূন্য, এক আর দুই ঠিক আছে, তিনটা হলেই একটু থামো, আর তিনের বেশি হলে খুব বিশেষ কারণ দরকার। এই সংখ্যাগুলো আদালতের আদেশের মতো মানতে হবে না — কিন্তু এগুলো বলে কোন দিকটা সুস্থ।
Smell টা শুধু সংখ্যার ব্যাপার না। আসল প্রশ্ন হলো: এই parameter গুলো কি একসাথে থাকে? একই দল কি বিভিন্ন method-এ বারবার আসে? একই type-এর পাশাপাশি parameter সহজে swap হয়ে যায় না? সংখ্যা হলো অ্যালার্ম; সম্পর্কগুলো হলো আসল রোগ।
একটু deep যাই: Parameter list হলো একটা function-এর interface-এর অংশ, আর interface হলো contract যেটা সিদ্ধান্ত লুকাবে, প্রকাশ করবে না — এটা Parnas-এর ১৯৭২ সালের information hiding নীতি। দশটা আলাদা parameter প্রতিটা caller-এর কাছে দশটা সিদ্ধান্ত প্রকাশ করে দেয়। একটা parameter object সেগুলো একটা নামকরণ করা ধারণার পেছনে আবার লুকিয়ে ফেলে, যাতে contract পরিবর্তন সামলাতে পারে: object-এ একটা field যোগ করলে একটাও signature ভাঙে না।
কীভাবে চিনবে
তারিক ভাই রুবেলের pull request খুলে তার checklist জোরে পড়ে। তোমার function signature গুলো এভাবে দেখো:
- একটা method চার, পাঁচ বা তার বেশি parameter নেয়, আর call site গুলো অর্থহীন value-র লম্বা সারি।
- কয়েকটা parameter একই type শেয়ার করে —
transfer(fromAccount, toAccount)বাmove(x1, y1, x2, y2)— নীরব swap-এর আমন্ত্রণ। - কিছু parameter শুধু অপরিবর্তিত অবস্থায় পাঠিয়ে দেওয়া হয় অন্য function-এ — method এমন data-র courier যেটা সে কখনো ব্যবহারই করে না।
- Boolean flag দেখা যাচ্ছে:
render(doc, true, false, true)— boolean গুলো কী নিয়ন্ত্রণ করছে কেউ বোঝে না। - একই দল parameter method-এর পর method-এ আসছে (street, city, PIN... বারবার)।
- একটা নতুন সংশ্লিষ্ট data যোগ করতে গেলে অনেক signature আর প্রতিটা caller বদলাতে হয়।
| লক্ষণ | কী বলছে |
|---|---|
Call site-এ f(a, b, c, d, e, f) | Declaration অন্য উইন্ডোতে খুলে না রাখলে কেউ call টা পড়তে পারবে না |
| একই type-এর পাশাপাশি parameter | Transposition bug অপেক্ষা করছে; compiler swap ধরবে না |
| অপরিবর্তিত forward হওয়া parameter | Data গুলো আলাদা টুকরো হিসেবে না, একটা object হিসেবে চলতে চাইছে |
Call-এ true, false, true | লুকানো behaviour switch; method-টা আসলে দুটো method হতে পারে |
| Signature জুড়ে একই দল বারবার | একটা Data Clump — একটা missing class — দরজায় কড়া নাড়ছে |
| একটা নতুন field ১৪টা file-এ পরিবর্তন আনে | আলাদা value গুলো ছড়িয়ে পড়েছে; পরিবর্তনের কোনো এক জায়গা নেই |
রুবেলের orderChai ছয়টার মধ্যে পাঁচটা বাক্সে টিক পড়ে। শুধু pass-through টা মিস করে — কারণ তখন পর্যন্ত app-এ মাত্র একটা function।
কেন এটা সমস্যা
- Error-prone call. একই type-এর positional argument নীরবে swap করা যায়।
createBooking("ঢাকা", "চট্টগ্রাম")বনামcreateBooking("চট্টগ্রাম", "ঢাকা")— দুটোই compile হয়, একটা refund অপেক্ষা করছে। - Unreadable call site.
setupAccount("নাসরিন", 14, true, false, 2500, "B")কোনো মানে বহন করে না। পাঠককে প্রতিটা call-এর পাশে signature খুলে রাখতে হয়, যেন একটা চিঠি পড়ছে অন্য হাতে অভিধান নিয়ে। - Brittle signature. একটা parameter যোগ করলে প্রতিটা caller ভাঙে। দুটো reorder করলে কিছু caller নীরবে ভাঙে। Method-টা evolve করা ব্যয়বহুল হয়ে যায়, তাই মানুষ evolve করাই ছেড়ে দেয়।
- Testing-এর ঝামেলা। প্রতিটা test একটা behaviour test করতে হলেও পুরো argument list তৈরি করতে হয়। Test file ভরে যায় noise-এ যা প্রতিটা test-এর আসল পয়েন্ট লুকিয়ে ফেলে।
- গভীর smell লুকায়। দীর্ঘ list সাধারণত Data Clumps (একসাথে থাকা দল) ধারণ করে আর প্রায়ই Primitive Obsession (সব কিছু আলাদা string আর number হিসেবে পাঠানো) থেকে খায়। আর দশটা input-এর দরকার হয় এমন একটা method প্রায়ই অনেক কাজ করছে — একটা Long Method লম্বা signature পরে আছে।
কীভাবে list এত লম্বা হয়? সব bloater যেভাবে বাড়ে — একবারে একটা নিরীহ পদক্ষেপ করে:
চিত্রের নিচের loop লক্ষ্য করো — এটাই ফাঁদ। একবার list লম্বা হলে আর call site অনেক হলে, আজকে redesign করার চেয়ে আরও একটা parameter যোগ করা সবসময় সস্তা। তাই list শুধু বাড়তেই থাকে।
তারিক ভাই তারপর whiteboard-এ রুবেলের আসল signature ব্যবচ্ছেদ করেন। কোন ধরনের parameter list ভরায়? বেশিরভাগই সবচেয়ে বিপজ্জনক ধরনের:
দশটার মধ্যে ছয়টা boolean। তাদের প্রতিটা পাশাপাশি জোড়া একটা নীরব swap অপেক্ষায়। আর একটা call site সঠিকভাবে পড়ার খরচ প্রতিটা parameter যোগে তীব্রভাবে বাড়ে, কারণ পাঠককে নাম না, position মেলাতে হয়:
একটা লাইন decode করতে আশি সেকেন্ড — আর code লেখার চেয়ে পড়া হয় অনেক বেশিবার।
রুবেলের সন্ধ্যা, দুটো diagram-এ
Code ঠিক করার আগে, তারিক ভাই রুবেলকে দেখায় যে সে দুটো design ইতিমধ্যেই জানে — প্রতিদিন চায়ের দোকানে জীবন দিয়ে বোঝে। প্রথমে, smelly protocol:
একই দোকান, একই চাওয়ালা, সম্পূর্ণ আলাদা error rate। পার্থক্য হলো message-এর আকৃতিতে: আলাদা positional value বনাম একটা নামকরণ করা, সংরক্ষিত bundle। রুবেলের দৈনন্দিন অভিজ্ঞতা একটা যাত্রা হিসেবে:
"তোমার function-এর caller-রা," তারিক ভাই বলে, "সবাই তোমার সন্ধ্যা কাটাচ্ছে। প্রতিটা call site দশটা value মুখস্থ করে আর প্রার্থনা করে।"
কোন signature আগে ঠিক করবে?
Codebase-এ ডজনখানেক লম্বা signature আছে। তারিক ভাই রুবেলকে শেখায় দুটো axis-এ rank করতে: কতগুলো parameter, আর কতগুলো call site। অনেক parameter আর অনেক call site মানে জরুরি অবস্থা; একবার call হওয়া লম্বা list শুধু অগোছালো।
orderChai app-এর সবচেয়ে বেশি call হওয়া function আর সবচেয়ে লম্বা signature — উপরের ডান কোণে, আজকেই ঠিক করো।
একটা বাস্তব code-এর উদাহরণ
এই function-টা রুবেল ঠিক যেভাবে লিখেছিল। চায়ের দোকানের ordering app, ইন্টার্নের nightmare-এর কেন্দ্রে:
function orderChai(
customerName: string,
sugarSpoons: number,
milkLevel: string, // "none" | "half" | "full"
ginger: boolean,
cardamom: boolean,
strong: boolean,
cupSize: string, // "small" | "regular"
veryHot: boolean,
takeaway: boolean,
paperCup: boolean,
): string {
let price = cupSize === "small" ? 10 : 15;
if (ginger) price += 2;
if (cardamom) price += 3;
if (takeaway && paperCup) price += 1;
let desc = customerName + ": " + cupSize + " chai, " +
sugarSpoons + " sugar, " + milkLevel + " milk";
if (ginger) desc += ", ginger";
if (cardamom) desc += ", cardamom";
if (strong) desc += ", strong";
if (veryHot) desc += ", very hot";
if (takeaway) desc += paperCup ? ", takeaway (paper cup)" : ", takeaway";
return desc + " = Rs." + price;
}
// The intern's daily call. Can YOU spot which boolean is which?
orderChai("Intern", 1, "full", true, false, true, "small", true, true, true);Call site-টা আবার পড়ো: (true, false, true, "small", true, true, true)। পাঁচটা boolean পর পর। যেকোনো দুটো swap করলে অর্ডার নীরবে বদলে যাবে — ঠিক "বেশি চিনি, কম আদা" ভুলে বলার মতো। আর ভাবো menu-তে নতুন option আসলো, যেমন তুলসী পাতা। সেটা আরও একটা parameter, আর app-এর প্রতিটা call site সঠিক position-এ edit করতে হবে। এই function-এর test গুলো ততটাই দুঃখজনক: প্রতিটা একটা test case-এর জন্য দশটা argument সাজাতে হবে।
Boolean parameter-এর সারি এই smell-এর সবচেয়ে বাজে সংস্করণ। প্রতিটা boolean একটা লুকানো switch, আর call site-এ শুধু true, false, true দেখা যায় — কারো কাছে কোনো মানে নেই। Flag এটাও ইঙ্গিত করে যে একটা function আসলে লুকিয়ে কয়েকটা function।
ধাপে ধাপে পরিষ্কার করা
ধাপ ১: Introduce Parameter Object দিয়ে recipe bundle করো। তারিক ভাইয়ের রুবেলের কাছে প্রথম প্রশ্ন: "এই দশটা value একসাথে কী বর্ণনা করে?" উত্তরটাই missing type-এর নাম বলে দেয় — একটা chai preference:
interface ChaiPreference {
sugarSpoons: number;
milkLevel: "none" | "half" | "full";
spices: { ginger: boolean; cardamom: boolean };
strong: boolean;
cupSize: "small" | "regular";
veryHot: boolean;
serving: "dine-in" | "takeaway-glass" | "takeaway-paper";
}
function orderChai(customerName: string, pref: ChaiPreference): string {
const price = priceOf(pref);
return describe(customerName, pref) + " = Rs." + price;
}দুটো parameter। আর bundle করার সময় একটা বোনাস উন্নতিও পেলাম: takeaway: boolean আর paperCup: boolean একটা অর্থহীন combination মঞ্জুর করত (takeaway = false, paperCup = true — dine-in... কাগজের কাপে??)। তিনটা নামকরণ করা value সহ একটা serving field সেই impossible state-কে অপ্রকাশযোগ্য করে দেয়। Parameter bundle করলে প্রায়ই এমন লুকানো নিয়ম বেরিয়ে আসে।
Call site এখন নিজেই ব্যাখ্যা করে:
orderChai("রুবেল", {
sugarSpoons: 1,
milkLevel: "full",
spices: { ginger: true, cardamom: false },
strong: true,
cupSize: "small",
veryHot: true,
serving: "takeaway-paper",
});প্রতিটা value-এ label আছে। Swap করা অসম্ভব। পরে তুলসী যোগ করলে type আর চা বানানোর অংশ বদলাবে — প্রতিটা caller না।
ধাপ ২: Preserve Whole Object দিয়ে "আমার সাধারণটা!" নিয়মিত customer-দের কাছে আগে থেকেই একটা সংরক্ষিত preference আছে। তাই customer থেকে field টেনে বের করে পাঠিও না — customer-এর সংরক্ষিত object-টাই পাঠাও:
class Customer {
constructor(
readonly name: string,
readonly usual: ChaiPreference,
) {}
}
function orderUsual(customer: Customer): string {
return orderChai(customer.name, customer.usual); // "ভাই, সাধারণটা!"
}তারিক ভাই গুড়ে বদলে গেলে, তার usual এক জায়গায় update হয়, আর প্রতিটা ভবিষ্যৎ অর্ডার সেটা follow করে — ঠিক সালাম ভাইকে একবার বলার মতো।
ধাপ ৩: Replace Parameter with Method Call দিয়ে method নিজেই খুঁজে নিতে পারে এমন parameter সরাও। ধরো original function todaysMilkPrice: numberও নিত, যেটা প্রতিটা caller পাঠাত। যদি function নিজেই price service-কে জিজ্ঞেস করতে পারে, parameter সরিয়ে দাও — caller-দের কম কিছু fetch আর forward করতে হবে:
// Before: every caller fetches the price and forwards it
function priceOf(pref: ChaiPreference, milkPrice: number): number { /* ... */ }
// After: the function asks for what it needs
function priceOf(pref: ChaiPreference): number {
const milkPrice = priceBoard.currentMilkPrice();
/* ... */
}Refactor করা design, যে diagram রুবেল pull request-এ যোগ করে:
আর flow, আগে আর পরে:
Preserve Whole Object-এ একটা সতর্কতা: পুরো object তখনই পাঠাও যখন function যুক্তিসংগতভাবে ওই object-এর জগতে পড়ে। একটা বিশাল Customer এমন function-কে দেওয়া যেটার শুধু একটা সংখ্যা দরকার, সেটা অপ্রয়োজনীয় dependency তৈরি করে। সব ওষুধের মতো, মাত্রা গুরুত্বপূর্ণ।
একটু deep যাই: একটা parameter object শুধু একটা bag না — এটা একটা সম্ভাব্য abstraction। ChaiPreference তৈরি হওয়ার মুহূর্তে, behaviour সেখানে migrate করতে শুরু করে: priceOf স্বাভাবিকভাবে pref.price() হয়ে যায়, আর validation তার constructor-এ চলে যায়। Fowler এই ripple effect-এর কথা বলেছেন: object introduce করাটা প্রায়ই একটা missing domain class আবিষ্কারের প্রথম ধাপ। Bag একটা নাগরিক হয়ে ওঠে।
এই smell-এর জীবনচক্র
একটা signature-এর জীবন প্রতিটা bloater-এর মতো একই arc অনুসরণ করে — আর যত দেরি করো, ফিরে আসার পথে তত বেশি call site touch করতে হবে:
সবচেয়ে সস্তা arrow, সবসময়ের মতো, হলো আগেরটা: parameter চারটায় bundle করো, যখন পাঁচটা call site আছে — parameter দশটায় না, যখন পঞ্চাশটা।
C#-এ একই smell
একটা school admission system, আগে আর পরে। Smelly signature:
public void EnrollStudent(
string firstName, string lastName, int age,
string street, string city, string pinCode,
string guardianName, string guardianPhone,
bool needsBus, string busRoute)
{
// ...
}
// Call site - good luck reading this in a code review:
EnrollStudent("সুমাইয়া", "বেগম", 12, "১৪ মিরপুর রোড", "ঢাকা", "১২১৬",
"জামাল হোসেন", "০১৭১২৩৪৫৬৭৮", true, "Route 7");Parameter object introduce করার পরে (লক্ষ্য করো parameter নামেই দলগুলো আগে থেকেই দৃশ্যমান ছিল — সেগুলো সবসময় Data Clumps ছিল):
public record StudentName(string First, string Last);
public record Address(string Street, string City, string PinCode);
public record Guardian(string Name, string Phone);
public record BusPlan(string Route); // absent = no bus needed
public void EnrollStudent(
StudentName name, int age, Address address,
Guardian guardian, BusPlan? bus)
{
// ...
}
EnrollStudent(
new StudentName("সুমাইয়া", "বেগম"), 12,
new Address("১৪ মিরপুর রোড", "ঢাকা", "১২১৬"),
new Guardian("জামাল হোসেন", "০১৭১২৩৪৫৬৭৮"),
new BusPlan("Route 7"));দশটা parameter পাঁচটায় নামল, প্রতিটা value তার type দিয়ে label করা, আর অদ্ভুত needsBus + busRoute জোড়া (যখন needsBus false ছিল তখন route মানে কী?) একটা nullable BusPlan-এ ভেঙে পড়ল। C# named argument (age: 12) call site-এ readability উন্নত করতে পারে — একটা ভালো band-aid, যদিও parameter object-ই আসল সমাধান।
বাস্তব project-এ এই smell কোথায় লুকায়
- বড় class-এর constructor। বারোটা field-এর একটা class প্রায়ই বারোটা parameter-এর constructor তৈরি করে। এটা এত সাধারণ যে Builder pattern মূলত এটাই manage করতে তৈরি হয়েছে — যদিও বারোটা value দরকার হয় এমন constructor প্রায়ই একটা Large Class-এর স্বীকারোক্তি।
- Business app-এ service আর helper method।
CreateInvoice(customerId, name, email, street, city, pin, amount, tax, discount, dueDate, notes)— enterprise codebase এগুলো ভরা, আর refactoring guide গুলো নিয়মিত এই আকৃতিকে warning উদাহরণ হিসেবে ব্যবহার করে। - Report আর search function।
searchProducts(keyword, minPrice, maxPrice, category, brand, inStock, sortBy, page, pageSize)— filter criteria হলো একটা singleSearchCriteriaparameter object-এর classic case। - Legacy C API আর তাদের wrapper। পুরনো procedural library সব কিছু explicitly পাঠায়; তাদের wrapper গুলো লম্বা list উত্তরাধিকার সূত্রে পায় যদি না কেউ সচেতনভাবে bundle করে।
- UI component props। একটা visual concept-এর জন্য বিশটা আলাদা prop নেওয়া component (colour, border, shadow, radius...) হলো front-end version — design system গুলো সেগুলো theme/style object-এ bundle করে।
- Test helper function।
makeTestOrder(a, b, c, d, e, f, g)— একটু হাসির কথা, test সরল করতে লেখা helper প্রায়ই সবচেয়ে লম্বা list বহন করে।
Linter এটা তোমার হয়ে দেখতে পারে: ESLint-এর max-params rule, SonarQube-এর parameter-count rule, আর বেশিরভাগ code-quality tool টিমকে একটা threshold (সাধারণত ৪) set করতে আর অপরাধী signature গুলো automatically flag করতে দেয়।
কখন উপেক্ষা করা ঠিক আছে
| পরিস্থিতি | Smell উপেক্ষা করবে? | কেন |
|---|---|---|
| ৩-৪টা সত্যিকারের স্বাধীন, সংশ্লিষ্টতাহীন parameter | হ্যাঁ | Method সৎভাবে তার input বলছে; নকল "parameter bag" শুধু indirection যোগ করবে |
| অনেক method জুড়ে একই দল বারবার আসছে | না | এটা Data Clump — একবার bundle করো আর সবাই উপকৃত হবে |
| Value-object constructor (যেমন একটা Date নিচ্ছে year, month, day) | সাধারণত হ্যাঁ | প্রতিটা parameter একটা value-এর আলাদা উপাদান; bundle করা circular হবে |
| একই type-এর পাশাপাশি যেমন (from, to) বা (x, y) | ঝুঁকিপূর্ণ | Transposition bug নীরব; Route বা Point-এর মতো ছোট typed pair বিবেচনা করো |
| Named/optional argument সহ ভাষা, ৪টা readable param | কখনো কখনো | Named argument পড়ার সমস্যা সারায়; দেখো দলটা বারবার আসছে কিনা |
| Behaviour নিয়ন্ত্রণকারী boolean flag | না | Flag bundle করার বদলে আলাদা নামের method-এ ভাগ করো |
সহজ কথায়: কয়েকটা সংশ্লিষ্টতাহীন parameter ঠিক আছে। সমাধান সবচেয়ে কার্যকর যখন parameter গুলো একসাথে থাকে, একই type শেয়ার করে, বা শুধু forward হয় — এগুলো missing object-এর লক্ষণ, শুধু অগোছালো signature-এর না।
কোন refactoring সারায়
| Refactoring | কখন ব্যবহার করবে |
|---|---|
| Introduce Parameter Object | বারবার আসা parameter-এর দল একসাথে থাকে — একটা named type-এ bundle করো |
| Preserve Whole Object | Caller ইতিমধ্যে value ধারণকারী object রাখে — টুকরো না, পুরো object পাঠাও |
| Replace Parameter with Method Call | Method নিজেই একটা value derive বা খুঁজে নিতে পারে — caller-দের fetch আর forward করা বন্ধ করো |
| Extract Method | Method অনেক input দরকার কারণ অনেক কাজ করে — ভাগ করো, প্রতিটা অংশের কম দরকার হবে |
| Replace Data Value with Object | আলাদা primitive list ফুলিয়ে দেয় — typed value গুলো সেটা সংকুচিত আর স্পষ্ট করে |
এক পাতায় পুরো smell
রুবেলের revision sketch, তার ডেস্কের উপরে tape করা চায়ের দোকানের ছবির পাশে:
দ্রুত revision বাক্স
+------------------------------------------------------------------+
| LONG PARAMETER LIST - CHEAT SHEET |
+------------------------------------------------------------------+
| What : A method demanding many loose values in exact |
| order (the 10-instruction chai order) |
| Family : Bloaters |
| Spot it : 4+ params, same-typed neighbours, boolean flags, |
| repeated clusters, pass-through values |
| Costs : Swap bugs, unreadable calls, brittle signatures, |
| painful tests |
| Main fix : Introduce Parameter Object ("Bhaiya, usual!") |
| Helpers : Preserve Whole Object, Replace Parameter with |
| Method Call |
| Ignore : Few, truly independent inputs; value-object ctors |
| Mantra : "Name the bundle, then pass the bundle." |
+------------------------------------------------------------------+অনুশীলনী
তারিক ভাইয়ের রুবেলের জন্য বিদায়ী homework — এখন তোমার। এই function একটা school-এর picnic bus বুক করে। Signature-টা দানব — একে বশে আনো।
function bookPicnicBus(
className: string,
section: string,
studentCount: number,
teacherName: string,
teacherPhone: string,
destination: string,
distanceKm: number,
departHour: number,
returnHour: number,
needVeg: boolean,
needJain: boolean,
ac: boolean,
): number {
let cost = distanceKm * 30;
if (ac) cost += 500;
if (returnHour - departHour > 8) cost += 300; // long day surcharge
const meals = studentCount * (needJain ? 60 : needVeg ? 50 : 55);
return cost + meals;
}
// A real call from the app:
bookPicnicBus("7", "B", 42, "Mrs. Iyer", "9812345678",
"Science City", 35, 8, 17, true, false, true);তোমার কাজ:
- লুকানো দলগুলো খোঁজো। (ইঙ্গিত: কোন parameter গুলো class group বর্ণনা করে? কোনগুলো trip বর্ণনা করে? কোনগুলো meal plan বর্ণনা করে? কোন জোড়া সবসময় একটা time window হিসেবে একসাথে চলে?)
- Introduce Parameter Object apply করো:
ClassGroup,Trip(একটাTimeWindowসহ), আরMealPlantype তৈরি করো। Function-টা তিন বা চারটা parameter নিতে rewrite করো। needVeg/needJainজোড়া একটা অর্থহীন combination মঞ্জুর করে (needVeg = false, needJain = true?)। দুটো boolean-কে একটা field দিয়ে replace করো:"standard" | "veg" | "jain"।- Bonus:
departHourআরreturnHourদুটোই number — swap-prone! তোমারTimeWindowtype কি validate করে যে return departure-এর পরে আসে? - Extra challenge: আগে/পরে তোমার নিজের চিত্র ৮-এর version sketch করো। Bundle কতটা arrow সরাল?
যখন তোমার call site একটা বাক্যের মতো পড়া যাবে আর কোনো দুটো argument নীরবে swap করা যাবে না, তখন চায়ের দোকানে তোমার "সাধারণটা" পাওয়ার যোগ্যতা হয়েছে — সালাম ভাই সেটা প্রস্তুত রেখেছেন। পরের পাঠ: এই বারবার আসা দলগুলোর পেছনের গভীর pattern — Data Clumps।
সচরাচর জিজ্ঞাসা
- কতটা parameter থাকলে সেটা বেশি হয়ে যায়?
- বেশিরভাগ গাইড চারটা বা তার বেশি হলে সতর্কতার রেখা টানে। Robert C. Martin তার Clean Code বইতে আরও কড়া কথা বলেছেন: শূন্য argument সবচেয়ে ভালো, এক বা দুইটা ঠিক আছে, তিনটা হলে একটু ভাবো, আর তিনের বেশি হলে সেটার খুব বিশেষ কারণ দরকার। এগুলো কঠোর আইন না, স্মেল ধরার সংকেত।
- Boolean flag parameter কি সত্যিই এতটা খারাপ?
- সাধারণত হ্যাঁ। render(doc, true, false) এর মতো call পড়ে বোঝার কিছু নেই, আর একটা flag মানে প্রায়ই একটা method আসলে দুটো কাজ করছে। বরং দুটো ভালো নামের method রাখো, যেমন renderDraft আর renderFinal, অথবা অন্তত একটা named options object ব্যবহার করো।
- Parameter object কী?
- একটা ছোট class বা type যেটা সংশ্লিষ্ট parameter গুলো একসাথে বেঁধে রাখে। প্রতিটা function-এ আলাদা করে street, city আর PIN পাঠানোর বদলে একটা Address পাঠাও। Call site ছোট হয়ে যায়, আর প্রতিটা signature না বদলেই নতুন field যোগ করা যায়।
- দুটো value পাঠানোর বদলে পুরো object পাঠানো কি বেশি খরচের না?
- না — প্রায় সব ভাষায় তুমি reference পাঠাও, copy না, তাই খরচ একই। বড় প্রশ্নটা হলো dependency-র: পুরো object তখনই পাঠাও যখন method সত্যিই ওই object-এর জগতে পড়ে।
- Named argument দিয়ে কি এই স্মেল ঠিক হয়?
- এটা readability-র সমস্যাটা কিছুটা সারায় — caller দেখতে পায় প্রতিটা value কীসের। কিন্তু মূল সমস্যা ঠিক হয় না: একই দল value এখনও অনেক signature-এ আলাদা আলাদা ঘুরে বেড়ায়। Parameter object দুটো সমস্যাই — reading আর design — একসাথে ঠিক করে।
আরো দেখো
সম্পর্কিত পাঠ
Data Clumps: যে বন্ধুরা সবসময় একসাথে ঘোরে
শিক্ষার্থীদের জন্য Data Clumps code smell — শেখো কীভাবে সবসময় একসাথে চলা value-এর গ্রুপ চেনা যায় আর সেগুলোকে একটা class-এ bundle করা যায়, ঠিক যেমন একটা student ID card।
Primitive Obsession: যখন সব কিছুই শুধু একটা string বা number
Primitive Obsession সহজ ভাষায় — কেন plain string আর number bug লুকিয়ে রাখে, আর কীভাবে Money বা Address-এর মতো value object দিয়ে code-কে safe আর পরিষ্কার করা যায়।
Long Method: যখন একটা function সব কিছু করতে চায়
Long Method code smell শিখো সহজ গল্পের মাধ্যমে — TypeScript আর C# example সহ, Extract Method দিয়ে step-by-step refactoring। একদম beginner-friendly গাইড।
Large Class: যে স্কুলের ব্যাগে সব কিছু থাকে
Large Class code smell কী সেটা বুঝো — কেন god class বড় হয়, low cohesion কীভাবে চেনা যায়, আর Extract Class দিয়ে কীভাবে ছোট ছোট focused class-এ ভাগ করা যায়।