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

Lazy Class: যে চাকরির কাজ শুধু একটা বাটন চাপা

Lazy Class code smell শিখো একটা মজার গল্পের মাধ্যমে। কোন class-গুলো টিকে থাকার যোগ্যতা রাখে না সেটা বুঝতে পারবে, আর Inline Class দিয়ে সেগুলো ঠিক করতে পারবে।

19 মিনিট আপডেট: June 11, 2026beginner
code-smellsdispensableslazy-classinline-classcollapse-hierarchyrefactoringtypescriptcsharp

যে চাকরির কাজ শুধু একটা বাটন চাপা

ধরো ঢাকার একটা আবাসিক building-এ একটা পুরনো lift আছে।

সেই lift-এ একটা ঝামেলা ছিল। Ground floor-এর বাটনটা পাঁচ সেকেন্ড ধরে না চাপলে lift দুই তলার মাঝখানে আটকে যেত। নতুন visitor-রা জানত না। বাচ্চারা মজার জন্য করত। দুইবার ফায়ার ব্রিগেড আসছে। তখন building committee জরুরি মিটিং করে একটা পদ তৈরি করল: lift-এর দারোয়ান। রহিম ভাই-এর কাজ ছিল কাঠের টুলে বসে থাকা আর visitor এলে সঠিকভাবে বাটনটা চেপে দেওয়া। সেটা তখন একদম দরকারি ছিল। রহিম ভাই তখন সত্যিকারের হিরো — এক আঙুলের হিরো।

তিন বছর আগে building-এ নতুন automatic lift লাগানো হলো। কোনো ট্রিক লাগে না। বাটন চাপলেই lift আসে। বাচ্চারাও পারে।

কিন্তু মজার ব্যাপার হলো: রহিম ভাই-এর পদটা এখনো আছে। প্রতি মাসে তার বেতন যাচ্ছে। তিনি টুলে বসে খবরের কাগজ পড়েন। কেউ এলে উঠে গিয়ে বাটনটা চেপে দেন। সেই বাটন যেটা visitor নিজেই চাপতে পারত। আর কিছু না — না security checking, না register, না gate duty। শুধু একটা বাটন, মাঝে মাঝে।

নতুন treasurer ফাতেমা আপা accounts দেখে annual meeting-এ একটাই প্রশ্ন করলেন: "এক আঙুলের কাজের জন্য পুরো বেতন কেন দিচ্ছি?" সবাই চুপ। কেউ বলল, "উনি তো সবসময় ছিলেন।" আরেকজন বলল, "পুরনো lift-এর সমস্যা আবার আসলে কী করব?" ফাতেমা আপা মনে করিয়ে দিলেন পুরনো lift-টা তিন বছর আগেই বিক্রি হয়ে গেছে। আরো চুপ। পদটার একটা সময় মানে ছিল। এখন শুধু একটা খরচ যেটা কেউ বাদ দিতে ভুলে গেছে।

চিত্র ১: একজন visitor-এর lobby দিয়ে যাওয়া — দারোয়ান আসলে কী যোগ করলেন?

Code-এও এই একই চরিত্র আছে: একটা class যেটা কোনো একটা ভালো কারণে বানানো হয়েছিল, কিন্তু আজকে প্রায় কিছুই করে না। তবুও বেতন পাচ্ছে — একটা file, একটা নাম, একটা import, প্রতিটা reader-এর জন্য একটা jump। এটাই Lazy Class smell।

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

Lazy Class (যাকে Freeloader-ও বলে) হলো এমন একটা class যেটা এতটাই কম করে যে তার থাকার কোনো justify নেই। হয়তো একটা trivial method আছে, অথবা অন্য class-এ call forward করে দেয়, অথবা একটা single field wrap করে কোনো rule ছাড়াই। একসময় যা কাজ করত সব সরে গেছে, কিন্তু class-টা রয়ে গেছে।

