Large Class: যে স্কুলের ব্যাগে সব কিছু থাকে
Large Class code smell কী সেটা বুঝো — কেন god class বড় হয়, low cohesion কীভাবে চেনা যায়, আর Extract Class দিয়ে কীভাবে ছোট ছোট focused class-এ ভাগ করা যায়।
🎯 যে স্কুলের ব্যাগ ছাত্রের চেয়ে ভারী
ধরো সুমাইয়ার কথা — সে ঢাকার একটা স্কুলে সপ্তম শ্রেণীতে পড়ে। তার স্কুলের ব্যাগ পুরো পাড়ায় বিখ্যাত, কারণ সেটা সব কিছু বহন করে।
চৌদ্দটা পাঠ্যবই, এমনকি যেদিন মাত্র চারটা class সেদিনও। প্রতিটা বিষয়ের জন্য তিনটা করে notebook। টিফিনবক্স, পানির বোতল, বিস্কুটের প্যাকেট। মঙ্গলবারের sports class-এর জন্য স্কেটস — সারা সপ্তাহ ভেতরে থাকে, "যদি লাগে" বলে। রং-তুলির বাক্স। গত বছরের পরীক্ষার কাগজ। দাদির দেওয়া অতিরিক্ত ছাতাটাও।
প্রতি সকালে সুমাইয়া ব্যাগ তুলতে গিয়ে কষ্ট পায়। স্যার বললেন "জ্যামিতি বক্স বের করো" — সুমাইয়া পাঁচ মিনিট খোঁজে। একবার পানির বোতল থেকে পানি পড়ে গণিতের homework নষ্ট হয়ে গেল। কাজিন "শুধু বিজ্ঞান বইটা" নিতে এলে অর্ধেক ব্যাগ খালি করতে হলো।
শেষে তার মা রাহেলা বুদ্ধিমান উপায়ে সমস্যা ঠিক করলেন: রুটিন অনুযায়ী গোছানো একটা ছোট দৈনিক ব্যাগ, একটা আলাদা টিফিন ব্যাগ, আর একটা sports kit যেটা শুধু মঙ্গলবার যায়। এখন প্রতিটা ব্যাগ হালকা, সব জিনিস খুঁজে পাওয়া যায়, আর এক ব্যাগে পানি পড়লে অন্য ব্যাগ নষ্ট হয় না।
এখানে গল্পটা একটু মোড় নেয়। সুমাইয়ার বড় বোন নাসরিন CSE-তে দ্বিতীয় বর্ষে পড়ে। একটা college hackathon-এ নাসরিন আর তার teammate তারিক মিলে একটা "Smart School" app বানাচ্ছে। নাসরিন পারিবারিক মজার কথা মাথায় রেখে তার main class-এর নাম দিল SchoolBag। ছয় সপ্তাহ পরে মজাটা বাস্তব হয়ে গেছে। App-এর SchoolBag class ঠিক সুমাইয়ার ব্যাগের মতো ফুলে উঠেছে: বই, টিফিন, sports, library fine — সব একটা class-এ। তারিক একটা feature যোগ করতে গেলে নাসরিনের code-এ হোঁচট খায়। তাদের স্যার, ড. রহমান, একবার দেখেই review sheet-এ দুটো কথা লিখলেন: "Large Class।"
কোডে, Large Class হলো সুমাইয়ার পুরনো স্কুলের ব্যাগ। একটা class-এ সব field আর method গুঁজে দেওয়া যেগুলো "সম্পর্কিত" মনে হয়েছিল। কাজ করে — ব্যাগটা জিনিস বহন করে — কিন্তু প্রতিটা খোঁজ ধীর, প্রতিটা পরিবর্তন অসম্পর্কিত কিছু নষ্ট করার ঝুঁকি নিয়ে আসে। এই lesson-এ নাসরিন আর তারিককে follow করব যখন তারা smell-টা চিনতে, মাপতে, আর ব্যাগটা ভাগ করতে শিখছে।
💡 এই smell-টা কী?
আগে একটা কথা মনে করিয়ে দিই: code smell কোনো bug নয়। Program চলে, সঠিক উত্তর দেয়। Smell হলো একটা কাঠামোগত সতর্কতা — যেন একটা ক্যাঁচকাঁচে তাক যেটা এখনো বই ধরে রাখছে। Large Class হলো Martin Fowler-এর Refactoring বই-এর "Bloater" smell-গুলোর একটা: code এতটাই ফুলে উঠেছে যে সামলানো কঠিন।
Large Class হলো এমন একটা class যেখানে অনেক বেশি field, অনেক বেশি method, আর — সবচেয়ে গুরুত্বপূর্ণ — অনেক বেশি দায়িত্ব জমা হয়েছে। এটা আর একটা "জিনিস" নয়। এটা পুরো একটা বিভাগ হয়ে গেছে একটা নামের মধ্যে চেপে।
চরম রূপের একটা ডাকনাম আছে industry-তে: god object — একটা class যেটা অনেক বেশি জানে আর অনেক বেশি করে, আর program-এর বাকি সব কিছু যার উপর নির্ভর করে। Legacy system-এ এগুলো বিখ্যাত: হাজার হাজার লাইনের একটা Manager বা Context class যেটা সব developer ভয় পায় আর প্রতিটা feature ছুঁয়ে যায়।
এই smell-এর আসল পরিমাপ হলো line count নয়, cohesion — class-এর অংশগুলো কি আসলেই একসাথে থাকার কথা? ৭০০ লাইনের একটা class যেখানে সব কিছু একটাই উদ্দেশ্যে কাজ করে, সেটা ২০০ লাইনের একটা class-এর চেয়ে সুস্থ হতে পারে যেটা তিনটা অসম্পর্কিত কাজ লুকিয়ে রেখেছে।
এই smell-টা Long Method-এর বড় ভাই। Long method একটা function-এ অনেক কাজ করে; Large class একটা class-এ অনেক কাজ করে — আর সাধারণত এটা long method-এও ভরা থাকে। Bloater-রা দলে আসে। ড. রহমান নাসরিনের SchoolBag খুলে এক মিনিটের মধ্যে তিনটা long method খুঁজে পেলেন।
কলেজ কর্নার: "এক class, এক কাজ"-এর formal নাম হলো Single Responsibility Principle (SRP) — Robert C. Martin এটা define করেছেন: "একটা class পরিবর্তনের শুধু একটাই কারণ থাকা উচিত।" Cohesion মাপা হয় LCOM (Lack of Cohesion of Methods) দিয়ে: মোটামুটি, এমন method pair গণনা করো যারা কোনো field share করে না। উচ্চ LCOM মানে class-টা গণিতগতভাবে "অপরিচিতদের হোস্টেল।"
👃 কীভাবে চেনা যায়
ড. রহমান নাসরিন আর তারিককে একটা checklist দিলেন। তোমার project-এর যেকোনো class খুলে এটা চালাও:
- ফাইলটা শত শত বা হাজার হাজার লাইন লম্বা; পড়ে navigate করা যায় না, Ctrl+F দিয়ে খুঁজতে হয়।
- Class-এ অনেক field আছে, আর প্রতিটা method সেগুলোর মধ্যে শুধু একটা ছোট, আলাদা subset ছোঁয়।
- Class-এর নাম একটা অস্পষ্ট bucket:
Manager,Service,Helper,Util,Engine,Context,Processor। - Field-এর নামে prefix আছে যেটা এলাকা চিহ্নিত করে:
tiffinBox,tiffinFresh,sportsShoes,sportsDay— এই prefix-গুলো আসলে class-এর নাম, বেরিয়ে আসতে চাইছে। - অসম্পর্কিত কারণে ফাইলটা পরিবর্তন হয়: একটা UI tweak, একটা pricing rule, আর একটা logging fix — তিনটোই এই একটা class-এ আসে।
- Class-এর test file-টা বিশাল, কষ্টকর setup সহ, কারণ object বানাতে গেলে সব concern টেনে আনতে হয়।
- এখানে merge conflict লেগেই থাকে, কারণ team-এর সবাই এই একটা file edit করে।
| লক্ষণ | কী বলছে |
|---|---|
| Method-গুলো field-এর আলাদা আলাদা group ব্যবহার করে | একটার ভেতরে কয়েকটা class লুকিয়ে আছে — low cohesion |
Manager বা Helper-এর মতো নাম | Class-এর কোনো একটা ধারণা নেই, তাই একটা bucket নাম পেয়েছে |
Prefixed field নাম (billingX, shippingY) | Author ইতোমধ্যে concept-গুলো অনানুষ্ঠানিকভাবে group করেছেন — group-গুলোকে real class করো |
| প্রতিটা feature এই file ছোঁয় | Class-টা god object আর team-এর bottleneck হয়ে গেছে |
| বিশাল test setup | Object বানাতে তার সব অসম্পর্কিত machinery বানাতে হয় |
| Class-এর ভেতরে একই logic দুবার আছে | Class এত বড় যে লেখক নিজেই ভুলে গেছেন কী কী আছে |
নাসরিন checklist চালিয়ে পাঁচটা box-এ টিক দিল। তারিক ষষ্ঠটায় টিক দিল — সে চুপচাপ একটা date-formatting helper আবার লিখে ফেলেছিল যেটা ইতোমধ্যে ৩০০ লাইন উপরে ছিল, কারণ সে এতদূর scroll করেনি।
⚠️ কেন এটা সমস্যা
কেন care করব, যদি ব্যাগটা তখনো বই বহন করে?
- Cognitive overload। কেউ ২,০০০ লাইনের class মাথায় রাখতে পারে না। তাই পরিবর্তন হয় আংশিক বোঝাপড়া নিয়ে, আর আংশিক বোঝাপড়া side effect তৈরি করে।
- Low cohesion, high coupling। অসম্পর্কিত field-এ কাজ করা method-গুলো একটা namespace share করে আর একে অপরকে disturb করতে পারে। এদিকে class-এর প্রতিটা ব্যবহারকারী আসলে যতটুকু দরকার তার চেয়ে অনেক বেশির উপর নির্ভর করে — যেন গণিত পরীক্ষায় স্কেটস নিয়ে যাওয়া।
- Team traffic jam। একটা hot file যেটা সবাই edit করে মানে অবিরাম merge conflict। Class-টা parallel কাজের bottleneck হয়ে যায়, যেন একটা school-এর একটাই counter সব ভর্তি সামলাচ্ছে। নাসরিন আর তারিক তৃতীয় সপ্তাহেই এটায় ধাক্কা খেল: প্রতিটা pull request অন্যজনেরটার সাথে conflict করল।
- লুকানো duplication। এত বড় surface area-তে, একই helper logic একই class-এর দুটো কোণায় আবার লেখা হয়, কেউ লক্ষ্য করে না। তারিকের date helper হলো জীবন্ত প্রমাণ।
- Untestable unit। Sports kit আর library record বানানো ছাড়া tiffin freshness logic test করা যায় না। Test ভারী, ধীর, বিরল হয়ে পড়ে — আর বিরল test মানে লুকানো bug।
আর সব bloater-এর মতো, এটা নীরবে বাড়ে। Class-গুলো এক ধরনের মাধ্যাকর্ষণ মেনে চলে: একটা class যত বেশি জানে, পরের feature সেখানে যোগ করা তত সহজ, কারণ সব data সুবিধামতো scope-এ আছে। এটাকে বলে rich-get-richer dynamics — যত connected class, তত বেশি দায়িত্ব আসে।
ড. রহমান যখন নাসরিনকে তার field-গুলো এলাকা অনুযায়ী গণনা করতে বললেন, ফলাফল তাকে লজ্জা দিল। SchoolBag নামের class-টা আসলে ব্যাগ নিয়ে প্রায় কিছুই করে না:
তারপর তিনি team cost দেখালেন। Class যত বাড়ে, merge conflict line count-এর চেয়ে অনেক দ্রুত বাড়ে — কারণ বেশি মানুষ বেশি বার একই file চায়:
কলেজ কর্নার: এটা coupling theory। একটা god object-এর উচ্চ afferent coupling থাকে (অনেক class এর উপর নির্ভর করে) আর উচ্চ efferent coupling থাকে (এটা অনেক class-এর উপর নির্ভর করে), তাই দুই দিকের প্রতিটি পরিবর্তনের blast radius-এ এটা থাকে। SRP cohesion ঠিক করে; dependency inversion আর ছোট interface coupling ঠিক করে। দুটো সমস্যা প্রায় সবসময় একসাথে আসে।
🧪 God class-এর জীবনে এক সপ্তাহ
ব্যাপারটা concrete করতে দেখো তারিক একটা ছোট feature যোগ করতে যাচ্ছে — "library বই ফেরত দেওয়ার জন্য students-দের remind করো" — ফুলে-ওঠা SchoolBag-এ কী হয়:
একটা feature, চারটা এলাকা বিঘ্নিত, একটা merge conflict, একটা অসম্পর্কিত এলাকায় ভাঙা test। এখন একই সপ্তাহটাকে emotional journey হিসেবে দেখো:
"লক্ষ্য করো," ড. রহমান বললেন, "আসল feature-টায় বিশ মিনিট লেগেছে। ব্যাগটার জন্য দুই দিন গেছে।"
📊 কোন class-গুলো আগে ভাগ করতে হবে?
এই sprint-এ সব বড় class ভাগ করা যাবে না, করা উচিতও নয়। ড. রহমান long method-এর মতো একই দুটো প্রশ্নের কৌশল শেখালেন: class-টা কতটা incohesive, আর কত বার পরিবর্তন হয়? তোমার class-গুলো plot করো, অগ্রাধিকার নিজেই দেখা যাবে:
SchoolBag গভীরভাবে "Refactor now"-এ বসে আছে: চারটা কাজ মেশায় আর দুজন teammate প্রতি সপ্তাহে edit করে। পুরনো report generator ঠিক ততটাই এলোমেলো কিন্তু frozen — এটা অপেক্ষা করতে পারে।
🧪 একটা বাস্তব code উদাহরণ
নাসরিনের আসল class-টা দেখা যাক। App শুরু হয়েছিল একটা সাধারণ SchoolBag দিয়ে। দুটো term (ঠিক আছে, ছয় hackathon সপ্তাহ) পরে এটা এরকম দেখাচ্ছে:
class SchoolBag {
// books territory
books: string[] = [];
notebooks: string[] = [];
// tiffin territory
tiffinDish: string = "";
tiffinPackedAt: Date = new Date();
waterBottleFull: boolean = true;
// sports territory
sportsShoes: boolean = false;
skates: boolean = false;
sportsDay: string = "Tuesday";
// library territory
borrowedBook: string = "";
dueDate: Date = new Date();
packForDay(timetable: string[]): void {
this.books = timetable.map((subject) => subject + " textbook");
this.notebooks = timetable.map((subject) => subject + " notebook");
}
isTiffinFresh(now: Date): boolean {
const hours = (now.getTime() - this.tiffinPackedAt.getTime()) / 3600000;
return hours < 6;
}
refillWater(): void {
this.waterBottleFull = true;
}
needsSportsKit(day: string): boolean {
return day === this.sportsDay;
}
packSportsKit(day: string): void {
if (this.needsSportsKit(day)) {
this.sportsShoes = true;
this.skates = true;
}
}
isLibraryBookOverdue(today: Date): boolean {
return this.borrowedBook !== "" && today > this.dueDate;
}
lateFine(today: Date): number {
if (!this.isLibraryBookOverdue(today)) return 0;
const daysLate = Math.ceil(
(today.getTime() - this.dueDate.getTime()) / 86400000,
);
return daysLate * 2; // Rs. 2 per day
}
totalWeightKg(): number {
let weight = this.books.length * 0.5 + this.notebooks.length * 0.2;
if (this.sportsShoes) weight += 0.7;
if (this.skates) weight += 1.5;
if (this.borrowedBook !== "") weight += 0.4;
return weight;
}
}থামো আর পরীক্ষা করো, ঠিক যেভাবে ড. রহমান নাসরিনকে করিয়েছিলেন। চারটা এলাকা দেখছো? Books, tiffin, sports, library। Comment-গুলো স্বীকার করছে, field prefix-গুলো স্বীকার করছে, method-গুলোও স্বীকার করছে: isTiffinFresh শুধু tiffin field ছোঁয়; lateFine শুধু library field ছোঁয়। Cluster-গুলো কখনো একে অপরের সাথে কথা বলে না — শুধু একটা ছাদ share করে।
এটা ব্যবহারিকভাবে কেন কষ্ট দেয়?
lateFine-এ একটা bug মানে tiffin আর skates-এ ভরা একটা বিশাল file খুলে আবার বুঝতে হবে।- Tiffin freshness unit test করতে sports আর library-র সব বোঝা নিয়ে একটা পুরো
SchoolBagবানাতে হবে। - School app-এর library module যখন due-date logic চায়, পুরো bag import না করে reuse করতে পারবে না।
- দুজন teammate tiffin আর library feature edit করতে এই একটা file-এ ধাক্কা খাবে — ঠিক নাসরিন আর তারিকের তৃতীয় সপ্তাহের মতো।
যখন একটা class-এর method-গুলো এমন দল তৈরি করে যারা কখনো field share করে না, class-টা এক জিনিস নয় — এটা অপরিচিতদের হোস্টেল। প্রতিটা দলের নিজের ঘর দরকার।
🛠️ ধাপে ধাপে পরিষ্কার করা
সমাধান হলো Extract Class: field আর method-এর একটা cohesive cluster বেছে নাও, সেগুলো একটা নতুন class-এ নিয়ে যাও, আর আসল class-টাকে একটা reference রাখতে দাও। নাসরিন আর তারিক কাজটা ভাগ করে নিল — আর সপ্তাহের পর সপ্তাহ পরে, প্রথমবারের মতো conflict ছাড়া parallel কাজ করতে পারল।
ধাপ ১: সবচেয়ে স্বাধীন cluster আগে extract করো — library record।
class LibraryLoan {
constructor(
readonly bookTitle: string,
readonly dueDate: Date,
) {}
isOverdue(today: Date): boolean {
return today > this.dueDate;
}
lateFine(today: Date): number {
if (!this.isOverdue(today)) return 0;
const daysLate = Math.ceil(
(today.getTime() - this.dueDate.getTime()) / 86400000,
);
return daysLate * 2;
}
weightKg(): number {
return 0.4;
}
}Fine-এর নিয়ম এখন due date যেখানে সেখানে থাকে। Library module LibraryLoan সরাসরি reuse করতে পারে — কোনো school bag দরকার নেই। একটা bonus লক্ষ্য করো: পুরনো code-এ isLibraryBookOverdue-কে borrowedBook !== "" check করতে হতো, কারণ bag-এ হয়তো বই নাও থাকতে পারে। নতুন design-এ একটা LibraryLoan object শুধু তখনই থাকে যখন একটা বই ধার নেওয়া হয়। "empty string" bug-এর একটা পুরো শ্রেণী অদৃশ্য হয়ে যায়।
ধাপ ২: Tiffin আর sports kit cluster-গুলো extract করো।
class Tiffin {
constructor(
readonly dish: string,
readonly packedAt: Date,
) {}
isFresh(now: Date): boolean {
const hours = (now.getTime() - this.packedAt.getTime()) / 3600000;
return hours < 6;
}
}
class SportsKit {
constructor(
readonly items: { name: string; weightKg: number }[],
readonly sportsDay: string,
) {}
isNeededOn(day: string): boolean {
return day === this.sportsDay;
}
weightKg(): number {
return this.items.reduce((sum, item) => sum + item.weightKg, 0);
}
}ধাপ ৩: ব্যাগটা একটা হালকা coordinator হয়ে যায়।
class SchoolBag {
books: string[] = [];
notebooks: string[] = [];
tiffin?: Tiffin;
sportsKit?: SportsKit;
loan?: LibraryLoan;
packForDay(timetable: string[], day: string): void {
this.books = timetable.map((s) => s + " textbook");
this.notebooks = timetable.map((s) => s + " notebook");
if (this.sportsKit && !this.sportsKit.isNeededOn(day)) {
this.sportsKit = undefined; // leave it at home!
}
}
totalWeightKg(): number {
return (
this.books.length * 0.5 +
this.notebooks.length * 0.2 +
(this.sportsKit?.weightKg() ?? 0) +
(this.loan?.weightKg() ?? 0)
);
}
}দেখো team কী পেল। SchoolBag এখন শুধু ব্যাগের কাজ করে: কী pack করতে হবে সেটা ঠিক করা আর ওজন মাপা। Tiffin freshness, sports schedule, আর library fine প্রতিটা ছোট, named, স্বাধীনভাবে testable class-এ থাকে। আর একটা বোনাস design improvement এসেছে বিনামূল্যে: ব্যাগ এখন আসলে স্কেটস বাড়িতে রেখে যেতে পারে — একটা বিশাল class-এ সব boolean field থাকলে এটা model করা অদ্ভুত ছিল।
এখানে নতুন design-এর class diagram — নাসরিন অবশেষে team-এর দেওয়ালে যে ছবিটা লাগাল:
আর এক নজরে আগে-পরে:
এই kit-এ আরও দুটো tool আছে। যদি একটা class বড় হয় কারণ কিছু feature শুধু কিছু object-এ প্রযোজ্য (ধরো, একটা Vehicle class-এ শুধু truck-এর জন্য field), তাহলে Extract Subclass ব্যবহার করো। যদি client-গুলো একটা বিশাল class-এর উপর নির্ভর করে কিন্তু প্রতিটা client শুধু একটা অংশ ব্যবহার করে, তাহলে Extract Interface ব্যবহার করো — প্রতিটা client-কে একটা ছোট, সৎ contract দাও।
কলেজ কর্নার: Extract Interface আসলে Interface Segregation Principle-এর প্রয়োগ — কোনো client-কে এমন method-এর উপর নির্ভর করতে বাধ্য করা উচিত নয় যেগুলো সে ব্যবহার করে না। Layered system-এ তুমি প্রায়ই দুটোই করো: implementation-এর cohesion ঠিক করতে Extract Class, তারপর প্রতিটা consumer যেন শুধু নিজের অংশ দেখে সেজন্য Extract Interface। একসাথে এগুলো একটা বিশাল dependency-কে কয়েকটা সংকীর্ণ, replaceable dependency-তে রূপান্তর করে — যেটাই unit test-এ mocking সম্ভব করে।
🔄 এই smell-এর জীবনচক্র
সুমাইয়ার ব্যাগের মতো, একটা class ভারী জন্মায় না। এটা states-এর মধ্য দিয়ে যায়, আর team-এর অভ্যাস দিক ঠিক করে:
সবচেয়ে সস্তা তীর হলো Growing থেকে Clean — প্রথম দৃশ্যমান seam-এ ভাগ করো, যখন extraction একটা বিকেলে হয়ে যায়। GodObject state আঠালো: ততদিনে এত caller class-এর উপর নির্ভর করে যে প্রতিটা extraction পুরো team জুড়ে coordination দরকার।
🧰 C#-এ একই smell
ড. রহমান class-এ দেখানো sample code থেকে আরেকটা উদাহরণ। এই hostel warden class "হোস্টেল manage করার লোক" হিসেবে শুরু হয়েছিল আর প্রতিটা hostel-সম্পর্কিত কাজ absorb করেছিল:
public class HostelWarden
{
public List<string> Rooms = new();
public Dictionary<string, string> RoomAllotments = new();
public List<string> MessMenu = new();
public decimal MessBudget;
public List<string> Complaints = new();
public void AllotRoom(string student, string room) =>
RoomAllotments[student] = room;
public bool IsRoomFree(string room) =>
!RoomAllotments.ContainsValue(room);
public void PlanWeeklyMenu(List<string> dishes) => MessMenu = dishes;
public decimal CostPerStudent(int students) => MessBudget / students;
public void FileComplaint(string text) => Complaints.Add(text);
public int PendingComplaints() => Complaints.Count;
}এক ঘরে তিনজন অপরিচিত: room allotment, mess planning, complaints। Extract Class-এর পরে:
public class RoomRegister
{
private readonly Dictionary<string, string> _allotments = new();
public void Allot(string student, string room) => _allotments[student] = room;
public bool IsFree(string room) => !_allotments.ContainsValue(room);
}
public class MessPlan
{
public List<string> Menu { get; private set; } = new();
public decimal Budget { get; set; }
public void PlanWeek(List<string> dishes) => Menu = dishes;
public decimal CostPerStudent(int students) => Budget / students;
}
public class ComplaintBook
{
private readonly List<string> _complaints = new();
public void File(string text) => _complaints.Add(text);
public int PendingCount => _complaints.Count;
}
public class HostelWarden
{
public RoomRegister Rooms { get; } = new();
public MessPlan Mess { get; } = new();
public ComplaintBook Complaints { get; } = new();
}Warden এখনো আছে — কিন্তু এখন তিনটা focused helper-এর coordinator হিসেবে, ঠিক রাহেলার তিনটা ছোট ব্যাগের মতো। প্রতিটা helper তিন লাইনের test দিয়ে পরীক্ষা করা যায়, আর mess team MessPlan বদলাতে room-allotment code কখনো ছুঁতে হয় না।
🔍 বাস্তব project-এ এই smell কোথায় লুকায়
Real software-এ Large Class-এর চেনা আশ্রয়স্থল:
- Legacy god class। পুরনো enterprise system-এ প্রায়ই একটা
ApplicationManager,SystemContext, বাMainFormথাকে কয়েক হাজার লাইনের, যার মধ্য দিয়ে প্রতিটা feature যায়। god object-এর Wikipedia entry ঠিক এই anti-pattern বর্ণনা করে: একটা object যেটা অনেক অসম্পর্কিত type reference করে আর অনেক বেশি জানে। - Service absorb করা entity। একটা
CustomerবাUserclass যেটা data হিসেবে শুরু হয়েছিল আর ধীরে ধীরে email পাঠানো, password hashing, invoice formatting, আর report generation পেয়েছে — প্রতিটা সংযোজন "customer-এর সাথে সম্পর্কিত," কোনোটাই customer হওয়া নয়। - Utility dumping ground।
Utils,Helpers, আরCommonclass যেখানে গৃহহীন function জমা হয়। নামটাই স্বীকার করছে যে সেখানে কোনো একটা ধারণা নেই। - MVC app-এ fat controller আর fat model। Web framework controller (বা model)-কে logic রাখার সবচেয়ে সহজ জায়গা করে তোলে, তাই checkout controller আর
Usermodel ফুলে ওঠে। Community এর বিরুদ্ধে slogan তৈরি করেছে ("skinny controllers")। - God constants file। একটা single class-এ শত শত অসম্পর্কিত constant — Maximiliano Contieri যেটাকে নিজস্ব smell হিসেবে বর্ণনা করেছেন — এটা একই রোগ ছোট আকারে।
অনেক static analyser এই smell flag করে। SonarQube class line আর complexity গণনা করে; LCOM (Lack of Cohesion of Methods)-এর মতো metric সরাসরি মাপে method-গুলো field share করে কিনা। উচ্চ LCOM score হলো "অপরিচিতরা এক ঘরে থাকছে"-এর গণিতসম্মত সংস্করণ।
🤔 কখন উপেক্ষা করা ঠিক আছে
| পরিস্থিতি | smell উপেক্ষা করব? | কেন |
|---|---|---|
| বড় class, কিন্তু প্রতিটা method একই data-এ একটাই উদ্দেশ্যে কাজ করে | হ্যাঁ | Low cohesion ছাড়া size রোগ নয়; ভাগ করলে একটা ধারণা ছড়িয়ে যাবে |
| Generated code (ORM model, API client) | হ্যাঁ | Machine এটা maintain করে; মানুষ খুব কমই পড়ে |
| Framework base class যা ইচ্ছাকৃতভাবে broad | সাধারণত হ্যাঁ | কিছু framework ইচ্ছাকৃতভাবে wide surface area দেয় |
| স্পষ্ট field cluster আর method camp সহ class | না | Seam-গুলো দৃশ্যমান — সেগুলো বরাবর extract করো |
| God class যেটা প্রতি সপ্তাহে সব team member edit করে | না | সর্বোচ্চ change traffic মানে ভাগ করার সর্বোচ্চ লাভ |
| ভাগ করলে স্পষ্ট নাম ছাড়া class হবে | অপেক্ষা করো | অংশগুলোর নাম দিতে না পারলে আসল seam এখনো খোঁজোনি — arbitrary লাইনে কাটাকাটি করো না |
সহজ কথায়: শুধুমাত্র একটা আসল seam বরাবর ভাগ করো — এমন field-এর subset যেগুলো method-এর একটা subset মিলে ব্যবহার করে। একটা সুস্থ class random টুকরোয় কাটলে কয়েকটা anemic class আর নতুন plumbing তৈরি হয়, যেটা একটা বড় ব্যাগের চেয়ে খারাপ। রাহেলা সুমাইয়াকে চৌদ্দটা ব্যাগ দেননি, এক বই এক ব্যাগ করে — তিনি তিনটা করেছিলেন, মেয়ের দিনের আসল seam বরাবর।
💊 কোন refactoring-গুলো এটা সারায়
| Refactoring | কখন ব্যবহার করব |
|---|---|
| Extract Class | মূল সমাধান — field আর method-এর cohesive cluster একটা নতুন collaborating class-এ নিয়ে যাও |
| Extract Subclass | কিছু feature শুধু নির্দিষ্ট instance-এ ব্যবহৃত হয় — সেগুলো subclass-এ নামিয়ে দাও |
| Extract Interface | Client-গুলোর শুধু class-এর একটা অংশ দরকার — প্রতিটা client-কে একটা ছোট contract দাও |
| Extract Method | Large class-গুলো long method-এ ভরা; আগে সেগুলো ছোট করলে প্রায়ই class seam বেরিয়ে আসে |
| Replace Data Value with Object | বিশাল class-এর loose primitive field-গুলো proper value object হয় যেগুলো বের করে নেওয়া যায় |
🧠 এক পাতায় পুরো smell-টা
নাসরিনের final revision sketch, সরাসরি তার hackathon notebook থেকে:
📦 দ্রুত revision box
+----------------------------------------------------------------+
| LARGE CLASS - CHEAT SHEET |
+----------------------------------------------------------------+
| What : One class stuffed with many jobs (the school bag) |
| Family : Bloaters (big sibling of Long Method) |
| Nickname : God object, when extreme |
| Spot it : Many fields, method camps, name prefixes, |
| bucket names like Manager/Helper/Util |
| Costs : Overload, coupling, merge conflicts, heavy tests |
| Main fix : Extract Class along real seams |
| Helpers : Extract Subclass, Extract Interface |
| Ignore : Big but cohesive classes; generated code |
| Mantra : "Low cohesion is the disease; size is only |
| the symptom." |
+----------------------------------------------------------------+✍️ অনুশীলন exercise
ড. রহমানের নাসরিন আর তারিকের জন্য — আর তোমার জন্য — homework। এখানে একটা cricket club app-এর একটা ছোট কিন্তু smelly class দেওয়া আছে। এটা ঠিকমতো কাজ করে — কিন্তু ভেতরে কমপক্ষে তিনটা আলাদা "ব্যাগ" গুঁজে দেওয়া আছে। Seam-গুলো খোঁজো আর class-গুলো extract করো।
class CricketClub {
// members
memberNames: string[] = [];
memberFeesPaid: boolean[] = [];
// ground booking
groundBookings: { date: string; team: string }[] = [];
// kit inventory
bats: number = 10;
balls: number = 24;
addMember(name: string): void {
this.memberNames.push(name);
this.memberFeesPaid.push(false);
}
payFee(name: string): void {
const i = this.memberNames.indexOf(name);
if (i >= 0) this.memberFeesPaid[i] = true;
}
bookGround(date: string, team: string): boolean {
if (this.groundBookings.some((b) => b.date === date)) return false;
this.groundBookings.push({ date, team });
return true;
}
lendKit(batCount: number, ballCount: number): boolean {
if (batCount > this.bats || ballCount > this.balls) return false;
this.bats -= batCount;
this.balls -= ballCount;
return true;
}
}তোমার কাজ:
- Field আর method-এর তিনটা cluster চিহ্নিত করো।
bookGroundকোন field ছোঁয়?payFeeকোন field ছোঁয়? সেগুলো কি overlap করে? - একটা
MemberRegister, একটাGroundSchedule, আর একটাKitStoreclass extract করো।CricketClub-কে প্রতিটার একটা করে রাখতে দাও। - কোনো code লেখার আগে নিজে চিত্র ৭-এর মতো একটা class diagram আঁকো — তোমার refactored design-এর। আগে diagram, পরে code।
- Bonus: parallel array
memberNamesআরmemberFeesPaidভঙ্গুর (সেগুলো sync-এর বাইরে চলে গেলে কী হবে?)। সেগুলো একটাMemberobject দিয়ে replace করো যেটায় নাম আর fee status থাকে — আমাদের পরের lesson Primitive Obsession-এর একটু আগাম স্বাদ।
তোমার solution-এর প্রতিটা class যদি এক বাক্যে ব্যাখ্যা করা যায়, তুমি school-bag smell জয় করেছো — আর নাসরিন আর তারিকের মতো, অবশেষে একে অপরের উপর পা না দিয়ে একই project-এ কাজ করতে পারবে।
সচরাচর জিজ্ঞাসা
- প্রতিটা বড় class কি Large Class smell?
- না। শুধু সাইজ দিয়ে রোগ বোঝা যায় না — আসল সমস্যা হলো low cohesion। একটা class বড় হলেও সুস্থ থাকতে পারে, যদি তার সব method একই data নিয়ে একটাই কাজ করে। smell তখন আসে যখন class-এর ভেতরে field আর method-এর আলাদা দল একে অপরকে দেখেই না — যেন অপরিচিত মানুষ এক ঘরে থাকছে।
- 'god object' কী জিনিস?
- God object হলো Large Class-এর চরম রূপ — একটা class যেটা অনেক বেশি জানে আর অনেক বেশি করে, অনেক অসম্পর্কিত type ব্যবহার করে। এটা পুরো program-এর কেন্দ্র হয়ে যায়, সবাই এর উপর নির্ভর করে — ফলে নিরাপদে কিছু বদলানো প্রায় অসম্ভব।
- একটা class পাঁচটায় ভাগ করলে project জটিল হয়ে যাবে না?
- সাধারণত উল্টোটাই হয়। পাঁচটা ছোট class, যাদের সৎ নাম আছে, একটা জট পাকানো দানবের চেয়ে শেখা অনেক সহজ — কারণ প্রতিটাকে আলাদা করে বোঝা যায়। বিভ্রান্তি আসে জট পাকানো ধারণা থেকে, file-এর সংখ্যা থেকে নয়।
- class কোথায় ভাগ করতে হবে সেই 'seam' কীভাবে খুঁজব?
- এমন field-এর subset খোঁজো যেগুলো method-এর একটা subset মিলে ব্যবহার করে। যদি তিনটা field আর চারটা method সবসময় একসাথে কাজ করে আর বাকিগুলো খুব কমই ছোঁয়, সেই cluster-টাই একটা লুকানো class। billingTotal আর billingTax-এর মতো name prefix-ও শক্তিশালী ইঙ্গিত।
- Large Class সবচেয়ে বেশি কোন refactoring দিয়ে ঠিক হয়?
- Extract Class। field আর method-এর একটা cohesive cluster বেছে নাও, সেটাকে একটা নতুন class-এ নিয়ে যাও, আর পুরনো class-টাকে সেটার একটা reference রাখতে দাও। Extract Subclass আর Extract Interface বিশেষ ক্ষেত্রে কাজে লাগে।
আরো দেখো
সম্পর্কিত পাঠ
Long Method: যখন একটা function সব কিছু করতে চায়
Long Method code smell শিখো সহজ গল্পের মাধ্যমে — TypeScript আর C# example সহ, Extract Method দিয়ে step-by-step refactoring। একদম beginner-friendly গাইড।
Primitive Obsession: যখন সব কিছুই শুধু একটা string বা number
Primitive Obsession সহজ ভাষায় — কেন plain string আর number bug লুকিয়ে রাখে, আর কীভাবে Money বা Address-এর মতো value object দিয়ে code-কে safe আর পরিষ্কার করা যায়।
Data Clumps: যে বন্ধুরা সবসময় একসাথে ঘোরে
শিক্ষার্থীদের জন্য Data Clumps code smell — শেখো কীভাবে সবসময় একসাথে চলা value-এর গ্রুপ চেনা যায় আর সেগুলোকে একটা class-এ bundle করা যায়, ঠিক যেমন একটা student ID card।
Divergent Change: এক বেচারা কেরানি, অনেক বস
Divergent Change code smell শেখো একটা school-এর কেরানির গল্পের মাধ্যমে — সহজ সংজ্ঞা, TypeScript ও C# example, Shotgun Surgery-র সাথে তুলনা, আর practice exercise।