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

Data Clumps: যে বন্ধুরা সবসময় একসাথে ঘোরে

শিক্ষার্থীদের জন্য Data Clumps code smell — শেখো কীভাবে সবসময় একসাথে চলা value-এর গ্রুপ চেনা যায় আর সেগুলোকে একটা class-এ bundle করা যায়, ঠিক যেমন একটা student ID card।

20 মিনিট আপডেট: June 11, 2026beginner
code smellsdata clumpsbloatersrefactoringparameter objectvalue objects

তিন বন্ধু — প্রতিটা স্কুল ফর্মে

ধরো ঢাকার আজিমপুর হাই স্কুলে প্রতিটা ফর্মে একই তিনটা জিনিস চাওয়া হয়:

নাম। ক্লাস। রোল নম্বর।

লাইব্রেরি বই ইস্যু ফর্ম? নাম, ক্লাস, রোল নম্বর। স্পোর্টস ডে রেজিস্ট্রেশন? নাম, ক্লাস, রোল নম্বর। পরীক্ষার হল টিকিট? নাম, ক্লাস, রোল নম্বর। পিকনিক পারমিশন স্লিপ? তুমি বুঝতেই পারছ।

বারো বছরের রাহিম বছরে দুইশোবার এই তিনটা বক্স পূরণ করে। আর ভুলও হয় প্রতিনিয়ত। স্পোর্টস ফর্মে সে গত বছরের পুরনো ক্লাস লিখেছিল। লাইব্রেরি ফর্মে তার বন্ধু করিম লিখেছিল রোল ২৩, ৩২ এর বদলে — আর বকেয়া নোটিশ গিয়ে পড়েছিল ভুল ছাত্রের কাছে, রোল ২৩ এর বেচারি নাসরিনের কাছে, যে সেই মাসে লাইব্রেরিতে ঢোকেইনি। প্রতিটা ফর্ম তিনটা value নিজের মতো check করে: লাইব্রেরিয়ান তার রেজিস্টারে, স্পোর্টস টিচার তার লিস্টে, আর কখনো কেউ যাচাই করেনই না।

তারপর একদিন স্কুল বুদ্ধিমান হলো। প্রতিটা ছাত্রকে একটা ID card দিল। নাম, ক্লাস, আর রোল নম্বর — একবার print, একবার যাচাই, একসাথে laminate। এখন প্রতিটা ফর্মে শুধু লেখা: "ID card-এর ফটোকপি সংযুক্ত করুন।" একটাই জিনিস বহন করতে হয়। ক্লাস ভুল লেখার সুযোগ নেই। রাহিম যখন পরের ক্লাসে ওঠে, স্কুল একটাই card নতুন করে দেয় — আর ভবিষ্যতের সব ফর্ম এমনিতেই ঠিক।

এই তিনটা value আসলে কখনো তিনটা আলাদা জিনিস ছিল না। এগুলো ছিল একটাই জিনিস — একজন ছাত্রের পরিচয় — টুকরো হয়ে চলছিল কারণ কেউ এটাকে একটা card দেয়নি।

পর্দার পেছনে আরেকটা গল্প আছে। স্কুলের app বানিয়েছিল সুমাইয়া, আজিমপুর হাই স্কুলের পুরনো ছাত্রী, এখন developer। প্রিন্সিপাল তাকে ID card সিস্টেম digitize করতে বললে, সুমাইয়া তার দুই বছর আগের code খোলে — আর থমকে যায়। তার library module, sports module, আর exam module প্রতিটাই name, className, rollNo তিনটা আলাদা value হিসেবে pass করছে। একই trio। একই check, copy-paste করা। তার code সবসময় করিমের ভুলটাই করে আসছিল, শুধু আরো দ্রুত।

Code-এ এই smell-এর নাম Data Clumps: একই ছোট গ্রুপের value বারবার একসাথে দেখা — parameter list-এ, field-এ, local variable-এ — কিন্তু কখনো তাদের একটা নাম আর নিজের ঘর দেওয়া হয়নি। এই lesson-টা রাহিমের ফর্ম আর সুমাইয়ার refactor পাশাপাশি follow করে, কারণ এগুলো আসলে একটাই গল্প।

এই smell টা আসলে কী?