তোমার program-এর প্রতিটা class একটা fixed fee নেয়, সে যতই কম কাজ করুক:

  • এটা একটা নাম যেটা team-এর সবাইকে মনে রাখতে হবে।
  • এটা একটা file যেটা reader-কে খুলতে হবে আর একটা jump যেটা follow করতে হবে।
  • এটা diagram-এ আরেকটা box, আরেকটা import, মাথায় আলাদা রাখার আরেকটা জিনিস।

একটা কাজের class এই fee সহজেই দিতে পারে — বিনিময়ে একটা meaningful responsibility দেয়। Lazy class কিছুই ফেরত দেয় না। Reader তার file-এ ঢুকে ভাবে কিছু একটা পাবে, পায় একটা one-line pass-through, আবার বেরিয়ে আসে। সময় গেল, কিছু শিখল না। ঠিক যেমন visitor এসে দেখল দারোয়ান সেই বাটনটাই চাপলেন যেটা সে নিজেই চাপতে পারত।

💡

যেকোনো class-এর জন্য সবচেয়ে দরকারি প্রশ্নটা কখনো "এই class ছোট কিনা" না। প্রশ্নটা হলো: "এই class কি আলাদা থাকার খরচটা earn করছে?" একটা ছোট class যার real কাজ আছে — validation, safety দেওয়া type, true boundary — সেটা তার জায়গা পাওয়ার যোগ্য। একটা ছোট class যেটা শুধু shell — সেটা না। Size দিয়ে judge করো না, কাজ দিয়ে করো।

একটু deep-এ যাই: Object-oriented metrics এই intuition-কে সংখ্যায় প্রকাশ করে। Lazy class সাধারণত weighted methods per class (WMC)-এ প্রায় শূন্য পায় আর LCOM cohesion measure-এ কোনো contribution করে না, কিন্তু dependency graph-এ edge যোগ করে — মানে coupling surface বাড়ে কিন্তু responsibility বাড়ে না। Martin Fowler-এর ভাষায়, প্রতিটা abstraction-এর একটা comprehension cost আছে, আর abstraction তখনই লাভজনক যখন সে যত complexity যোগ করে তার চেয়ে বেশি compress করে। Lazy class হলো negative-profit abstraction: পুরো indirection cost নেয়, কিছুই compress করে না। এজন্যই "class-এর সংখ্যা" এককভাবে quality metric হিসেবে meaningless — দশটা meaningful class, ত্রিশটা shell-এর চেয়ে ভালো যেখানে বিশটাই ফাঁকা।

পুরো বিষয়টা এক map-এ:

চিত্র ২: Lazy Class smell-এর পূর্ণ মানচিত্র

কীভাবে চিনবে

তোমার codebase-এ এই checklist নিয়ে হাঁটো:

  • একটা class যার একটাই trivial method, বা শুধু constructor আর getter, যেটা একটা plain field দিয়েও হতো।
  • একটা subclass যেটা কোনো meaningful কিছু override করে না, নিজস্ব কোনো behavior যোগ করে না।
  • Growth-এর কথা মাথায় রেখে বানানো class যে growth আর আসেনি ("এখানে পরে নিশ্চয়ই আরো কিছু যোগ হবে")।
  • একটা class যার পুরো body তার একমাত্র caller-এ paste করলেও কোনো clarity হারাবে না।
  • একটা interface আর ঠিক একটাই implementation, "flexibility-র জন্য" বানানো, বছরের পর বছরেও দ্বিতীয় implementation নেই।
  • একটা class যেটা শুধু call receive করে অন্য class-এ unchanged forward করে দেয় (এর cousin smell: Middle Man)।
