Data Clumps: একসঙ্গে ঘোরা মান এক object-এ আনুন
প্রতিটি function-এ একই দুটি-তিনটি মান একসঙ্গে এলে তারা একটি object হতে চায়। পরীক্ষিত DateRange উদাহরণে দেখুন।

Data Clumps হলো একসঙ্গে সবসময় আসা মানের গোষ্ঠী: start ও end, latitude ও longitude, street, city ও zip। এগুলো function থেকে function-এ পাঠানো হয়, পাশাপাশি field হিসেবে রাখা হয় এবং বারবার যাচাই হয়। নিচের code ও output প্রকাশের আগে Python 3-এ চালানো।
কীভাবে চিনবেন
- একই দুটি-তিনটি parameter-এর নাম অনেক signature-এ দেখা যায়।
- একটি class-এ সাধারণ prefix-ওয়ালা কয়েকটি field (
billing_street,billing_city,billing_zip)। - গোষ্ঠী থেকে একটি মান মুছলে বাকিদের অর্থ থাকে না।
- একই যাচাই ("শেষ শুরুর আগে হতে পারে না") কয়েক জায়গায় লেখা।
ভালো পরীক্ষা: একটি মান মুছুন। বাকিরা অর্থ হারালে তারা এক object-এ থাকার।
সমাধান: clump-কে object করুন
গোষ্ঠীর জন্য ছোট একটি class বানান, নিয়ম তাতে সরান ও সেটি পাঠান। এটি প্রায়ই একসঙ্গে Long Parameter List-ও সারায় এবং function-এ ছড়ানো আচরণের একটি ঘর দেয়।
from dataclasses import dataclass
from datetime import date
# ---- BEFORE: the same two values travel together through every function ----
def days_between_before(start, end):
if end < start: raise ValueError("end before start")
return (end - start).days + 1
def overlaps_before(s1, e1, s2, e2):
return s1 <= e2 and s2 <= e1
def label_before(start, end):
return f"{start:%d %b %Y} to {end:%d %b %Y}"
# ---- AFTER: the clump becomes one object that owns the rules ----
@dataclass(frozen=True)
class DateRange:
start: date
end: date
def __post_init__(self):
if self.end < self.start:
raise ValueError("end before start")
@property
def days(self): return (self.end - self.start).days + 1
def overlaps(self, other): return self.start <= other.end and other.start <= self.end
def __str__(self): return f"{self.start:%d %b %Y} to {self.end:%d %b %Y}"
DateRange এখন তৈরির সময় একবার দেখে শেষ শুরুর আগে নয়, তাই অবৈধ range কখনও থাকতেই পারে না। যে function start, end নিত তা হয়েছে property (days), method (overlaps) ও string রূপ।
উত্তর একই, প্রমাণ
a = DateRange(date(2026, 11, 3), date(2026, 11, 9))
b = DateRange(date(2026, 11, 8), date(2026, 11, 12))
print(a, "|", a.days, "days |", "overlaps b:", a.overlaps(b))
assert a.days == days_between_before(a.start, a.end)
assert a.overlaps(b) == overlaps_before(a.start, a.end, b.start, b.end)
assert str(a) == label_before(a.start, a.end)
try: DateRange(date(2026, 11, 9), date(2026, 11, 3))
except ValueError as e: print("error:", e)
print("same answers, and the rule lives in one place")
03 Nov 2026 to 09 Nov 2026 | 7 days | overlaps b: True
error: end before start
same answers, and the rule lives in one place
নতুন object পুরনো function-গুলোর মতো একই দিনের সংখ্যা, overlap ফল ও label দেয়, আর অবৈধ range একটি পরিষ্কার error দিয়ে প্রত্যাখ্যান করে। আগে overlaps_before(s1, e1, s2, e2)-এর মতো function চারটি খোলা তারিখ নিত এমন ক্রমে যা মনে রাখতে হতো।
নিরাপদে refactor করবেন যেভাবে
- গোষ্ঠী খুঁজুন: কয়েকটি signature বা field-এ একই নাম দেখুন।
- ওই field-ওয়ালা frozen dataclass বানান এবং
__post_init__-এ যাচাই দিন। - একটি function-কে object নিতে বদলান। test চালান।
- বাকিগুলোয় একই করুন, আর যে logic শুধু এই field ব্যবহার করে তা নতুন class-এ সরান।
- নতুন আচরণ খুঁজুন যার এখন স্পষ্ট ঘর আছে (যেমন
overlaps)।
কখন ছেড়ে দেবেন
- যে দুটি মান কাকতালীয়ভাবে একবার একসঙ্গে ব্যবহৃত, তা clump নয়।
- স্বাধীনভাবে বদলায় এমন মানের গোষ্ঠীর ভাগ করা object লাগে না।
- লক্ষ লক্ষ আইটেমের hot loop-এ class-এর চেয়ে সাধারণ tuple ভালো বিনিময় হতে পারে।
সাধারণ ভুল
- class বানিয়ে logic পুরনো function-এই রেখে দেওয়া, ফলে object খালি থলে থেকে যায় (পরের smell, Data Class)।
- পরিবর্তনযোগ্য করা, ফলে একটি function এমন range বদলায় যা অন্যটি এখনও ব্যবহার করছে।
- constructor-এ যাচাই না করা।
- আসল ধারণার বদলে অস্পষ্ট নাম (
Info,Data) দেওয়া।
মূল কথা: যে মান সবসময় একসঙ্গে ঘোরে সে লুকানো একটি ধারণা। তাকে নাম দিন, নিয়মসহ object করুন, signature ছোট হয় আর আচরণ ঘর পায়।
Code smell ও refactoring সিরিজ, Bloaters: 5 নম্বর (মোট 5)। আগের লেখা: Long Parameter List: সম্পর্কিত argument গুছিয়ে object করুন.