আগে একটু মনে করিয়ে দিই: code smell মানে bug না। clump ভরা code ঠিকমতোই চলে। smell টা সামনের ঝামেলার সতর্কতা দেয় — duplicated check, swap bug, আর পরিবর্তন যা ডজনখানেক file জুড়ে ছড়িয়ে পড়ে। Data Clumps হলো Martin Fowler-এর Refactoring বই থেকে পাঁচটা "Bloater" smell-এর শেষটা। আর এটা সবচেয়ে নীরব টা: কিছু দেখতে বড় না, তবু bloat সর্বত্র পাতলা করে ছড়িয়ে আছে।

Data Clump হলো দুই, তিন, বা চারটা data item-এর একটা গ্রুপ যেগুলো সবসময় পাশাপাশি থাকে:

  • startDate আর endDate
  • latitude আর longitude
  • street, city, আর pinCode
  • name, className, আর rollNumber

এই clump টা method-এর পর method-এ parameter হিসেবে আসে, আর class-এর পর class-এ field হিসেবে থাকে — তবুও code-এ গ্রুপটার নিজের কোনো নাম নেই। Martin Fowler তার bliki আর Refactoring বইয়ে এই pattern নিয়ে বলেছেন: কয়েকটা data item যদি বারবার একসাথে জড়ো হয়, সেগুলোকে তাদের নিজস্ব একটা object-এ বানিয়ে ফেলো।

💡

Fowler-এর বিখ্যাত deletion test: মাথায় গ্রুপের একটা সদস্য মুছে দাও। বাকিগুলো কি তখনও মানে রাখে? startDate ছাড়া endDate মানেহীন — তাই এই pair আসলে একটা concept (একটা date range) যা টুকরো হয়ে আছে। যদি বাকিগুলো একা একা মানে রাখে, সেটা কখনো সত্যিকারের clump ছিলই না।

দেখো এই smell কীভাবে তার Bloater ভাইবোনদের সাথে যুক্ত। parameter হিসেবে pass হওয়া clump গুলো Long Parameter List তৈরি করে। clump গুলো সাধারণত loose primitive দিয়ে তৈরি — সেটাই Primitive Obsession। আর Large Class-এর ভেতরে বারবার আসা field clump গুলো ঠিক সেই জায়গা চিহ্নিত করে যেখানে class টাকে ভাগ করা উচিত। একটা Bloater খুঁজে পেলে কাছেই তার কাজিনদেরও পাবে। সুমাইয়া এক বিকেলেই চারটাই খুঁজে পায়।

কলেজ কর্নার: একটা shared rule সহ clump — "start অবশ্যই end-এর আগে হতে হবে" — এটা একটা multi-field invariant, আর invariant-এর একজনই enforcer দরকার। design-এর ভাষায়, cure object (যেমন DateRange বা StudentId) একটা value object যার constructor হলো একমাত্র সেই gate যেখানে invariant check হয়। এটাই "invalid states অপ্রকাশ্য করো" ধারণা — Primitive Obsession থেকে নেওয়া, শুধু একটা value থেকে একটা ছোট গ্রুপে প্রসারিত।

কীভাবে চিনবে

সুমাইয়া এই checklist দিয়ে তার codebase খুঁজেছে। তুমিও তোমারটায় খোঁজো:

  • একই দুই থেকে চারটা value অনেক signature-এ একসাথে চলে: প্রতিটা booking function নেয় (checkIn, checkOut), প্রতিটা map function নেয় (lat, lng)
  • গ্রুপটা কয়েকটা অসম্পর্কিত class-এ field হিসেবে থাকেOrder, Invoice, আর Shipment সবাই আলাদাভাবে street, city, pinCode declare করেছে।
  • গ্রুপ সম্পর্কিত একটা rule বারবার নকল হয় যেখানেই যায়: "start অবশ্যই end-এর আগে" পাঁচটা আলাদা method-এ check হচ্ছে।
  • সদস্যগুলো positional primitive হিসেবে pass হয়, তাই একই type-এর পাশের দুটো চুপচাপ swap হয়ে যেতে পারে।
  • গ্রুপটা deletion test ফেল করে — একটা সদস্য সরাও আর বাকিগুলো অর্থহীন হয়।
  • একটা সম্পর্কিত value যোগ করা মানে গ্রুপটা যেখানে যেখানে আছে সব জায়গা edit করতে হবে
উপসর্গকী বলছে
অনেক signature-এ একই trioএকটা concept সবার মাথায় আছে কিন্তু code-এ কোথাও নেই
অনেক class-এ একই field আবার declareপ্রতিটা class একটা missing type হাতে copy করছে
সব জায়গায় "start before end" checkগ্রুপটার একটা invariant আছে কিন্তু enforce করার কেউ নেই
(from, to) দুটোই DateTime বা দুটোই stringtransposition bug অপেক্ষা করছে; compiler সাহায্য করতে পারবে না
Deletion test ফেলvalue গুলো একটা concept যা টুকরো হয়ে আছে
একটা নতুন field ২০টা file জুড়ে ছড়িয়ে পড়েclump-এর পরিবর্তনের জন্য একটা ঘর নেই