লক্ষণজিজ্ঞেস করোউত্তর যদি হয়...
একটাই ছোট methodএটা কি caller-এর method হতে পারত?হ্যাঁ → lazy
একটা field wrap করাWrapper কি কিছু validate, format বা protect করছে?না → lazy
Subclass কিছু যোগ করে নাকোনো behavior override বা extend করছে?না → lazy
একটাই implementation-এর interfaceআজকে কি real test seam বা published boundary আছে?না → lazy
Refactoring-এ ছোট হয়ে যাওয়া classতার responsibility কি অন্য জায়গায় চলে গেছে?হ্যাঁ → leftover shell
"Manager"/"Helper" দশ লাইনেএটা কি real concept ধরে, নাকি ভাসছে?ভাসছে → lazy

Class judge করার সবচেয়ে ভালো mental tool হলো এই chart-এ রাখা — কতটুকু করে আর কতটুকু ব্যবহার হয়?

চিত্র ৩: কাজ আর ব্যবহারের ভিত্তিতে class judge করা — bottom-left corner হলো দারোয়ানের টুল

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

তুমি হয়তো ভাবছো: "ছোট তো, ক্ষতি কী?" দেখো কেন এটা ভুল।

খরচ ১: Indirection tax। প্রতিটা reader যে class-টার সামনে পড়বে তাকে file খুলে দেখতে হবে এটা ফাঁকা কিনা। একটা lazy class এক মিনিট নষ্ট করে। চল্লিশটা lazy class এক মিনিট করে নষ্ট করে, প্রতিটা নতুন team member-এর জন্য, চিরকাল। দারোয়ানের বেতন মাসে একবার যায়; lazy class-এর বেতন প্রতিটা reader-এর কাছ থেকে নেয়।

খরচ ২: Design signal দুর্বল হয়ে যায়। সুস্থ codebase-এ "এটা একটা class" মানে "এটা একটা meaningful responsibility"। যখন অর্ধেক class-ই ফাঁকা shell, সেই signal মরে যায়। Reader আর trust করতে পারে না কোনো class গুরুত্বপূর্ণ কিনা, তাই সব কিছু দেখতে হয়। সত্যিকারের important abstraction-গুলো trivial class-এর ভিড়ে হারিয়ে যায়।

খরচ ৩: Maintenance drag। Lazy class-গুলো প্রতিটা rename, প্রতিটা dependency upgrade, প্রতিটা API migration-এ সাথে থাকে। তাদের compile রাখার জন্য তুমি সময় দাও, কিন্তু তারা বিনিময়ে কিছু দেয় না। এটাই Dead Code-এর drag — দুটো smell আসলে কাছের আত্মীয়।

খরচ ৪: এগুলো বংশবিস্তার করে। একটা pass-through class দেখলে পরেরটা normal মনে হয়। নতুন developer copy করে house style: "ওহ, এখানে সব কিছু XyzManager-এ wrap করা হয়।" কিছুদিনেই architecture diagram-এ box দ্বিগুণ হয়, মানে অর্ধেক হয়।

একজন নতুন teammate "lift কীভাবে নড়ে" এই simple প্রশ্নের উত্তর খুঁজতে গিয়ে indirection tax কীভাবে দেয় দেখো:

চিত্র ৪: নতুন developer indirection tax দিচ্ছে, একটার পর একটা ফাঁকা file-এ

আর shell জমতে থাকলে tax compound হয়:

চিত্র ৫: Lazy class জমার সাথে সাথে নতুন কেউ একটা feature trace করতে কতটুকু সময় লাগে

একটা lazy class-এর জীবন কীভাবে এগোয়, কেন কেউ খেয়াল করে না:

চিত্র ৬: একটা class-এর জীবন — একসময় কাজের ছিল, এখন ফাঁকা, শেষমেশ delete হলো
চিত্র ৭: দুটো পরিণতি — চিরকাল tax দাও, অথবা shell-টা inline করো

বাস্তব code-এ দেখো

TypeScript-এ building-এর lift system লিখি — দারোয়ানসহ।

// Smelly version: spot the freeloaders
class LiftButton {
  constructor(private readonly floor: number) {}
  press(): number {
    return this.floor; // that's it. that's the whole class.
  }
}
 
