Long Method: যখন একটা function সব কিছু করতে চায়
Long Method code smell শিখো সহজ গল্পের মাধ্যমে — TypeScript আর C# example সহ, Extract Method দিয়ে step-by-step refactoring। একদম beginner-friendly গাইড।
রামু কাকার গল্প — বাড়ির একমাত্র সাহায্যকারী
ধরো মিরপুরের একটা পুরনো বাড়ি। বাড়িতে পাঁচজন মানুষ: দাদি, বাবা, মা, ষোলো বছরের ফাতেমা, আর আট বছরের ছোট্ট রাফি — যে সারাদিন খিদে নিয়ে ঘোরে।
বাড়িতে একজনই কাজ করেন — রামু কাকা। আর রামু কাকা সব কিছু করেন।
ভোর পাঁচটায় উঠে নাস্তা রান্না করেন। তারপর পুরো বাড়ি ঝাড়ু দেন। তারপর বাজারে দৌড়ান সবজি কিনতে। তারপর কাপড় ধোন। বিকেলে রাফিকে অঙ্কও পড়ান! রাতে রামু কাকা ক্লান্ত, মাথা এলোমেলো, একটু বিরক্তও।
একদিন দাদি জিজ্ঞেস করলেন, "রামু কাকা, আজ দুপুরের খাবার দেরি হলো কেন?" রামু কাকা সহজে উত্তর দিতে পারলেন না। বাজারে ভিড় ছিল বলে? কাপড় ধুতে দেরি হয়েছিল বলে? রাফির অঙ্ক ক্লাস বেশিক্ষণ চলল বলে? সব কিছু একজনের দিনে মিশে গেছে — কোথায় সমস্যা সেটা খুঁজে পাওয়া কঠিন।
এবার একটু ভাবো — যদি পরিবার আলাদা রাঁধুনি, আলাদা ঝাড়ুদার, আলাদা গৃহশিক্ষক রাখত, আর রামু কাকা শুধু তাদের manage করতেন? তাহলে দুপুরের খাবার দেরি হলে রাঁধুনিকে জিজ্ঞেস করতো। মেঝে নোংরা হলে ঝাড়ুদারকে। প্রতিটা কাজের একটা নাম আছে, একজন মালিক আছে, একটা পরিষ্কার জায়গা আছে।
এখন গল্পে দ্বিতীয় চরিত্র আসে। ফাতেমা computer science-এর প্রথম বর্ষের ছাত্রী, এখন বাড়িতে ছুটিতে এসেছে। সে code করতে ভালোবাসে। মজার project হিসেবে একটা ছোট "Home Helper" app বানাল রামু কাকার দিন track করতে। তার cousin তারিক, যে একটা software company-তে কাজ করে, video call-এ code review করার কথা দিল।
ফাতেমা runHelperDay নামে একটা বড় function লিখল। সেটা রান্না করে, পরিষ্কার করে, বাজার করে, পড়ায় — সব একটা বিশাল block-এ, ঠিক রামু কাকার মতো। কাজ করছে perfectly। সে গর্বিত।
তারিক file খুলে একবার scroll করল, দুইবার scroll করল, তারপর হাসল। "ফাতেমা," সে বলল, "তোমার code-এ একটা smell আছে। সবচেয়ে বিখ্যাত smell। নাম হলো Long Method।"
এই পুরো lesson হলো সেই একটা video call-এর গল্প — তারিক কীভাবে ফাতেমাকে smell চিনতে, মাপতে আর সারাতে শেখাল। শেষে তুমিও যেকোনো function দেখলে এটা করতে পারবে।
এই smell টা আসলে কী?
Long Method define করার আগে তারিক ফাতেমাকে একটা গুরুত্বপূর্ণ phrase শেখায়: code smell।
Code smell মানে bug না। Code চলছে। Output ঠিক আছে। Customer এখন খুশি। Smell হলো code-এর structure-এ একটা সতর্কসংকেত। ঠিক যেমন রান্নাঘর থেকে অদ্ভুত গন্ধ আসা মানে এখনই খাবার পুড়ে গেছে না — মানে হলো এখনই না দেখলে পুড়ে যাবে। Smell বলে, "এই code পরে বদলাতে গেলে কষ্ট হবে।"
এই একটা লাইন মনে রাখো: Code smell মানে bug না — code কাজ করছে, কিন্তু ভেতরে সমস্যার সংকেত দিচ্ছে।
তাহলে Long Method smell টা কী?
Long Method হলো এমন একটা function (বা method) যেটা এত বড় হয়ে গেছে যে সে একসাথে অনেক আলাদা কাজ করার চেষ্টা করছে। Input validate করছে, calculation করছে, output format করছে, data save করছে, message পাঠাচ্ছে — সব একটা body-র ভেতরে। Martin Fowler তার বিখ্যাত বই Refactoring-এ এটাকে "Bloater" smell-এর সবচেয়ে common উদাহরণ বলেছেন। Bloater মানে হলো যে code এত বড় হয়ে গেছে যে নিয়ে কাজ করা কঠিন।
"কিন্তু কত লাইন হলে বেশি?" ফাতেমা জিজ্ঞেস করে। তারিক মাথা নাড়ে। আসল ব্যাপারটা লাইন সংখ্যা না। আসল ব্যাপার হলো এক method, এক কাজ। ১০ লাইনের method যদি তিনটা কাজ করে, সেটা ৩০ লাইনের method যেটা একটা পরিষ্কার কাজ করে তার চেয়ে বেশি খারাপ। Robert C. Martin Clean Code-এ বলেন: একটা function একটা কাজ করবে, সেটা ভালোভাবে করবে, আর শুধু সেটাই করবে।
রামু কাকার দিনের মতো ভাবো। সমস্যাটা এই না যে সে দশ ঘণ্টা কাজ করছে। সমস্যা হলো রান্না, ঝাড়ু, বাজার, পড়ানো — সব একটা সময়সূচিতে জড়িয়ে গেছে। তাই কোনো একটা অংশ আলাদা করে check, বদলানো বা অন্য কাউকে দেওয়া যাচ্ছে না।
College corner: formal ভাষায় বললে, Long Method সাধারণত low cohesion এর সমস্যা — এর statements অনেকগুলো আলাদা লক্ষ্য পূরণ করছে — আর high cyclomatic complexity আছে, যেটা code-এর ভেতরের independent path গুলো count করে (প্রতিটা if, loop, আর case একটা করে যোগ করে)। SonarQube-এর মতো tools complexity ১০-১৫ পার হলে flag করে। Fowler-এর আরো গভীর heuristic হলো separation of intention and implementation: method body-টা intentions-এর একটা list হওয়া উচিত, আর প্রতিটা implementation ভালো নামের একটা call-এর পেছনে লুকানো থাকবে।
কীভাবে চিনবে
তারিক screen share করে ফাতেমাকে তার personal checklist দেখায়। Long Method detect করতে fancy tool লাগে না। নিজের চোখই যথেষ্ট। কোনো function খুললেই এই list চালাও:
- পুরো method পড়তে scroll করতে হচ্ছে — একটা screen-এ ধরছে না।
- ভেতরে comment banner আছে, যেমন
// step 1: validateবা// step 2: calculate। প্রতিটা banner আসলে একটা method যেটা জন্ম নিতে চাইছে। - Code-এ nesting-এর সিঁড়ি আছে — loop-এর ভেতরে if, if-এর ভেতরে loop, তিন-চার level গভীর।
- উপরে অনেক local variable declare হয়েছে আর পুরো body জুড়ে বদলাচ্ছে। তাদের মান মাথায় রাখতে হয় memory game-এর মতো।
- Method-এর নাম অস্পষ্ট:
process,handle,doStuff, অথবা নামে "and" আছে — যেমনvalidateAndSaveAndNotify। - এর জন্য test লিখতে গেলে মনে হচ্ছে একটা বিস্কুট খেতে পুরো বিয়ের আয়োজন করতে হচ্ছে।
এই checklist-টা একটা quick reference table হিসেবে:
| লক্ষণ | কী বলছে |
|---|---|
| পড়তে scroll করতে হচ্ছে | Method-এ এক screen-এর বেশি idea আছে, তাই এক কাজের বেশি করছে |
// এখন tax calculate করো এরকম comment | যিনি লিখেছিলেন তিনি sections জানতেন — শুধু extract করেননি |
| ৪+ level nesting | Condition আর loop জড়িয়ে গেছে; logic-কে নাম দিয়ে আলাদা করতে হবে |
| ১০+ local variable | Section-গুলোর মধ্যে অনেক বেশি state share হচ্ছে; এক জায়গায় বদলালে আরেক জায়গা ভেঙে যেতে পারে |
processData এরকম অস্পষ্ট নাম | লেখক কাজের নাম দিতে পারেননি কারণ কাজ অনেকগুলো |
| এক test-এ ১২টা জিনিস check হচ্ছে | Method-কে ছোট, সৎ টুকরোয় test করা যাচ্ছে না |
দুটো বা তার বেশিতে টিক পড়লে smell পেয়ে গেছো। ফাতেমা runHelperDay-তে checklist চালিয়ে চুপ করে চারটায় টিক দেয়। "ঠিক আছে," সে স্বীকার করে, "smell আছে।"
College corner: researcher-রা এটা LCOM (Lack of Cohesion of Methods) আর statement-level slice-based cohesion দিয়ে measure করেন। কিন্তু practice-এ ওই math লাগে না — comment banner আর shared local variable-ই একই signal, খালি চোখে দেখা যায়।
কেন এটা সমস্যা
"কিন্তু তারিক ভাই, code তো কাজ করছে!" ফাতেমা বলে। হ্যাঁ, করছে। তাহলে চিন্তা কেন? কারণ software একবার লিখে ভুলে যাওয়া যায় না। এটা শত শতবার পড়া হয় আর ডজন ডজনবার বদলানো হয়। Long Method প্রতিটা ভবিষ্যৎ visit-কে কষ্টের করে তোলে।
- পড়া ধীর হয়ে যায়। Discount logic বুঝতে গেলে validation, loop, আর printing পেরিয়ে যেতে হয়। একটা আলমারিতে সব কিছু এলোমেলো ছুঁড়ে দেওয়া থেকে একটা মোজা খোঁজার মতো।
- Reuse অসম্ভব হয়ে যায়। Tax calculation ২০০ লাইনের method-এর মাঝখানে চাপা থাকলে অন্য কেউ call করতে পারবে না। তখন পরের developer সেটা copy করবে — আর এখন Duplicate Code smell-ও পেয়ে গেলে।
- Testing মোটা হয়ে যায়। Shopping না করে শুধু cooking test করা যাচ্ছে না। তাই test কম আর ধীর হয়, আর bugs লুকিয়ে থাকে untested কোণে।
- বদলানো ঝুঁকিপূর্ণ হয়। Long method-এ অনেক local variable share হয়। উপরে একটা লাইন বদলাও, ৮০ লাইন নিচে কোনো কিছু চুপচাপ ভুল আচরণ করতে শুরু করতে পারে।
- অন্য smell লুকিয়ে যায়। Dead code, duplication, আর জটিল condition — সব noise-এর মধ্যে অদৃশ্য হয়ে যায়।
সবচেয়ে খারাপ ব্যাপার হলো এই smell কত চুপচাপ বাড়ে। কেউ কখনো plan করে ৪০০ লাইনের method লেখে না। একটা একটা করে "ছোট" addition দিয়ে বাড়তে থাকে:
প্রতিটা step একা একা দেখলে ছোট মনে হয়েছিল। সব মিলিয়ে একটা দানব তৈরি হয়ে গেছে। Real world-এও একই ঘটনা ঘটে — e-commerce system-এর checkout function বছরের পর বছর ধরে tax, coupon, gift card, fraud check যোগ হতে হতে কয়েক শো লাইন হয়ে যায়।
তারিক তারপর একটা চালাক কাজ করে। ফাতেমার runHelperDay function-এর প্রতিটা লাইন কোন কাজের সেটা color করে দেখায়। ফলাফল তাকে চমকে দেয় — তার "একটা function" আসলে একটা ছাদের নিচে পাঁচটা department লুকিয়ে রেখেছে:
"আর এটা কেউ beginners-দের বলে না," তারিক যোগ করে। "পড়ার সময় ভদ্রভাবে length-এর সাথে বাড়ে না। সেটা বিস্ফোরণ ঘটায়।" প্রতিটা অতিরিক্ত section মাথায় রাখা state-এর combinations বাড়িয়ে দেয়:
২০০ লাইনের method ১০ লাইনের চেয়ে বিশ গুণ কঠিন না। ত্রিশ গুণের কাছাকাছি কঠিন, কারণ shared variable আর branch একে অপরের সাথে interact করে।
College corner: testing theory-তে এই বিস্ফোরণের নাম হলো path coverage। nটা independent branch সহ একটা method-এ ২ঘাত n পর্যন্ত execution path থাকতে পারে। Method ভেঙে দিলে প্রতিটা টুকরোর কম path আলাদাভাবে test করা যায় — multiplication-কে addition-এ পরিণত হয়। এটাই short method থাকার সবচেয়ে গভীর কারণ — style না, testability।
ফাতেমা তার প্রথম bug খোঁজে — কষ্টের live demo
Theory ভালো, কিন্তু তারিক চায় ফাতেমা cost-টা অনুভব করুক। সে একটা কাজ দেয়: "শুক্রবারে রাফির বিস্কুটের budget ভুল হচ্ছে। তোমার নিজের function-এ bug খোঁজো। আমি সময় দেখছি।"
খোঁজার ঘটনাটা কেমন হলো:
একটা ছোট bug-এর জন্য পঁচিশ মিনিট — আর বেশিরভাগ সময় fixing না, finding আর re-reading। আরো খারাপ, fix করার পরেও ফাতেমা নিশ্চিত না সে কিছু ভেঙে ফেলেছে কিনা, কারণ shopping section-এর variable-গুলো cooking section-এর সাথে share হচ্ছে।
এই emotional journey-টা দেখো — প্রতিটা programmer চিনবে:
"এবার ভাবো," তারিক বলে, "প্রতি সপ্তাহে এটা করছো, দশ গুণ বড় file-এ, যেটা দুই বছর আগে চলে যাওয়া কেউ লিখে গেছে। এটাই আমার সোমবার।"
আজ fix করবে নাকি পরে?
ফাতেমা এখন চিন্তায় পড়েছে তার লেখা সব long function নিয়ে। তারিক দুটো প্রশ্ন দিয়ে তাকে ঠান্ডা করে:
- Method কতটা বড় আর জটিল?
- এটা কত ঘন ঘন বদলায়?
যে বিশাল method সপ্তাহে সপ্তাহে বদলাচ্ছে সেটা emergency। যে বিশাল method ২০১৯ সাল থেকে বসে আছে, test আছে, সেটা অপেক্ষা করতে পারে। এই chart-এ যেকোনো method plot করলেই উত্তর বেরিয়ে আসে:
ফাতেমার runHelperDay "Refactor now"-এ পড়ছে — পরিবারের routine বদলালেই সে এটা edit করে, আর এটা project-এর সবচেয়ে বড় function। সময় এসেছে কাজে নামার।
Real code-এর উদাহরণ
রামু কাকাকে code-এ রাখা যাক, ঠিক যেভাবে ফাতেমা লিখেছিল। একটা function helper-এর পুরো দিন চালায়: রান্না, পরিষ্কার, বাজার, পড়ানো। এটাই smelly version TypeScript-এ:
function runHelperDay(home: Home, budget: number): DayReport {
// validate
if (!home) throw new Error("No home given");
if (budget <= 0) throw new Error("Budget must be positive");
if (home.members.length === 0) throw new Error("Empty house");
// cook breakfast and lunch
let mealsCooked = 0;
let groceriesNeeded: string[] = [];
for (const member of home.members) {
if (member.diet === "veg") {
mealsCooked += 2;
if (!home.pantry.includes("dal")) groceriesNeeded.push("dal");
if (!home.pantry.includes("rice")) groceriesNeeded.push("rice");
} else {
mealsCooked += 2;
if (!home.pantry.includes("eggs")) groceriesNeeded.push("eggs");
}
}
// clean the house
let roomsCleaned = 0;
for (const room of home.rooms) {
if (room.lastCleanedDaysAgo > 1) {
roomsCleaned++;
if (room.type === "kitchen" && mealsCooked > 0) {
roomsCleaned++; // kitchen cleaned twice on cooking days
}
}
}
// go shopping
let moneySpent = 0;
for (const item of groceriesNeeded) {
const price = item === "eggs" ? 80 : 60;
if (moneySpent + price <= budget) {
moneySpent += price;
} else {
console.log("Skipped " + item + " - over budget");
}
}
// teach the children
let lessonsTaught = 0;
for (const member of home.members) {
if (member.age < 14 && member.needsTuition) {
lessonsTaught++;
}
}
// build the report
return {
meals: mealsCooked,
rooms: roomsCleaned,
spent: moneySpent,
lessons: lessonsTaught,
summary:
"Cooked " + mealsCooked + " meals, cleaned " + roomsCleaned +
" rooms, spent Rs." + moneySpent + ", taught " + lessonsTaught + " lessons",
};
}তারিক যেভাবে ফাতেমাকে দেখিয়েছিল সেভাবে ভালো করে দেখো। Function কাজ করছে। কিন্তু সমস্যার চিহ্নগুলো লক্ষ করো:
- Comment banner-গুলো (
// cook breakfast,// clean the house) method name-এর কাজ করছে। এটা method name-ই করার কথা ছিল। - Variable
groceriesNeededcooking section-এ তৈরি হয়েছে কিন্তু shopping section-এ ব্যবহার হচ্ছে। VariablemealsCookedআবার cleaning section-এও উঁকি দিচ্ছে। Section-গুলো আলাদা মনে হচ্ছে, কিন্তু shared variable দিয়ে হাত ধরাধরি করে আছে। এটাই long method-এর আসল বিপদ — cooking loop বদলাও, cleaning চুপচাপ ভেঙে যেতে পারে। - Shopping budget logic শুধু test করতে চাইলে পুরো
Homeবানাতে হবে members, rooms, pantry সহ — একটা বিস্কুটের জন্য পুরো বিয়ের আয়োজন।
Shared local variable হলো Long Method-এর লুকানো তার। তারা section-গুলোকে connect করে যেগুলো দেখতে আলাদা। দুই নম্বর section-এ একটা তার কাটলে পাঁচ নম্বর section-এর আলো নিভে যেতে পারে।
Step by step পরিষ্কার করা
তারিক-এর নিয়ম: একসাথে সব কিছু rewrite করব না। ভালো refactoring ছোট, নিরাপদ step-এ এগোয়, প্রতিটার পর test চালায়। আমাদের মূল হাতিয়ার হলো Extract Method — এক কাজ করা একটা block নিয়ে নতুন function-এ ঢোকাও, আর তাকে সৎ নাম দাও।
Step 1: Validation আর cooking extract করো।
function validateDay(home: Home, budget: number): void {
if (!home) throw new Error("No home given");
if (budget <= 0) throw new Error("Budget must be positive");
if (home.members.length === 0) throw new Error("Empty house");
}
function cookMeals(home: Home): { meals: number; groceriesNeeded: string[] } {
let meals = 0;
const groceriesNeeded: string[] = [];
for (const member of home.members) {
meals += 2;
for (const item of missingItemsFor(member.diet, home.pantry)) {
groceriesNeeded.push(item);
}
}
return { meals, groceriesNeeded };
}
function missingItemsFor(diet: string, pantry: string[]): string[] {
const wanted = diet === "veg" ? ["dal", "rice"] : ["eggs"];
return wanted.filter((item) => !pantry.includes(item));
}একটা সুন্দর জিনিস লক্ষ করো। Extract করতে গিয়ে ফাতেমা আবিষ্কার করে যে দুটো diet-ই একই সংখ্যক meal রান্না করত — শুধু grocery list আলাদা ছিল। Extraction শুধু code ছোট করেনি, একটা সহজ সত্য বের করে এনেছে। এটা খুব ঘন ঘন হয়। আমরা এখানে Decompose Conditional ভাবনাও ব্যবহার করেছি: diet-এর condition নিজেই একটা ছোট নামের function হয়ে গেছে।
Step 2: Cleaning, shopping, আর tutoring extract করো।
function cleanRooms(home: Home, cookedToday: boolean): number {
let cleaned = 0;
for (const room of home.rooms) {
if (room.lastCleanedDaysAgo > 1) {
cleaned++;
if (room.type === "kitchen" && cookedToday) cleaned++;
}
}
return cleaned;
}
function buyGroceries(items: string[], budget: number): number {
let spent = 0;
for (const item of items) {
const price = priceOf(item);
if (spent + price <= budget) spent += price;
}
return spent;
}
function priceOf(item: string): number {
return item === "eggs" ? 80 : 60;
}
function teachChildren(home: Home): number {
return home.members.filter((m) => m.age < 14 && m.needsTuition).length;
}দেখো cleanRooms আর সরাসরি mealsCooked variable-এ উঁকি দিচ্ছে না। সে সৎভাবে যা দরকার সেটা চাইছে: একটা সহজ cookedToday flag। লুকানো তারটা এখন দৃশ্যমান, নামকরণ করা doorbell। ছোট priceOf helper হলো Replace Temp with Query-এর একটু ছোঁয়া — temporary variable-এ value রাখার বদলে নামের function-কে জিজ্ঞেস করছি।
Step 3: Top-level method হয়ে যাচ্ছে একটা summary।
function runHelperDay(home: Home, budget: number): DayReport {
validateDay(home, budget);
const { meals, groceriesNeeded } = cookMeals(home);
const rooms = cleanRooms(home, meals > 0);
const spent = buyGroceries(groceriesNeeded, budget);
const lessons = teachChildren(home);
return buildReport(meals, rooms, spent, lessons);
}জোরে পড়ো: validate the day, cook meals, clean rooms, buy groceries, teach children, build report। পড়তে রামু কাকার নতুন job description-এর মতো লাগছে — সে এখন clearly নাম দেওয়া workers-দের manager। প্রতিটা worker এখন আলাদা test করা যাবে। Shopping budget logic check করতে চাও? buyGroceries(["dal"], 50) call করো আর assert করো। কোনো বিয়ের আয়োজন লাগবে না।
নতুন structure টা একটা ছোট design diagram হিসেবে manager-and-workers shape-টা পরিষ্কার দেখাচ্ছে:
আর before-and-after পাশাপাশি:
Local variable-গুলো যদি এত জড়িয়ে যেত যে টুকরো করা সম্ভব হতো না, তাহলে আরেকটা tool ছিল reserve-এ: Replace Method with Method Object — পুরো method-কে তার নিজের ছোট class-এ রূপান্তর করে, যেখানে local-গুলো field হয়ে যায়। এখানে দরকার হয়নি, কিন্তু সত্যিই জটিল ক্ষেত্রে pocket-এ রাখো।
College corner: ফাতেমা যা করল সেটা Single Level of Abstraction Principle (SLAP)-এর প্রয়োগ: runHelperDay-এর ভেতরের প্রতিটা statement এখন একই উচ্চতায় আছে — "একটা পুরো কাজ করো" — আর details একটা level নিচে আছে। উচ্চ-স্তর আর নিম্ন-স্তর মিশিয়ে ফেলাটাই (যেমন high-level step-এর পাশে string-concatenation loop) long method পড়তে রসায়নের সমীকরণ দিয়ে থামানো recipe-র মতো মনে করায়।
এই smell-এর জীবনচক্র
তারিক design lecture শেষ করে আরেকটা ছবি দিয়ে। একটা method চিরকাল long বা short থাকে না — সে state-এর মধ্যে দিয়ে যায়, আর তোমার team-এর অভ্যাস ঠিক করে কোন দিকে যাবে:
সবচেয়ে সস্তা বের হওয়ার পথ হলো Growing থেকে Clean arrow — আগেই extract করো, যখন method শুধু গরম, এখনো আগুন ধরেনি। সবচেয়ে ব্যয়বহুল path হলো Monster আর Feared-এর মধ্যে loop — প্রতিটা forced edit পরেরটাকে আরো ভয়ংকর করে তোলে।
C#-এ একই smell
এই smell সব language-এ একই দেখতে। তারিক ফাতেমাকে তার নিজের office থেকে একটা ছোট C# example দেখায় — একটা school report card builder যেটা grade দেয়, comment লেখে, আর format করে একটা method-এ:
public string BuildReportCard(Student student)
{
if (student == null) throw new ArgumentNullException(nameof(student));
double total = 0;
foreach (var mark in student.Marks) total += mark.Score;
double average = total / student.Marks.Count;
string grade;
if (average >= 90) grade = "A";
else if (average >= 75) grade = "B";
else if (average >= 60) grade = "C";
else grade = "D";
string remark = grade == "A" ? "Excellent work!"
: grade == "B" ? "Very good, keep going."
: grade == "C" ? "Good, but practice more."
: "Needs attention.";
return $"{student.Name}\nAverage: {average:F1}\nGrade: {grade}\nRemark: {remark}";
}আর Extract Method-এর পর clean version:
public string BuildReportCard(Student student)
{
Validate(student);
var average = AverageScore(student.Marks);
var grade = GradeFor(average);
return Format(student.Name, average, grade, RemarkFor(grade));
}
private static double AverageScore(IReadOnlyList<Mark> marks)
=> marks.Sum(m => m.Score) / marks.Count;
private static string GradeFor(double average)
=> average >= 90 ? "A" : average >= 75 ? "B" : average >= 60 ? "C" : "D";এখন GradeFor এক লাইনে test করা যাচ্ছে, আর grading rule ঠিক একটা নামের জায়গায় আছে। পরের বছর school board grade সীমা বদলালে edit করতে হবে একটা tiny function — formatting code-এর মধ্যে চাপা কোনো paragraph না।
Real project-এ এই smell কোথায় লুকায়
Real codebase-এ Long Method-এর পছন্দের লুকানোর জায়গা আছে। এগুলো জানলে খুঁজে পাওয়া সহজ হয়:
- MVC web app-এর controller action। একটা
POST /orderhandler যেটা request validate করে, stock check করে, discount apply করে, payment নেয়, database-এ লেখে, আর email পাঠায়। Developer-রা এটাকে "controller bloat" বা "fat controller" বলে। - UI code-এ event handler।
onSaveButtonClickfunction যেটা পাঁচ বছরে বাড়তে বাড়তে এখন application-এর অর্ধেক business rule ধরে রেখেছে। - Legacy "process" job।
RunEndOfDay-এর মতো nightly batch script যেটা কেউ পুরোটা বোঝে না আর সবাই ভয় পায়। - Checkout আর billing flow। Tax rule, coupon rule, আর fraud check বছরের পর বছর যোগ হতে হতে checkout function কয়েক শো লাইন হয়ে যাওয়ার গল্প বারবার শোনা যায়।
- Fowler-এর বইয়ের প্রথম উদাহরণ। Martin Fowler Refactoring শুরু করেন একটা video rental shop-এর statement-printing function ছাড়িয়ে — দেখান যে একটা সহজ billing printout-ও Long Method-এ পচে যায় যদি কেউ extract না করে।
SonarQube, ESLint (complexity rules সহ), আর Visual Studio-র code metrics-এর মতো static analysis tools স্বয়ংক্রিয়ভাবে long বা complex method flag করতে পারে। তারা cyclomatic complexity measure করে — মোটামুটি বললে method-এর ভেতর দিয়ে কতটা আলাদা path যায়। Tools-গুলোকে বন্ধুত্বপূর্ণ reminder হিসেবে ব্যবহার করো, কড়া judge হিসেবে না।
কখন ignore করা যায়
সৎ engineer-রা স্বীকার করে: প্রতিটা long method খারাপ না। তারিক এ বিষয়ে দৃঢ় — refactoring-এরও cost আছে, আর ভালো engineer সেই cost খরচ করে যেখানে change হয়। একটা fair trade-off table:
| পরিস্থিতি | Smell ignore করবে? | কেন |
|---|---|---|
| Flat, সরল setup code যেটা ৪০টা config value assign করছে | হ্যাঁ | Branching নেই, shared state নেই — ভাগ করলে clarity না বেড়ে hops বাড়বে |
| Comment banner আর branching সহ method | না | Banner-ই প্রমাণ করছে author section দেখেছিলেন; extract করো |
| Auto-generated code (parser, migration) | হ্যাঁ | মানুষ এটা লাইনে লাইনে maintain করে না |
| Performance-critical code-এর hot inner loop, measure করার পর | কখনো কখনো | খুব কমই, ইচ্ছাকৃতভাবে inline রাখা হয় — কিন্তু আগে measure করো; compiler সাধারণত নিজেই inline করে |
| প্রতি sprint-এ সবাই edit করে এমন long method | না | High change frequency smell-কে সবচেয়ে costly করে — আগে fix করো |
| ৫ বছরে বদলায়নি এমন long method, test আছে | হয়তো | Refactoring-এরও cost আছে; যেখানে change হয় সেখানে খরচ করো |
সহজ নিয়ম: split করার সুবিধা সবচেয়ে বেশি যখন method branch করছে, shared variable বদলাচ্ছে, বা detail-এর level মিশিয়ে ফেলছে। লম্বা কিন্তু flat আর frozen method অপেক্ষা করতে পারে। চিত্র ৬ দেখো — quadrant chart এই table-টাই ছবি হিসেবে।
কোন refactoring কাজে লাগবে
| Refactoring | কখন ব্যবহার করবে |
|---|---|
| Extract Method | মূল চিকিৎসা, বেশিরভাগ ক্ষেত্রেই কাজে লাগে — প্রতিটা coherent block নামকরণ করা method-এ নাও |
| Replace Temp with Query | Temporary variable extraction আটকে দিচ্ছে; আগে সেগুলো ছোট নামের function দিয়ে replace করো |
| Decompose Conditional | জটিল if/else-ই লম্বা অংশ — condition আর branch-গুলোকে নিজস্ব নাম দাও |
| Replace Method with Method Object | Local-গুলো এত জড়িয়ে গেছে যে পিস পিস করে extract সম্ভব না — পুরো method-কে নিজস্ব class-এ নাও |
| Introduce Parameter Object | Extraction-এ দেখা যাচ্ছে একই group of value সব জায়গায় pass হচ্ছে |
পুরো smell এক পাতায়
Call শেষ হওয়ার আগে তারিক ফাতেমার notebook-এর জন্য একটা mind map এঁকে দেয় — পুরো lesson এক ছবিতে:
Quick revision box
+--------------------------------------------------------------+
| LONG METHOD - CHEAT SHEET |
+--------------------------------------------------------------+
| What : One method doing many jobs (the Ramu Kaka smell) |
| Family : Bloaters |
| Spot it : Scrolling, comment banners, deep nesting, |
| many locals, vague names with "and" |
| Costs : Hard to read, test, reuse; risky to change |
| Main fix : Extract Method (one job -> one named method) |
| Helpers : Decompose Conditional, Replace Temp with Query, |
| Replace Method with Method Object |
| Ignore : Flat, linear, rarely-changed sequences |
| Mantra : "Each comment banner is a method waiting |
| to be born." |
+--------------------------------------------------------------+Practice exercise
তোমার পালা! Call শেষ হওয়ার আগে তারিক ফাতেমাকে homework দেয়, আর সেই homework তোমারও। নিচের function-টা একটা school canteen-এর order handle করে। কাজ করছে, কিন্তু smell আছে। ভেতরে লুকানো আলাদা কাজগুলো খোঁজো, তারপর সেগুলো well-named function-এ extract করো যেন top-level function একটা গল্পের মতো পড়া যায়।
function handleCanteenOrder(items: string[], wallet: number): string {
if (items.length === 0) throw new Error("Empty order");
if (wallet < 0) throw new Error("Bad wallet");
// price the order
let total = 0;
for (const item of items) {
if (item === "samosa") total += 15;
else if (item === "juice") total += 25;
else total += 10;
}
// apply Friday discount
const day = new Date().getDay();
if (day === 5 && total > 50) {
total = total - total * 0.1;
}
// check wallet and make message
if (total > wallet) {
return "Sorry, you need Rs." + (total - wallet) + " more.";
}
let msg = "Order placed! Items: ";
for (const item of items) msg += item + " ";
msg += "| Total: Rs." + total;
return msg;
}তোমার কাজ:
- এই function কোন কোন আলাদা কাজ করছে সেগুলো list করো। (Hint: comment-গুলো সব স্বীকার করছে।)
- অন্তত তিনটা function extract করো: একটা pricing-এর জন্য, একটা discount rule-এর জন্য, একটা message বানানোর জন্য।
- Bonus: Friday discount-এ
new Date()function-এর ভেতরে লুকানো আছে। সেই day-টাকে parameter হিসেবে পাস করো — দেখো testing কতটা সহজ হয়ে যায়! - Extra challenge: চিত্র ৫-এর নিজস্ব version আঁকো — এই function-এ bug fix করার যাত্রা refactor-এর আগে আর পরে। কোন steps মিলিয়ে যায়?
তোমার top-level function যখন "validate, price, discount, respond" হিসেবে পড়া যাবে, তখন তুমি Long Method বুঝে গেছো — আর তারিকের মাথা নাড়ানো approval পেয়ে গেছো, ঠিক ফাতেমা যেটা পেয়েছিল। পরেরটা: পুরো একটা class এভাবে ফুলে উঠলে কী হয় — Large Class smell।
সচরাচর জিজ্ঞাসা
- কত লাইন হলে একটা method 'বেশি লম্বা' হয়?
- কোনো magic number নেই। কোনো team বলে ২০ লাইন, কেউ বলে ১০। আসল test হলো এটা: method কি একটার বেশি কাজ করছে? যদি '// এখন tax calculate করো' এরকম comment দিয়ে অংশ আলাদা করতে হয়, তাহলে সেটা বেশি লম্বা — ১৫ লাইনেও।
- Long method কি একটা bug?
- না। Code smell মানে bug না। Code ঠিকমতোই চলছে। Long method হলো একটা সতর্কসংকেত যে ভবিষ্যতে এই code বদলানো কঠিন, ধীরে আর ঝুঁকিপূর্ণ হবে। আমরা এটা fix করি আগামীকালের কাজ সহজ করতে, আজকের output ভুল বলে না।
- Long Method ঠিক করার সবচেয়ে ভালো refactoring কোনটা?
- Extract Method। Martin Fowler বলেন বেশিরভাগ ক্ষেত্রেই method ছোট করা মানে হলো একসাথে কাজ করা কিছু লাইন খুঁজে বের করা, সেগুলো নতুন method-এ নেওয়া, আর সেই method-কে একটা পরিষ্কার নাম দেওয়া।
- অনেক ছোট ছোট method রাখলে কি program slow হয়ে যাবে?
- প্রায় কখনোই না। Modern compiler আর runtime ছোট function call খুব ভালোভাবে optimize করে — প্রায়ই নিজে থেকেই inline করে দেয়। যদি কোনো cost থাকেও, সেটা ৩০০ লাইনের একটা method ভুল বোঝার cost-এর তুলনায় কিছুই না।
- কখন method লম্বা রাখা ঠিক আছে?
- যখন method টা সোজা একটা list of steps — কোনো branching নেই, কোনো shared variable নেই। যেমন একটা setup method যেটা পর পর চল্লিশটা setting assign করছে। এই ধরনের straight-line code ভেঙে দিলে উল্টো পড়তে কষ্ট হয়।
আরো দেখো
সম্পর্কিত পাঠ
Large Class: যে স্কুলের ব্যাগে সব কিছু থাকে
Large Class code smell কী সেটা বুঝো — কেন god class বড় হয়, low cohesion কীভাবে চেনা যায়, আর Extract Class দিয়ে কীভাবে ছোট ছোট focused class-এ ভাগ করা যায়।
Long Parameter List: দশটা নির্দেশনার চায়ের অর্ডার
Long Parameter List কোড স্মেল সহজ ভাষায় — কেন বেশি argument-এর method বাগ তৈরি করে, আর কীভাবে parameter object দিয়ে call ছোট, পরিষ্কার আর নিরাপদ করা যায়।
Data Clumps: যে বন্ধুরা সবসময় একসাথে ঘোরে
শিক্ষার্থীদের জন্য Data Clumps code smell — শেখো কীভাবে সবসময় একসাথে চলা value-এর গ্রুপ চেনা যায় আর সেগুলোকে একটা class-এ bundle করা যায়, ঠিক যেমন একটা student ID card।
Duplicate Code: ৫০টা বিয়ের কার্ডে হাতে লেখা একই ঠিকানা
বিয়ের কার্ডের গল্প দিয়ে Duplicate Code smell বোঝো। DRY, Rule of Three, আর Extract Method দিয়ে copy-paste কোডের বিপদ থেকে বাঁচো।