সুমাইয়ার audit result: trio টা তিনটা signature আর দুটো class-এ, আর roll-number check তিনবার paste — ইতিমধ্যেই ছোট ছোট পার্থক্যসহ।

কেন এটা সমস্যা

  1. Duplicated validation যা ভিন্ন হয়ে যায়। "check-out অবশ্যই check-in-এর পরে" rule প্রতিটা জায়গায় আবার type করা হয়। একদিন একটা copy same-day booking handle করতে ঠিক হলো, বাকি চারটা হলো না। এখন তোমার system নিজের সাথে নিজে দ্বিমত করছে।
  2. তোমার code-এর ভাষায় একটা missing শব্দ। Team প্রতিটা meeting-এ বলে "date range", "coordinate", "student identity" — কিন্তু code শুধু বলে DateTime, DateTime আর string, string, number। code টুকরোয় কথা বলে যখন মানুষ concept-এ কথা বলে।
  3. পরিবর্তনে বিস্তৃত ঢেউ। Date handling-এ একটা timezone যোগ করো আর তোমাকে প্রতিটা method edit করতে হবে যেটা loose pair বহন করে। একটা DateRange type থাকলে একটা file edit করলেই শেষ।
  4. সর্বত্র long parameter list। Clump গুলো Long Parameter List smell-এর এক নম্বর feeder — তিনটা তিন-এর clump মানে নয়-parameter দানব।
  5. Transposition bug। isAvailable(end, start) সুন্দরভাবে compile হয় আর উল্টো সময়ে ঘর book করে। একই type-এর পাশের দুটো নীরব ফাঁদ।
Figure 1: একটা unnamed pair একটা codebase জুড়ে ছড়িয়ে পড়ে যতক্ষণ না প্রতিটা পরিবর্তন সর্বত্র ঢেউ তোলে

ব্যাপারটা হলো, সেই figure-এর সবচেয়ে দুঃখের পথটা হলো শেষেরটা। পরিবর্তন যখন অনেক বড় হয়ে যায়, developer রা হাল ছেড়ে দেয় আর নতুন value টা কোথাও local করে লাগিয়ে দেয় — design আরো অসামঞ্জস্যপূর্ণ হয়। অঠিক clump গুলো স্থির থাকে না, এগুলো নষ্ট হয়।

সুমাইয়া যখন map করে তার student trio কোথায় কোথায় আছে, ছড়িয়ে পড়ার পরিমাণ তাকে অবাক করে — clump টা প্রতিটা module দখল করে নিয়েছে:

Figure 2: স্কুল app-এ name-class-rollNo trio কোথায় কোথায় আছে — একটা concept-এর পাঁচটা copy

আর এখানে ripple cost যা প্রিন্সিপাল সত্যিকারে অনুভব করেন। স্কুল একটা চতুর্থ value যোগ করতে চাইছে — ছাত্রের হাউস (লাল, নীল, সবুজ, হলুদ) — স্পোর্টস ডে-র জন্য। bundle ছাড়া, একটা সম্পর্কিত field যোগ করার cost clump-এর প্রতিটা copy-র সাথে বাড়তে থাকে:

Figure 3: একটা সম্পর্কিত field যোগ করতে edit করতে হবে এমন file — loose clump বনাম bundled type

নিচের flat line টা bundled design: একটা type, একটা file, শেষ। ওই একটা flat line-ই clump ঠিক করার পুরো business case।

ভুল বকেয়া নোটিশ — একটা clump bug-এর ভেতরে

মনে আছে করিম ৩২ এর বদলে ২৩ লিখেছিল? একটু ভাবো — সুমাইয়ার app-এ ঠিক একই ঘটনা, যেখানে একটা transposed value তিনটা loose parameter দিয়ে চুপচাপ চলে যায়:

Figure 4: কীভাবে একটা transposed roll number unchecked চলে গিয়ে ভুল ছাত্রের কাছে পৌঁছায়

প্রতিটা individual check pass হয়েছিল — ২৩ একটা সম্পূর্ণ valid roll number। bug টা ছিল সম্পর্কে: সেই roll number টা সেই নাম আর class-এর সাথে মেলেনি। একটা bundled, verified identity (ID card) সেটা ধরতে পারত। এদিকে রাহিমের এক বছরের কাগজপত্র এরকম দেখায়:

Figure 5: রাহিমের ফর্ম পূরণের বছর, আইডি কার্ডের আগে ও পরে

কোন clump গুলো class পাওয়ার যোগ্য

একটু ভাবো — দুটো value যদি শুধু একবার কাকতালীয়ভাবে দেখা হয়, সেটা clump না। সুমাইয়া তার candidates দুটো axis-এ rank করে: group টা কতবার বারবার আসে, আর এটা কতটা একটা concept — deletion test ফেল করে কিনা, একটা rule শেয়ার করে কিনা। extract করার আগে এখানে একটা group plot করো:

Figure 6: Bundle সিদ্ধান্ত — যে repeated group একটা সত্যিকারের concept তৈরি করে সেগুলো আগে class পায়

student trio আর date pair "Bundle it now"-এ বসে — ভারী repetition, শক্তিশালী মানে। একটা নাম একবার একটা theme colour-এর পাশে দেখা গেলে সেটা শুধু কাকতালীয়। সেটা bundle করলে একটা ভুয়া concept তৈরি হবে।

বাস্তব code উদাহরণ

সুমাইয়ার আসল code দেখি — ID card-এর আগে আজিমপুর হাই স্কুলের অবস্থা। দেখো trio টা কীভাবে চলে:

// Library module
function issueBook(
  name: string, className: string, rollNo: number, bookTitle: string,
): void {
  if (rollNo <= 0 || rollNo > 60) throw new Error("Bad roll number");
  if (!/^[5-9][A-D]$/.test(className)) throw new Error("Bad class");
  console.log(`Issued "${bookTitle}" to ${name} (${className}-${rollNo})`);
}
 
// Sports module
function registerForSportsDay(
  name: string, className: string, rollNo: number, event: string,
): void {
  if (rollNo <= 0 || rollNo > 60) throw new Error("Bad roll number");   // copy 2
  if (!/^[5-9][A-D]$/.test(className)) throw new Error("Bad class");    // copy 2
  console.log(`${name} (${className}-${rollNo}) registered for ${event}`);
}
 
// Exam module
function printHallTicket(
  name: string, className: string, rollNo: number, examCode: string,
): string {
  if (rollNo <= 0 || rollNo > 60) throw new Error("Bad roll number");   // copy 3
  if (!/^[5-9][A-D]$/.test(className)) throw new Error("Bad class");    // copy 3
  return `HALL TICKET ${examCode}: ${name}, ${className}, Roll ${rollNo}`;
}
 
// And the classes copy the fields too:
class LibraryRecord {
  name = ""; className = ""; rollNo = 0; booksIssued: string[] = [];
}
class SportsEntry {
  name = ""; className = ""; rollNo = 0; event = "";
}

ক্ষতিটা count করো, যেভাবে সুমাইয়া সেই বিকেলে count করেছিল:

  • name, className, rollNo trio টা তিনটা signature আর দুটো class-এ দেখা গেছে — একটা concept-এর পাঁচটা copy।
  • Validation rule গুলো তিনবার paste হয়েছে। যেদিন স্কুল class 10 যোগ করবে, /^[5-9]/ হবে /^([5-9]|10)/ — কতটা copy আসলে update হবে? অভিজ্ঞতা বলে: সব না।
  • প্রতিটা call site swap-prone: issueBook("7A", "Kabir", 32, ...) — নাম আর class উল্টো — চলে যাবে যদি দুটোই accepted string হয়, বা ভুলটা থেকে অনেক দূরে গিয়ে fail করবে।
  • Deletion test জোরে fail করে: class ছাড়া roll number কাউকে identify করে না। কোন class-এর roll 32?
⚠️

ছড়িয়ে পড়া duplication দেখতে সবচেয়ে কঠিন। প্রতিটা module আলাদাভাবে ঠিকই দেখায় — library-এর code, sports code, exam code প্রতিটাই সুন্দর মনে হয়। smell টা শুধু তখনই দেখা যায় যখন তুমি সেগুলো পাশাপাশি তুলনা করো। code review-এর সময় এই তুলনাটা অভ্যাসে পরিণত করো।

ধাপে ধাপে পরিষ্কার করা

ধাপ ১: Introduce Parameter Object দিয়ে ID card ইস্যু করো। সুমাইয়া সেই concept-এর নাম দেয় যেটা সবসময় সেখানে ছিল:

class StudentId {
  constructor(
    readonly name: string,
    readonly className: string,
    readonly rollNo: number,
  ) {
    if (rollNo <= 0 || rollNo > 60) throw new Error("Bad roll number");
    if (!/^[5-9][A-D]$/.test(className)) throw new Error("Bad class");
  }
 
  label(): string {
    return `${this.name} (${this.className}-${this.rollNo})`;
  }
}

Validation এখন একবারই আছে, একমাত্র সেই gate-এ যেখানে একটা StudentId জন্ম নিতে পারে। পরের বছর class 10 যোগ করা হবে একটা line, একটা file পরিবর্তনে। label() formatting — আগে তিনটা জায়গায় string দিয়ে আবার তৈরি হতো — তার স্বাভাবিক ঘরে চলে গেছে।

ধাপ ২: প্রতিটা signature পাতলা করো। প্রতিটা function এখন card accept করে:

function issueBook(student: StudentId, bookTitle: string): void {
  console.log(`Issued "${bookTitle}" to ${student.label()}`);
}
 
function registerForSportsDay(student: StudentId, event: string): void {
  console.log(`${student.label()} registered for ${event}`);
}
 
function printHallTicket(student: StudentId, examCode: string): string {
  return `HALL TICKET ${examCode}: ${student.label()}`;
}

চারটা parameter হয়ে গেছে দুটো। কোনো validation দেখা যাচ্ছে না — কারণ দরকার নেই। আর issueBook(bookTitle, student) এর মতো transposed call এখন compile error, নাসরিনের কাছে ভুল বকেয়া নোটিশ না।

ধাপ ৩: Extract Class চিন্তা দিয়ে field ঠিক করো, আর Preserve Whole Object দিয়ে object ভাঙা বন্ধ করো। যে class গুলো trio copy করেছিল সেগুলো এখন card ধরে:

class LibraryRecord {
  constructor(
    readonly student: StudentId,
    readonly booksIssued: string[] = [],
  ) {}
}
 
// Preserve Whole Object: card ভেঙে তার টুকরো pass করো না!
// Smelly:  issueBook(record.student.name, record.student.className, ...)
// Clean:
issueBook(record.student, "The Jungle Book");

এই শেষ পয়েন্টটা একটু থামার দাবি রাখে। type তৈরির পরেও clump ফিরে আসার একটা চালাক উপায় হলো caller রা সেটা unpack করে: f(student.name, student.className, student.rollNo)। যেই মুহূর্তে একটা object-এর টুকরো pass করছ, সেটাই pass করো। এক বাক্যে Preserve Whole Object

Refactored design, যেমন সুমাইয়া প্রিন্সিপালকে present করে — তিনি সাথে সাথে বোঝেন, কারণ এটাই তো ID card system:

Figure 7: একটা StudentId type প্রতিটা module serve করে — class diagram হিসেবে ID card

আর before-and-after flow:

Figure 8: আগে তিনটা value loose হয়ে প্রতিটা module-এ যায়; পরে একটা StudentId card সর্বত্র যায়

কলেজ কর্নার: লক্ষ্য করো refactor-এর সময় behaviour-এ কী হলো: label() StudentId-এ চলে গেছে, আর overlap বা comparison logic একইভাবে DateRange-এর মতো type-এ চলে যায়। এটাই clump সম্পর্কে Fowler-এর গভীর কথা — নতুন class টা data bag হিসেবে শুরু হয় কিন্তু দ্রুত সেই method গুলো আকর্ষণ করে যেগুলো তার। Object-oriented design মূলত এটাই: behaviour কে সেই data-র কাছে নিয়ে যাওয়া যেটা তার।

এই smell-এর জীবনচক্র

Clump গুলো একটা নীরব কিন্তু অনুমানযোগ্য arc follow করে। কারণ কিছুই কখনো বড় দেখায় না, team রা সাধারণত state পরিবর্তনগুলো notice করে না যতক্ষণ না ripple cost কামড়ায়:

Figure 9: একটা clump-এর জীবনচক্র — একটা নির্দোষ pair থেকে codebase-wide convention পর্যন্ত, আর ফিরে

আসলে প্রতিটা bloater-এর মতো, সস্তায় বের হওয়ার পথ হলো তাড়াতাড়ি: দ্বিতীয়বার একই group type করার সাথে সাথেই concept-এর নাম দাও। clump entrenched হয়ে গেলে, refactor এখনো মূল্যবান — কিন্তু এখন এটা এক বিকেলের কাজ না, একটা project।