// The "watchman": exists only to press the button for you
class LiftWatchman {
  constructor(private readonly button: LiftButton) {}
  assistVisitor(): number {
    return this.button.press(); // forwards the call, adds nothing
  }
}
 
// A subclass that adds... nothing
class AutomaticLift extends Lift {
  // empty. it was going to have "smart scheduling" someday.
}
 
class Lift {
  private currentFloor = 0;
 
  goTo(floor: number): void {
    this.currentFloor = floor;
    console.log(`Lift moving to floor ${floor}`);
  }
}
 
// caller
const button = new LiftButton(0);
const watchman = new LiftWatchman(button);
const lift = new AutomaticLift();
lift.goTo(watchman.assistVisitor());

Freeloader-গুলো গুনে দেখো:

  1. LiftButton একটা single number wrap করে ফেরত দেয়। কোনো validation নেই (negative floor? চারতলা building-এ ৯৯ তলা? — কিছুই care করে না)। এটা costume পরা একটা number।
  2. LiftWatchman call receive করে unchanged forward করে দেয়। এটা হুবহু সেই দারোয়ান যে বাটন চাপে যেটা তুমি নিজেই চাপতে পারতে। Pure Middle Man behavior, delegation আকারে laziness।
  3. AutomaticLift একটা empty subclass যেটা "smart scheduling" feature-এর জন্য রাখা ছিল, সেই feature আর আসেনি — Speculative Generality থেকে জন্ম নেওয়া laziness।

"Lift-কে floor 0-তে পাঠাও" এই চার লাইনের operation বুঝতে reader-কে চারটা class visit করতে হবে। Building তিনটা বাড়তি বেতন দিচ্ছে। এই payroll class-diagram আকারে:

চিত্র ৮: Cleanup-এর আগের payroll — চারটার মধ্যে তিনটা class কোনো real কাজ করে না

Real project-এ এই smell audit করলে সন্দেহজনক class-গুলো সাধারণত এই bucket-এ পড়ে:

চিত্র ৯: Real project-এ lazy class কোথা থেকে আসে

ধাপে ধাপে ঠিক করো

ধাপ ১: Laziness confirm করো। প্রতিটা সন্দেহজনক class-এর জন্য check করো: এটা কি কিছু validate করছে? Transform করছে? Protect করছে? দ্বিতীয় implementation realistically আসতে পারে? এই তিনটা class-এর ক্ষেত্রে উত্তর সব না।

ধাপ ২: Inline Class দিয়ে দারোয়ানকে retire করো। LiftWatchman-এর একটাই কাজ caller-এ নিয়ে যাও, তারপর class-টা delete করো। LiftButton-এও একই কাজ — এর "value" শুধু একটা number, সেই number সরাসরি pass করো।

ধাপ ৩: Collapse Hierarchy দিয়ে ফাঁকা hierarchy merge করো। AutomaticLift Lift-এর উপর কিছুই যোগ করে না, তাই দুই layer এক হয়ে যাক। কোথাও যদি শুধু tiny pass-through method থাকে, Inline Method সেটা শেষ করবে।

ধাপ ৪: Delete করো আর enjoy করো।

// Clean version: one class, one real responsibility
class Lift {
  private currentFloor = 0;
 
  goTo(floor: number): void {
    this.currentFloor = floor;
    console.log(`Lift moving to floor ${floor}`);
  }
}
 
// caller
const lift = new Lift();
lift.goTo(0);

চারটা class হয়ে গেল একটা। কিছু হারায়নি — কারণ বাকি তিনটায় কিছু ছিলই না। Reader-এর journey চার file থেকে কমে একটায় হয়ে গেল।

ধাপ ৫ (গুরুত্বপূর্ণ): কখন থামতে হবে সেটা জানো। ধরো LiftButton এরকম হতো:

// NOT lazy: this wrapper earns its keep through validation
class FloorNumber {
  constructor(private readonly value: number) {
    if (!Number.isInteger(value) || value < 0 || value > 4) {
      throw new Error(`Green Park has floors 0 to 4, got ${value}`);
    }
  }
  toNumber(): number {
    return this.value;
  }
}

এই class ছোট, কিন্তু কাজ করছে। এটা guarantee করছে যে program-এ কোথাও invalid floor থাকতে পারবে না। সেই guarantee সত্যিকারের কাজ। Smell হলো emptiness, কখনো smallness না। চিত্র ৩-এর quadrant chart-এ FloorNumber আরামে "keep it" zone-এ, আর LiftWatchman inline corner-এ।

চিত্র ১০: সন্দেহজনক ছোট class-এর ভবিষ্যত ঠিক করা

C#-এ একই smell

দুই বছরের refactoring-এর পর একটা shipping module। Helper আগে tax calculate করত, currency handle করত, total round করত। সব সরে গেছে। এখন যা আছে:

// Before: the hollowed-out leftover of past refactorings
public class PriceHelper
{
    public decimal AddDelivery(decimal price) => price + 50m;
}
 
public class Checkout
{
    private readonly PriceHelper _helper = new();
 
    public decimal FinalAmount(decimal cartTotal)
        => _helper.AddDelivery(cartTotal);
}

+ 50m-এর জন্য একটা পুরো class, file আর instantiation। Inline Class apply করো:

// After: the shell is gone; the knowledge stays, with a name
public class Checkout
{
    private const decimal DeliveryCharge = 50m;
 
    public decimal FinalAmount(decimal cartTotal)
        => cartTotal + DeliveryCharge;
}

একটা file কমল, একটা jump কমল, আর delivery charge এখনো clearly named। ভবিষ্যতে delivery pricing সত্যিই complex হলে — weight slab, area-based charge, partner rate — তখন DeliveryPricing class earn করে ফিরে আসবে। Class-দের real কাজের জন্য hire করো, পুরনো sympathy-তে রাখো না।

Python-এও একই freeloader আছে:

# Before: a class with one method that wraps one expression
class GstCalculator:
    def add_gst(self, amount: float) -> float:
        return amount * 1.18
 
# every caller:
total = GstCalculator().add_gst(bill)
 
# After: a plain function does this job honestly
GST_RATE = 0.18
 
def add_gst(amount: float) -> float:
    return amount * (1 + GST_RATE)
 
total = add_gst(bill)

একটু deep-এ যাই: এখানে language nuance আছে। Java আর C#-এ organization-এর unit হলো class, তাই laziness মানে সাধারণত অন্য class-এ inline করো। Python, TypeScript, আর Go-তে module-level function first-class citizen — তাই lazy class-এর cure প্রায়ই function-এ demote করো, যেটা আরো সস্তা। একটা useful cross-language principle: সবচেয়ে ছোট organization unit বেছে নাও যেটা কাজটা করতে পারে — expression, function, class, module, service — আর পরের level-এ তখনই যাও যখন current level সত্যিই উপচে পড়ছে। অনেক lazy class শুধু এজন্যই exist করে কারণ কেউ একটা level বেশি উপরে শুরু করেছিল।