C#-এ একই smell

ধরো একটা delivery app latitude আর longitude loose double হিসেবে pass করছে — দুনিয়ার সবচেয়ে বিস্তৃত clump:

public double DistanceKm(double lat1, double lng1, double lat2, double lng2)
{
    // four same-typed doubles in a row: swap any two and
    // your delivery boy swims in the Arabian Sea
    /* haversine formula ... */
}
 
public bool IsInDeliveryZone(double lat, double lng,
                             double centerLat, double centerLng, double radiusKm)
{
    return DistanceKm(lat, lng, centerLat, centerLng) <= radiusKm;
}

একবার concept-এর নাম দাও:

public readonly record struct GeoPoint(double Latitude, double Longitude)
{
    public double DistanceKmTo(GeoPoint other)
    {
        /* haversine formula ... */
    }
}
 
public bool IsInDeliveryZone(GeoPoint customer, GeoPoint hub, double radiusKm)
    => customer.DistanceKmTo(hub) <= radiusKm;

চারটা anonymous double হয়ে গেছে দুটো named point। distance behaviour টা যে concept-এর সে সেখানে চলে গেছে। আর call list-এ (lng, lat) swap করা অনেক কঠিন হয়ে গেছে। একটা ছোট্ট record struct — runtime-এ প্রায় কোনো cost ছাড়াই — map bug-এর একটা পুরো পরিবার সরিয়ে দিল।

বাস্তব project-এ এই smell কোথায় লুকায়

Data Clumps-এর পছন্দের আবাসস্থল আছে, আর researcher আর practitioner রা বারবার একই জায়গাগুলো খুঁজে পান:

  • Date pair। startDate/endDate, validFrom/validTo, checkIn/checkOut — Fowler নিজেই range কে textbook উদাহরণ হিসেবে ব্যবহার করেন। একটা DateRange type Overlaps method সহ booking, reporting, আর billing module জুড়ে ছড়িয়ে থাকা comparison logic replace করে।
  • Coordinate আর dimension। (x, y), (lat, lng), (width, height) — graphics, mapping, আর game codebase loose pair-এ উপচে পড়ছে যেগুলো Point, GeoPoint, বা Size হতে চায়।
  • Address টুকরো। street, city, state, pin Customer, Order, Warehouse, আর Invoice-এ আলাদাভাবে declare — একটা system-এ চারটা হাতে-copy করা address book, প্রতিটা আলাদাভাবে validate।
  • Money pair। amount আর currencyCode আলাদাভাবে চলছে — এই clump Primitive Obsession-ও, আর এর সমাধান Money value object software design-এর সবচেয়ে বিখ্যাতগুলোর একটা।
  • Connection settings। host, port, username, password চারটা argument হিসেবে infrastructure code-এর layer দিয়ে pass হচ্ছে একটা ConnectionInfo-এর বদলে — একটা clump যেটা log line-এ সহজে secret ফাঁস করে দেয়।
  • Pagination trio। page, pageSize, sortBy একটা API-এর প্রতিটা list endpoint-এ copy হয়ে যাচ্ছে একটা PageRequest object-এর বদলে।
ℹ️

কিছু IDE আর analysis tool স্বয়ংক্রিয়ভাবে clump detect করে method জুড়ে identical parameter group খুঁজে বের করে — IntelliJ-এর "Extract Parameter Object" আর অন্য tool-এর similar inspection এমনকি bundling নিজেই করে দেবে। Academic tool গুলো ঠিক এই উদ্দেশ্যে signature জুড়ে "tuple recurrence" study করে।

কখন ignore করা যায়

পরিস্থিতিSmell ignore করবে?কেন
দুটো value একবার কাকতালীয়ভাবে একসাথেহ্যাঁBundle করলে ভুয়া concept তৈরি হয়; মানেহীন নাম না থাকার চেয়েও খারাপ
Group ৩+ জায়গায় বারবার আসেনাপ্রতিটা repetition মানে duplicated validation আর একটা swap hazard
Group-এর invariant আছে (start < end)নাএকটা invariant-এর ঠিক একজন enforcer দরকার — তাকে একটা constructor দাও
Group deletion test pass করে (সদস্যরা স্বাধীন)হ্যাঁএগুলো সহযাত্রী, একটা concept না — loose রাখো
ছোট script, group দুবার ব্যবহার, কোনো rule নেইসম্ভবতনতুন type টা short-lived script-এ কোনো payoff ছাড়া শুধু overhead
Bundle-এর জন্য সৎ কোনো নাম মাথায় আসছে নাঅপেক্ষা করোস্বাভাবিক নাম না থাকা মানে প্রায়ই স্বাভাবিক concept নেই; জোর করে DataHolder3 বানিও না