Real project-এ এই smell কোথায় লুকিয়ে থাকে

  • বড় refactoring-এর ফসিল। Method pull up হয়, feature সরে যায়, আর একসময়ের busy class shell হয়ে পড়ে। কেউ delete করে না কারণ "এটা তো সবসময় ছিল"। Lazy class-রা বেশিরভাগই পরিবর্তনের ফসিল — ঠিক রহিম ভাই-এর পদের মতো যেটা যে lift-এর জন্য ছিল সেটা বিক্রি হয়ে গেছে।
  • সব জায়গায় one-implementation interface। কিছু codebase-এ প্রতিটা FooService-এর জন্য আপনাআপনি IFooService তৈরি হয়। যেখানে কোনো real test seam বা boundary নেই, প্রতিটা pair একটা lazy layer। আধুনিক mocking tool প্রায়ই concrete class-কেও mock করতে পারে।
  • Empty body-র exception class। চল্লিশটা custom exception যেগুলো কোনো field, message বা special handling যোগ করে না — শুধু একটা নাম। কয়েকটা real মানে সম্পন্ন exception অনেক ভালো কাজ করত।
  • "Manager", "Processor", "Helper" shell। Architecture template-এর দাবিতে বানানো, একটা দশ লাইনের method আছে যেটা পাশের class-এ থাকা উচিত ছিল।
  • "ভবিষ্যত variant"-এর জন্য empty subclass। PremiumUser extends User — empty body, তিন বছর ধরে premium feature-এর অপেক্ষায়। এটা Speculative Generality-র রেখে যাওয়া ফসিল।
  • Wrapper-এর wrapper। OrderFacade calls OrderManager calls OrderService calls repository। প্রায়ই শুধু একটা layer-ই real কাজ করে।

কখন ignore করা ঠিক আছে

সত্যি কথা বলি। ছোট class কখনো কখনো সেরা design, সেগুলো delete করা ভুল হবে। এই judgment table দেখো।

ছোট classরাখব না সরাব?কারণ
Construction-এ validate করা value objectরাখোInvariant guard করছে; safety real কাজ
আলাদা ID type (CustomerId বনাম OrderId)রাখোType system এখন mix-up bug ঠেকাচ্ছে
আজকে ব্যবহার হচ্ছে এমন real test seam সহ interfaceরাখোHard dependency fake করা concrete benefit
External user-দের সাথে published extension pointরাখোবাইরের মানুষ depend করছে; সরালে break হবে
আজকে ছোট কিন্তু actively বাড়ছেরাখো, নজর রাখোএটা চারা, ফসিল না
পুরনো refactoring-এর hollowed-out leftoverসরাওShell কিছু শেখায় না, একটা jump নেয়
কল্পিত ভবিষ্যতের জন্য empty subclassসরাওসেই ভবিষ্যত আসলে তখন যোগ করো
Pure pass-through wrapperসরাওMiddle Man laziness; inline করো
⚠️

যেকোনো class delete করার আগে কে ব্যবহার করছে সেটা check করো। তোমার project-এর ভেতরে unused মনে হওয়া class হয়তো বাইরের কেউ ব্যবহার করছে — অন্য team, plugin, বা published library consumer। আগে published contract search করো, তারপর delete করো। কিন্তু নিজের application code-এ সাহসী হও: version control সব মনে রাখে।

কোন refactoring দিয়ে ঠিক করবে

পরিস্থিতিঠিক করার refactoringফলাফল
কম member-এর class, একটাই main userInline ClassMember-গুলো user-এ যায়; shell delete হয়
প্রায়-ফাঁকা subclass বা superclassCollapse Hierarchyদুই hierarchy level এক হয়ে যায়
ছোট pass-through method বাকি আছেInline MethodIndirection শেষ হয়ে যায়
Class শুধু অন্যটায় delegate করছেRemove Middle ManCaller সরাসরি real worker-এর সাথে কথা বলে
Wrapper-টা বরং বড় হওয়া উচিত ছিলReal behavior যোগ করো (Move Method)Lazy class কাজের value object হয়ে যায়

শেষ row-টা মনে রাখো: কখনো কখনো lazy class-এর cure হলো delete না, promotionPostalCode যদি একটা lazy string-in-a-box হয়, তুমি হয় inline করে সরিয়ে দিতে পারো — অথবা যে validation job তার পাওয়া উচিত ছিল সেটা দিতে পারো। দুটো cure-ই laziness শেষ করে। ফাতেমা আপার committee-রও একই দুটো option ছিল: দারোয়ানের পদ তুলে দাও, অথবা সেটাকে real কাজ দাও — gate register, parcel handling, visitor pass। পুরো বেতন দিয়ে শুধু এক আঙুলের কাজ — এটা justify করার কোনো উপায় ছিল না।