সহজ কথায়: extract করো যখন grouping stable (বারবার আসতে থাকে) আর meaningful (deletion test fail করে, বা shared rule আছে)। একটা clump তার class অর্জন করতে হবে — কিন্তু যখন করে, class টা সারাজীবন কাজ দেয়। Figure 6-এর সেই quadrant-এ ঠিক এটাই।

কোন refactoring গুলো ঠিক করে

Refactoringকখন ব্যবহার করবে
Extract ClassClump টা এক বা একাধিক class-এ field হিসেবে থাকে — তাকে নিজস্ব একটা class দাও
Introduce Parameter ObjectClump টা method signature-এ চলে — group টাকে একটা object দিয়ে replace করো
Preserve Whole ObjectCaller রা একটা object ভেঙে তার অংশ pass করছে — পুরো object pass করো
Replace Data Value with ObjectClump-এর individual সদস্যরা নিজেরাও smelly primitive — তাদেরও upgrade করো
Extract MethodClump সম্পর্কিত logic (formatting, comparing) চারদিকে paste হয়ে আছে — একসাথে করো, তারপর নতুন type-এ সরিয়ে নাও

এক পাতায় পুরো smell

Tech club-এর জন্য সুমাইয়ার summary slide — রাহিম সামনের সারিতে বসে আছে:

Figure 10: Data Clumps এক নজরে — লক্ষণ, পরীক্ষা, cost, আর সমাধান

দ্রুত revision

+------------------------------------------------------------------+
|                  DATA CLUMPS - CHEAT SHEET                       |
+------------------------------------------------------------------+
| What     : The same small group of values travelling together    |
|            everywhere, never given a name (name+class+rollNo)    |
| Family   : Bloaters (the quiet, scattered one)                   |
| Spot it  : Repeated parameter trios, re-declared field groups,   |
|            duplicated group rules, swap-prone neighbours         |
| Test     : Deletion test - remove one member; if the rest        |
|            become meaningless, it is a real clump                |
| Costs    : Drifting validation, ripple on change, long           |
|            parameter lists, transposition bugs                   |
| Main fix : Introduce Parameter Object / Extract Class            |
| Helper   : Preserve Whole Object (stop re-scattering it!)        |
| Ignore   : One-off pairings; bundles you cannot honestly name    |
| Mantra   : "If they always travel together, give them            |
|             one ID card."                                        |
+------------------------------------------------------------------+

অনুশীলনী

Tech club-এর জন্য সুমাইয়ার exercise — আর তোমার জন্যও। নিচে একটা coaching centre-এর app। একটা clump চারবার দেখা যাচ্ছে — দুবার signature-এ, দুবার field-এ — আর তার rule duplicate। সেটা খুঁজে বের করো আর ঠিক করো।

function scheduleBatch(
  subject: string,
  startHour: number,
  endHour: number,
  teacher: string,
): void {
  if (startHour >= endHour) throw new Error("Bad timing");
  console.log(`${subject} batch: ${startHour}:00 to ${endHour}:00 with ${teacher}`);
}
 
function isTeacherFree(
  teacher: string,
  startHour: number,
  endHour: number,
  bookings: { teacher: string; startHour: number; endHour: number }[],
): boolean {
  if (startHour >= endHour) throw new Error("Bad timing");
  return !bookings.some(
    (b) =>
      b.teacher === teacher &&
      startHour < b.endHour &&
      b.startHour < endHour,
  );
}
 
class Batch {
  subject = "";
  startHour = 0;
  endHour = 0;
  teacher = "";
}

তোমার কাজ:

  1. Clump-এর নাম দাও। Deletion test apply করো: startHour ছাড়া endHour কি কোনো মানে রাখে?
  2. একটা TimeSlot class তৈরি করো যার constructor-এ validation আছে আর দুটো method আছে: overlaps(other: TimeSlot) আর একটা সুন্দর toString()
  3. দুটো function আর Batch class কে TimeSlot ব্যবহার করতে rewrite করো। startHour >= endHour check কতটা copy বাকি থাকে? Target: ঠিক একটা।
  4. Bonus: overlap formula (start < b.end && b.start < end) হলো tricky logic যা isTeacherFree-এর ভেতরে বসে আছে। তোমার refactor-এর পরে এটা TimeSlot-এর একটা tested, reusable method হওয়া উচিত — এর জন্য দুটো quick test case লেখো: touching slot (10-12 আর 12-14) আর overlapping slot (10-13 আর 12-14)।
  5. Extra challenge: Figure 6-এর quadrant-এ TimeSlot plot করো। কোন কোণে পড়ে, আর কেন?