এক নজরে মনে রাখো

+--------------------------------------------------------------+
|  LAZY CLASS — QUICK REVISION                                 |
+--------------------------------------------------------------+
|  Story   : A watchman post kept alive only to press one      |
|            lift button anyone could press themselves.        |
|  Smell   : A class that does too little to justify its       |
|            cost — a shell, a pass-through, an empty child.   |
|  Why bad : Every class charges a fee (name, file, jump).     |
|            Lazy classes pay nothing back and dilute the      |
|            meaning of every real class.                      |
|  Born as : Leftover of refactoring, or speculation that      |
|            never came true.                                  |
|  Test    : "Does it EARN the cost of being separate?"        |
|            Small but protective -> keep. Empty shell -> cut. |
|  Cures   : Inline Class, Collapse Hierarchy, Inline Method,  |
|            Remove Middle Man — or promote it with real work. |
+--------------------------------------------------------------+

অনুশীলন করো

নিচে একটা tuition center-এর program আছে, সন্দেহজনক কিছু "কর্মচারী" আছে। Audit করো।

class StudentName {
  constructor(private readonly name: string) {}
  get(): string { return this.name; }
}
 
class AttendanceMarker {
  constructor(private readonly register: AttendanceRegister) {}
  mark(name: StudentName): void {
    this.register.add(name.get());
  }
}
 
class AttendanceRegister {
  private readonly present: string[] = [];
  add(name: string): void {
    if (name.trim().length === 0) throw new Error("Name cannot be empty");
    this.present.push(name);
  }
  count(): number { return this.present.length; }
}
 
class DigitalAttendanceRegister extends AttendanceRegister {
  // planned: cloud sync. status: not planned anymore.
}
 
// usage
const register = new DigitalAttendanceRegister();
const marker = new AttendanceMarker(register);
marker.mark(new StudentName("Riya"));

তোমার কাজ:

  1. চারটা class আছে। প্রতিটার জন্য test apply করো: "এটা কি আলাদা থাকার খরচ earn করছে?" প্রতিটার জন্য এক বাক্যে judgment লেখো, আর চিত্র ৩-এর quadrant chart-এ রাখো।
  2. StudentName কোনো rule ছাড়া string wrap করছে। এর ভাগ্য ঠিক করো: inline করে সরিয়ে দাও, অথবা AttendanceRegister থেকে empty-name validation StudentName-এর constructor-এ নিয়ে এসে promote করো। তোমার choice implement করো আর এক বাক্যে defend করো।
  3. AttendanceMarker একটাই call forward করছে। Inline Class apply করে সরিয়ে দাও।
  4. DigitalAttendanceRegister একটা cancelled feature-এর empty subclass। Collapse Hierarchy apply করো।
  5. Final, clean usage code লেখো। কতটা class থাকল? "Riya-কে present mark করো" বুঝতে reader-কে কতটা jump করতে হয়? চিত্র ৪-এর চার-jump journey-র সাথে তুলনা করো।
  6. Bonus: একটা situation describe করো যেখানে AttendanceMarker-এর exist করার মানে হতো। (Hint: attendance mark করলে যদি parents-কে notify করতে হতো আর fees update করতে হতো? তাহলে এটা real coordinator হতো, দারোয়ান না।)

তোমার final design-এ যদি প্রতিটা class real, nameable কাজ করে — তুমি laziness cure করেছ, আর ফাতেমা আপা তোমার budget approve করতেন।

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

Lazy Class মানে কী সহজ কথায়?
Lazy Class হলো এমন একটা class যেটা এতটাই কম কাজ করে যে আলাদা class হিসেবে থাকার কোনো মানে নেই। হয়তো একটা ছোট method আছে, অথবা শুধু অন্য class-এ call forward করে দেয়। প্রতিটা class-এর একটা খরচ আছে — নাম মনে রাখো, file খোলো, jump করো — কিন্তু lazy class সেই খরচের বিনিময়ে কিছুই দেয় না।
Lazy Class কীভাবে codebase-এ ঢুকে পড়ে?
সাধারণত দুইভাবে। এক, বাদ পড়া অংশ হিসেবে: কোনো class একসময় কাজ করত, refactoring-এর সময় সব কাজ সরিয়ে নেওয়া হয়েছে, কিন্তু খালি shell-টা থেকে গেছে। দুই, ভবিষ্যতের জন্য বানানো: কেউ একটা feature-এর জন্য class বানিয়ে রেখেছে, সেই feature আর আসেনি, কিন্তু class-টা থেকে গেছে।
Lazy Class ঠিক করতে কোন refactoring ব্যবহার করব?
Inline Class ব্যবহার করে বাকি member-গুলো যে class ব্যবহার করছে তার মধ্যে ঢুকিয়ে দাও, তারপর shell-টা delete করো। Collapse Hierarchy দিয়ে প্রায়-খালি subclass বা superclass-কে তার partner-এর সাথে merge করে দাও যখন inheritance-এর level কোনো কাজেই আসছে না।
ছোট class মানেই কি Lazy Class?
না। ছোট আর lazy এক জিনিস না। একটা ছোট value object যেটা নিজে validate করে, বা একটা type যেটা CustomerId আর OrderId মিশে যাওয়া ঠেকায় — এগুলো তাদের জায়গা earn করে নেয়। প্রশ্নটা 'ছোট কিনা' না, প্রশ্নটা হলো 'আলাদা থাকার খরচটা উসুল হচ্ছে কিনা'।
একটাই implementation আছে এমন interface কি delete করব?
অনেক ক্ষেত্রেই হ্যাঁ, কিন্তু সবসময় না। রেখে দাও যদি এটার আজকে একটা concrete কারণ থাকে — সত্যিকারের test seam, published extension point, বা dependency boundary। Delete করো যখন এটা শুধু কল্পিত ভবিষ্যতের flexibility-র জন্য বানানো হয়েছিল যেটা আর আসেনি।

আরো দেখো

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

Speculative Generality: যে সুইমিং পুলের জন্য পাইপ বসালে, পুলটাই হলো না

বাড়ি বানানোর গল্প দিয়ে Speculative Generality smell বোঝো। YAGNI কী, ভবিষ্যতের অনুমানে কোড লেখা কেন ক্ষতিকর, আর অব্যবহৃত abstraction কীভাবে সরাতে হয় — সব পরিষ্কার হয়ে যাবে।

আরও পড়ুন

Data Class: নিয়মহীন রেজিস্টার — যে কেউ যা খুশি লিখে যায়

Data Class smell শেখো একটা society register-এর গল্পের মাধ্যমে। দেখো কেন behavior ছাড়া data encapsulation ভেঙে পড়ে, আর কখন DTO আর record একদম ঠিকঠাক।

আরও পড়ুন

Middle Man: যে helper শুধু তোমার message পৌঁছে দেয়, নিজে কিছু করে না

Middle Man code smell টা বোঝো একটা school-এর সেই পিয়নের গল্প দিয়ে — যে শুধু চিরকুট বহন করে, নিজে কিছু যোগ করে না। যখন একটা class শুধু সব call forward করে, সেটা সরিয়ে দাও। কিন্তু Proxy, Facade, আর Adapter কেন জেনেশুনে middle man হয় — সেটাও জানো।

আরও পড়ুন

Dead Code: গুদামঘরে পুরনো জিনিস 'যদি লাগে' বলে আটকে রাখা

Dead Code smell শেখো একটা গুদামঘরের গল্প দিয়ে। দেখো কেন না-চলা কোড আসলে টাকা আর সময় নষ্ট করে, Knight Capital-এর ৪৪০ মিলিয়ন ডলারের ঘটনা থেকে শিখো, আর সহজভাবে সমাধান করো।

আরও পড়ুন