এই lesson-এর সাথে তুমি পুরো Bloater পরিবার শেষ করলে: Long Method, Large Class, Primitive Obsession, Long Parameter List, আর Data Clumps। দেখো একই medicine বারবার এলো — Extract, Introduce, Preserve। এই কয়েকটা refactoring ভালো করে শিখে নাও, আর পুরো bloated পরিবার তোমাকে ভয় পাবে — ঠিক যেমন আজিমপুর হাই স্কুলের প্রতিটা ফর্ম এখন রাহিমের laminated ID card-কে ভয় পায়।

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

Data Clump নিশ্চিত করার সবচেয়ে দ্রুত উপায় কী?
Deletion test — Martin Fowler-এর পরামর্শ। মাথায় ভাবো গ্রুপের একটা value মুছে দিলে কী হয়। বাকিগুলো কি তখনও মানে রাখে? startDate ছাড়া endDate মানেহীন — তাই এই pair একটা সত্যিকারের clump, এটা একটা object হওয়া দরকার।
Data Clumps আর Long Parameter List-এর পার্থক্য কী?
Long Parameter List হলো একটা signature-এ অনেক বেশি parameter। Data Clumps হলো একই ছোট গ্রুপ অনেক জায়গায় বারবার দেখা — signature-এ, field-এ, local variable-এ। একটা clump প্রায়ই long parameter list তৈরি করে, কিন্তু এটা field declaration-এও লুকিয়ে থাকতে পারে যেখানে কোনো parameter list নেই।
Data Clumps আর Primitive Obsession-এর পার্থক্য কী?
Primitive Obsession হলো একটা value ভুল type-এ থাকা — যেমন email plain string হিসেবে। Data Clumps হলো কয়েকটা value-এর কোনো shared home না থাকা — যেমন street, city, PIN কখনো bundle না হওয়া। এগুলো সাধারণত একসাথেই দেখা যায়, আর একই সমাধান — একটা value object — প্রায়ই দুটোই ঠিক করে দেয়।
একসাথে দেখা যাওয়া প্রতিটা pair কি clump?
না। দুটো value যদি শুধু একবার কাকতালীয়ভাবে দেখা হয়, সেটা clump না। সেগুলো bundle করলে একটা ভুয়া concept তৈরি হবে। এমন গ্রুপ খোঁজো যেগুলো অনেক জায়গায় বারবার দেখা যায়, deletion test ফেল করে, বা 'start অবশ্যই end-এর আগে হতে হবে' এরকম একটা rule শেয়ার করে।
কোন refactoring-গুলো data clumps ঠিক করে?
Extract Class যখন clump field হিসেবে থাকে, Introduce Parameter Object যখন signature-এ থাকে, আর Preserve Whole Object যখন caller একটা object ভেঙে তার টুকরো pass করে। প্রায়ই তিনটাই একসাথে লাগে।

আরো দেখো

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

Long Parameter List: দশটা নির্দেশনার চায়ের অর্ডার

Long Parameter List কোড স্মেল সহজ ভাষায় — কেন বেশি argument-এর method বাগ তৈরি করে, আর কীভাবে parameter object দিয়ে call ছোট, পরিষ্কার আর নিরাপদ করা যায়।

আরও পড়ুন

Primitive Obsession: যখন সব কিছুই শুধু একটা string বা number

Primitive Obsession সহজ ভাষায় — কেন plain string আর number bug লুকিয়ে রাখে, আর কীভাবে Money বা Address-এর মতো value object দিয়ে code-কে safe আর পরিষ্কার করা যায়।

আরও পড়ুন

Large Class: যে স্কুলের ব্যাগে সব কিছু থাকে

Large Class code smell কী সেটা বুঝো — কেন god class বড় হয়, low cohesion কীভাবে চেনা যায়, আর Extract Class দিয়ে কীভাবে ছোট ছোট focused class-এ ভাগ করা যায়।

আরও পড়ুন

Long Method: যখন একটা function সব কিছু করতে চায়

Long Method code smell শিখো সহজ গল্পের মাধ্যমে — TypeScript আর C# example সহ, Extract Method দিয়ে step-by-step refactoring। একদম beginner-friendly গাইড।

আরও পড়ুন