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

Divergent Change: এক বেচারা কেরানি, অনেক বস

Divergent Change code smell শেখো একটা school-এর কেরানির গল্পের মাধ্যমে — সহজ সংজ্ঞা, TypeScript ও C# example, Shotgun Surgery-র সাথে তুলনা, আর practice exercise।

24 মিনিট আপডেট: June 11, 2026beginner
divergent changecode smellschange preventerssingle responsibilityextract classtypescript

রাহিম ভাইয়ের গল্প — স্কুলের একমাত্র কেরানি

ধরো তুমি একটা স্কুলে গেছো। সেই স্কুলে মাত্র একজন কেরানি — রাহিম ভাই। ছোট্ট একটা টেবিল, পুরনো একটা কম্পিউটার, আর এক কাপ চা — যেটা কখনো গরম থাকে না। কেন? কারণ সবাই সারাদিন রাহিম ভাইকে ডাকছে।

সকাল ৯টায় পরীক্ষার দায়িত্বে থাকা ম্যাডাম আসলেন। "রাহিম ভাই, বোর্ড marksheet format বদলে দিয়েছে। সব marksheet আপডেট করো।" রাহিম ভাই মাথা নাড়লেন, marksheet file খুললেন।

১০টায়, শেষ করার আগেই, হিসাবের স্যার এলেন। "রাহিম ভাই, fee structure বদলেছে। late fine এখন ৫০ টাকা, ২০ না। fee receipt ঠিক করো।" রাহিম ভাই একটা দীর্ঘশ্বাস ফেললেন। marksheet file বন্ধ, fee register খোলা।

১১টায় sports স্যার দৌড়ে এলেন। "রাহিম ভাই! বার্ষিক sports day! winner-দের জন্য trophy order আর certificate লাগবে!" রাহিম ভাই fee register বন্ধ করলেন, sports file খুললেন।

১২টায় পরীক্ষার ম্যাডাম আবার এলেন। "রাহিম ভাই, marksheet হয়েছে?" অবশ্যই হয়নি। কোনোটাই শেষ হয় না — কারণ তিনটা department তাকে নিজেদের personal helper ভাবে।

বিকেল ২টায় আরও বড় বিপদ। fee receipt তাড়াতাড়ি লিখতে গিয়ে রাহিম ভাই একটা fee number এক marksheet-এ লিখে ফেললেন। টেবিলে সব কাগজ একসাথে ছিল কিনা! পরের দিন পরীক্ষার ম্যাডাম ধরলেন, আর সবচেয়ে পরিশ্রমী মানুষটাই বকা খেলেন।

সন্ধ্যায় রাহিম ভাই তার ডায়েরিতে লিখলেন: "আজকেও তিন বস, এক টেবিল, শূন্য শেষ করা কাজ।"

পুরো দিনটা এক ছবিতে দেখো — আর মনের mood কীভাবে পড়ে যাচ্ছে দেখো:

Figure 1: এক কেরানির সারাদিন — প্রতিটা department এসে একই টেবিলে পড়ছে, মন ক্রমশ খারাপ হচ্ছে

একটু ভাবো। রাহিম ভাই কি খারাপ কর্মী? না। সমস্যা হলো school office-এর design। তিনটা আলাদা কাজ — পরীক্ষা, হিসাব, sports — সব একজনের কাছে যাচ্ছে। যেকোনো department-এ যেকোনো change হলে একই টেবিলে এসে পড়ছে।

যদি তিনটা আলাদা টেবিল থাকতো — exam কেরানি, হিসাব কেরানি, sports কেরানি — তাহলে প্রতিটা change সঠিক টেবিলে যেতো। marksheet বদলানো? শুধু exam desk। fee বদলানো? শুধু হিসাব desk। কারো চা ঠান্ডা হতো না, আর fee number কখনো marksheet-এ যেতো না — কারণ fee কাগজ আর marksheet কখনো একই টেবিলে থাকতো না।

আমাদের code-এও ঠিক রাহিম ভাইয়ের মতো class থাকে। একটা class exam-এর কারণে, fee-র কারণে, sports-এর কারণে বদলাচ্ছে। এই smell-এর নাম: Divergent Change

এই smell আসলে কী?

Divergent Change হয় যখন একটা class অনেক আলাদা আলাদা কারণে বারবার বদলাতে হয়।

"divergent" মানে "ভিন্ন দিকে যাওয়া"। এই class-এ যে change আসে সেগুলো diverge করে — একদম আলাদা আলাদা দিক থেকে আসে। tax rule বদলানো, database বদলানো, email format বদলানো — সব একই file খুলছে।

Martin Fowler তার বিখ্যাত বই Refactoring-এ এই smell describe করেছেন। তার simple test হলো এই: যখন তুমি বলছো "database বদলালে এই তিনটা method বদলাতে হয়, নতুন payment type আসলে ওই চারটা method বদলাতে হয়" — তখন class-টা Divergent Change-এ ভুগছে। দুটো আলাদা ভবিষ্যৎ একই class টানছে।

এই smell একটা পরিবারের অংশ যাকে বলে Change Preventers। এগুলো হলো সেই smell যেগুলো change কে কষ্টকর করে তোলে। code তোমাকে বদলাতে আটকায় না — কিন্তু প্রতিটা change কে ধীর, ভয়ের, আর ব্যয়বহুল করে তোলে। তাই দলের সবাই আস্তে আস্তে change এড়াতে শুরু করে। এটাই আসল ক্ষতি।

এখানে একটা গভীর principle লুকিয়ে আছে। Single Responsibility Principle (SRP) বলে: একটা class-এর পরিবর্তনের কারণ শুধু একটাই হওয়া উচিত। Divergent Change হলো SRP ভাঙলে real জীবনে কেমন লাগে সেটা। তুমি diagram-এ SRP ভাঙতে দেখবে না — অনুভব করবে যখন একই বেচারা class প্রতি ধরনের pull request-এ বারবার আসবে, ঠিক রাহিম ভাইয়ের মতো যাকে প্রতিটা department ডাকে।

University corner: SRP-র wording টা লক্ষ্য করো। Uncle Bob বলেননি "একটা class শুধু একটা কাজ করবে" — বলেছেন শুধু একটাই কারণ থাকবে বদলানোর জন্য, আর পরে আরও sharpen করেছেন: "একটা module শুধু একটা actor-এর কাছে দায়ী থাকবে।" Actor মানে হলো সেই group of people যারা change চায়: exam team, accounts team, sports team। Divergent Change হলো ঠিক সেই situation যেখানে multiple actor একটা module share করছে। ডিজাইন document লেখার সময় SRP check করতে function গুনো না — actor গোনো। এক class, এক actor — এটাই লক্ষ্য। রাহিম ভাইয়ের ছিল তিনটা।

💡

সহজ মনে রাখার উপায়: Divergent Change = এক রাহিম ভাই, অনেক বস। এক class, অনেক কারণে বদলায়। সমাধান হলো প্রতিটা বস-কে তার নিজস্ব কেরানি দাও — class ভেঙে দাও যেন প্রতিটা কারণের নিজের ঘর হয়।

আরেকটা গুরুত্বপূর্ণ কথা। Divergent Change code-এর একটা screenshot দেখে বোঝা কঠিন। এটা ইতিহাসের smell। একটা class দেখতে "একটু বড়" মনে হতে পারে। কিন্তু যদি git log খুলে দেখো "fix tax rounding", "change email template", "switch to new database driver" — সব একই file-এ — তখন smell টা জোরে জোরে চিৎকার করছে।

এই smell-এর পুরো ধারণাটা এক map-এ দেখো:

Figure 2: Divergent Change-এর mind map — লক্ষণ, ক্ষতি, সমাধান, আর উল্টো smell

কীভাবে চিনবে

একটা class-এ কী ধরনের change আসছে সেটা দেখে Divergent Change চেনা যায়। এই checklist দেখো। তিনটার বেশি tick হলে এই smell আছে বলে ধরে নাও।

  • আলাদা ধরনের requirement বদলালে সবসময় একই class খুলতে হয়।
  • class-এর git history পড়লে মনে হয় পুরো product-এর changelog, একটা feature-এর না।
  • ভেতরের method গুলো আলাদা "দ্বীপ" তৈরি করেছে — একটা group database-এর সাথে কথা বলে, আরেকটা text format করে, আরেকটা message পাঠায় — আর island গুলো প্রায় কোনো field share করে না।
  • class-এর নামটা vague: Manager, Processor, Handler, Service, Util। এত কাজ cover করতে গিয়ে নামটা vague হতে বাধ্য হয়েছে।
  • file-এর উপরে import list দেখতে পুরো system-এর tour-এর মতো — networking, storage, formatting, math সব একসাথে।
  • "and" ছাড়া class টাকে এক বাক্যে describe করতে পারছো না।

এক নজরে দেখার জন্য একটা table:

লক্ষণকী দেখছোমানে কী
Magnet fileআলাদা pull request বারবার একই file ছুঁয়ে যাচ্ছেএক জায়গায় অনেক কারণ বাস করছে
Method islandmethod-এর group আছে যারা কোনো field share করে নাএকটার ভেতরে কয়েকটা লুকানো class আটকে আছে
Vague nameOrderManager, StudentHelper, DataServiceনামটা এত কাজ cover করতে গিয়ে নির্দিষ্ট হতে পারেনি
Import zooDB, HTTP, email, formatting import এক file-এclass system-এর সব কোণে যোগাযোগ করছে
Re-test ভয়ছোট্ট fee change-এ exam test পুনরায় চালাতে হচ্ছেresponsibility গুলো জড়িয়ে আছে, আলাদা না
"and" test"এটা fee calculate করে AND marksheet print করে AND..."একের বেশি responsibility, নিশ্চিত

real project-এ একটা practical tip: git log --oneline -- path/to/File.ts চালাও আর commit message পড়ো। যদি message গুলো তোমার product-এর তিনটা আলাদা department-এর কথা বলে, তুমি তোমার রাহিম ভাইকে খুঁজে পেয়েছো।

প্রমাণটা ছবিতেও আঁকতে পারো। যে class-টাকে সন্দেহ করছো তার শেষ বিশটা commit নাও আর সেগুলোকে কারণ অনুযায়ী ভাগ করো। সুস্থ class-এর চার্ট বোরিং হয় — একটা বড় slice। রাহিম ভাইয়ের class-এর চার্ট দেখতে রঙিন pizza-র মতো:

Figure 3: একটা class-এর শেষ ২০টা commit কারণ অনুযায়ী ভাগ করো — এরকম রঙিন pie-ই এই smell-এর আঙুলের ছাপ

তোমার pie-তে যদি একটা বড় slice থাকে — relax। class-এর এক মালিক। কিন্তু উপরের মতো তিন-চারটা সমান slice থাকলে, যেখানে কারণগুলো সম্পূর্ণ আলাদা — class অনেক বস-এর চাকর হয়ে গেছে।

কেন সমস্যা

তুমি বলতে পারো: "তো কী হলো যদি এক class অনেক কাজ করে? code তো চলছে!" সত্যি। কিন্তু মনে রাখো — Change Preventers program ভাঙে না। এরা দলের speed আর confidence ভাঙে। ধাপে ধাপে cost দেখো।

১. প্রতিটা edit ঝুঁকিপূর্ণ। fee logic বদলাতে গিয়ে marksheet code আর sports code-এর পাশ দিয়ে scroll করতে হচ্ছে। চোখ ক্লান্ত হয়ে যাচ্ছে। একটা ভুল line আর exam ভেঙে যাচ্ছে fee ঠিক করতে গিয়ে। আলাদা জিনিস একসাথে থাকলে আলাদা জিনিস একসাথে ভাঙে — ঠিক রাহিম ভাইয়ের ঠাসা টেবিলে fee number marksheet-এ উঠে গেলে যেমন হয়।

২. সব জায়গায় merge conflict। একটা দলে exam developer আর accounts developer একই সপ্তাহে একই file edit করছে। Git কাঁদছে। সময় নষ্ট হচ্ছে এমন conflict সামলাতে যাদের আসলে দেখা হওয়ারই কথা না।

৩. Test ধীর আর মোটা হয়ে যাচ্ছে। fee calculation একা test করা যাচ্ছে না, কারণ সেটা database code আর email code-এর সাথে জড়িয়ে আছে। তাই test চালাতে database লাগছে, mail server লাগছে, সব লাগছে। ধীর test skip করা হয়। skip করা test মানে bug।

৪. কিছুই reuse করা যাচ্ছে না। marksheet formatting-এর সুন্দর logic অন্য project-এ ব্যবহার করা যাচ্ছে না, কারণ সেটা fee logic আর trophy logic-এর সাথে একই class-এ আটকে আছে।

৫. নতুন developer কষ্ট পাচ্ছে। একটা ছোট জিনিস safely বদলাতে হলে পুরো ৮০০ লাইনের class বুঝতে হবে। একটা tiny change-এর learning cost বিশাল হয়ে যাচ্ছে।

Figure 4: আলাদা আলাদা কারণ একটা class টানছে — প্রতিটা department-এর change একই file-এ এসে পড়ছে

শেষ box টা লক্ষ্য করো: দল change এড়িয়ে চলে। এটাই শেষ, নীরব ক্ষতি। একটা file বদলানো ভয়ের মনে হলে মানুষ সেটা improve করা বন্ধ করে দেয়। class পচতে থাকে, বাড়তে থাকে, আর তোমার project-এর বিখ্যাত "ওই file-এ কেউ হাত দিস না" হয়ে ওঠে।

cost নম্বর ২ — merge conflict — এর নিজস্ব ছবি দরকার, কারণ college-এ প্রথম group project করার সময় এই cost-ই সবার আগে টের পাওয়া যায়। দুই teammate, দুটো সম্পূর্ণ আলাদা ticket, একটা shared class:

Figure 5: দুই developer, দুটো আলাদা কারণ, একটা class — আর Git খারাপ খবর দিচ্ছে

University corner: এখানেই textbook-এর coupling আর cohesion শব্দ দুটো real হয়ে ওঠে। Cohesion মাপে একটা module-এর ভেতরের অংশগুলো কতটা একে অপরের সাথে সম্পর্কিত। Divergent Change class-এর cohesion কম: exam method, fee method, আর sports method একটা flat share করা অপরিচিত মানুষের মতো। আর যেহেতু ওই কাজগুলোর প্রতিটার consumer-কে পুরো class-এর উপর depend করতে হচ্ছে, class আবার high coupling-ও তৈরি করছে — fee module-এর callers accidentally exam code-এর সাথেও coupled হয়ে যাচ্ছে যেটা তারা কখনো ব্যবহার করে না। "high cohesion, low coupling" — এই classic design goal টা আসলে formal ভাষায় রাহিম ভাইয়ের গল্প: প্রতিটা কাজকে নিজের টেবিল দাও (cohesion), যাতে department গুলো আর একে অপরের সাথে ধাক্কা না খায় (coupling)।

একটা real code example

রাহিম ভাইয়ের টেবিলকে code-এ লেখা যাক। এই হলো স্কুলের software, সব কিছু এক class-এ ঢেলে দেওয়া — ঠিক যেভাবে real project-এ বাড়ে, একটা "ছোট" method এক এক করে।

// SchoolClerk.ts — the poor class everyone disturbs
class SchoolClerk {
  // ----- Reason to change #1: EXAM rules -----
  prepareMarksheet(student: Student): string {
    const total = student.marks.reduce((sum, m) => sum + m.score, 0);
    const percent = (total / (student.marks.length * 100)) * 100;
    const grade = percent >= 90 ? "A+" : percent >= 75 ? "A" : "B";
    return `MARKSHEET\n${student.name}\nPercent: ${percent.toFixed(1)}\nGrade: ${grade}`;
  }
 
  // ----- Reason to change #2: FEE rules -----
  generateFeeReceipt(student: Student, paidOn: Date): string {
    const baseFee = 1200;
    const dueDate = new Date(paidOn.getFullYear(), paidOn.getMonth(), 10);
    const lateFine = paidOn > dueDate ? 50 : 0; // accounts sir changes this often!
    return `RECEIPT\n${student.name}\nFee: ${baseFee}\nFine: ${lateFine}\nTotal: ${baseFee + lateFine}`;
  }
 
  // ----- Reason to change #3: SPORTS events -----
  orderTrophies(eventName: string, winners: Student[]): string[] {
    return winners.map(
      (w, rank) => `Trophy: ${eventName} - Rank ${rank + 1} - ${w.name}`
    );
  }
 
  // ----- Reason to change #4: STORAGE -----
  saveRecord(student: Student): void {
    // talks to the database — changes when the school changes its software vendor
    db.execute("INSERT INTO students ...", student);
  }
}

comment গুলো দেখো। চারটা আলাদা department চারটা আলাদা অংশের মালিক। পরীক্ষার ম্যাডাম prepareMarksheet বদলাতে বলবেন। হিসাবের স্যার generateFeeReceipt-এর মালিক। sports স্যার orderTrophies-এর। আর IT vendor saveRecord-এর। চার বস, এক class। এটাই textbook Divergent Change।

এই situation টা class diagram-এ দেখো — বাঁ দিকে সমস্যা, ডান দিকে সমাধান। overloaded কেরানি কিছু delegate করছে না; সুস্থ office-এ প্রতিটা কারণের জন্য একটা ছোট্ট desk আছে:

Figure 6: overloaded class বনাম সেটা যে চারটা ছোট desk হওয়া উচিত

বিপদটা real। ধরো হিসাবের স্যার বললেন "fine এখন ৭৫ টাকা"। তুমি file খুলবে, একটা number বদলাবে — আর editor-এর auto-format হয়তো accidentally marksheet method-ও ছুঁয়ে যাবে। এখন একটা fee change-এর জন্য exam test-ও চালাতে হবে "just in case"। সব connected কারণ সব একসাথে থাকছে।

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

সমাধান হলো class-কে change-এর কারণ অনুযায়ী ভাগ করা। প্রতিটা কারণ তার নিজের ছোট class পাবে। মূল tool হলো Extract Class, সাথে Move Method আর Move Field। আস্তে আস্তে করো — একটা responsibility এক বার, test চালাও, তারপর পরের টা।

ধাপ ১ — কারণগুলো লেখো। class পড়ো আর প্রতিটা method-এর জন্য জিজ্ঞেস করো: "কে আমাকে এটা বদলাতে বলবে?" উত্তর লিখে রাখো। SchoolClerk-এর জন্য পাচ্ছো: exam team, accounts, sports, IT vendor। চারটা কারণ। তাই চারটা ঘর বানাতে হবে।

ধাপ ২ — প্রথম responsibility বের করো। একটা cluster বেছে নাও — ধরো exam — আর Extract Class দিয়ে তাকে নিজের ঘর দাও:

// ExamOffice.ts — owned by the exam team only
class ExamOffice {
  prepareMarksheet(student: Student): string {
    const total = student.marks.reduce((sum, m) => sum + m.score, 0);
    const percent = (total / (student.marks.length * 100)) * 100;
    const grade = this.gradeFor(percent);
    return `MARKSHEET\n${student.name}\nPercent: ${percent.toFixed(1)}\nGrade: ${grade}`;
  }
 
  private gradeFor(percent: number): string {
    return percent >= 90 ? "A+" : percent >= 75 ? "A" : "B";
  }
}

তারপর SchoolClerk-এ পুরনো method শুধু delegate করবে: prepareMarksheet(s) শুধু this.examOffice.prepareMarksheet(s) return করবে। test চালাও। সব কাজ করছে, কিন্তু exam logic এখন নিজের ঘরে।

ধাপ ৩ — বাকি কারণগুলোর জন্য repeat করো। Move Method দিয়ে fee logic AccountsDesk-এ, trophy logic SportsDesk-এ, আর saving logic StudentRepository-তে নিয়ে যাও। কোনো method যদি সাথে data টানে (যেমন base fee amount), তাহলে Move Field দিয়ে data-কেও নতুন জায়গায় নাও।

// After the full split: each class has exactly ONE reason to change
class AccountsDesk {
  private readonly baseFee = 1200;
  private readonly lateFine = 50;
  generateFeeReceipt(student: Student, paidOn: Date): string { /* fee logic only */ }
}
 
class SportsDesk {
  orderTrophies(eventName: string, winners: Student[]): string[] { /* sports only */ }
}
 
class StudentRepository {
  saveRecord(student: Student): void { /* storage only */ }
}

ধাপ ৪ — পুরনো class-এর ভবিষ্যৎ ঠিক করো। কখনো কখনো original class একটা পাতলা coordinator হয়ে যায় যে শুধু চারটা desk ধরে রাখে — সেটাও ঠিক আছে। কখনো সে খালি হয়ে যায়, delete করে দাও। দুটো ending-ই সুখের।

ধাপ ৫ — এক বাক্যের test করো। প্রতিটা নতুন class-কে এক বাক্যে describe করো। "AccountsDesk fee আর fine calculate করে।" "SportsDesk trophy order তৈরি করে।" আলাদা কাজ জোড়া দেওয়ার জন্য "and" লাগছে না? হয়ে গেছে।

class-এর পুরো জীবনকাল — সুস্থ থেকে overloaded থেকে সারানো — এই diagram-এ দেখো:

Figure 7: Divergent Change class-এর জীবনচক্র — দায়িত্ব আস্তে আস্তে জমে, সমাধান হলো সচেতনভাবে ভাগ করা

আর ফলাফল পরিমাপ করা যায়। ভাগ করার আগে আর পরে প্রতিটা class-এ কতটা আলাদা কারণ edit force করতে পারে সেটা গোনো:

Figure 8: সংখ্যায় ভাগ — class-প্রতি change-এর কারণ চার থেকে এক হয়ে যায়

এখন পরের মাসে কী হয় দেখো। হিসাবের স্যার বললেন fine ৭৫ টাকা। তুমি AccountsDesk খুললে, একটা number বদলালে, শুধু fee test চালালে, আর সময়মতো বাড়ি গেলে। exam code খোলাই হলো না। এটাই পুরো পুরস্কার।

University corner: John Ousterhout তার A Philosophy of Software Design বইয়ে এই যন্ত্রণার একটা নির্দিষ্ট নাম দিয়েছেন: change amplification — যখন একটা simple change অনেক জায়গায় modification লাগায়, বা change-এর চেয়ে অনেক বেশি code বুঝতে বাধ্য করে। Divergent Change বোঝার amplification করে: এক লাইন fee edit করতে তোমাকে ৮০০ লাইনের exam আর sports code বুঝতে হচ্ছে (বা সাবধানে এড়িয়ে যেতে হচ্ছে)। ভাগ করার পরে, একটা change বুঝতে ঠিক ততটুকুই বুঝতে হয় যতটুকু সেই change-এর আকার। change amplification কমানো হলো "ভালো design" এর সবচেয়ে practical definition।

⚠️

বেশি ভাগ করো না! যদি বিশটা ছোট class বানাও যেগুলো সবসময় একসাথে বদলায়, তাহলে উল্টো smell তৈরি হবে — Shotgun Surgery। ভাগ করো change-এর কারণ অনুযায়ী, method-এর সংখ্যা অনুযায়ী না। দুটো method যদি সবসময় একসাথে বদলায়, তারা একসাথে থাকবে।

C# আর Python-এ একই smell

C#-এ দেখতে একদম একই। এক compact version — একটা OrderProcessor যেটা pricing কারণে, storage কারণে, আর email কারণে বদলায়:

// Before: three reasons to change, one class
public class OrderProcessor
{
    public decimal CalculateTotal(Order order)   // changes when tax rules change
    {
        var subtotal = order.Lines.Sum(l => l.Price * l.Qty);
        return subtotal + subtotal * 0.18m; // GST
    }
 
    public void Save(Order order)                // changes when the database changes
    {
        using var conn = new SqlConnection(_cs);
        conn.Execute("INSERT INTO Orders ...", order);
    }
 
    public void SendConfirmation(Order order)    // changes when email design changes
    {
        _smtp.Send(order.Email, "Your order", RenderHtml(order));
    }
}

change-এর কারণ অনুযায়ী ভাগ করার পরে:

// After: one reason per class
public class PricingCalculator { public decimal CalculateTotal(Order o) { /* tax only */ } }
public class OrderRepository   { public void Save(Order o) { /* storage only */ } }
public class OrderNotifier     { public void SendConfirmation(Order o) { /* email only */ } }

GST বদলালে এখন শুধু PricingCalculator ছুঁতে হবে, শুধু তার test চালাতে হবে। database team আর email team আর কখনো tax team-এর সাথে ধাক্কা খাবে না।

Python student, তুমিও বাদ যাচ্ছো না — dynamic typing এই smell লুকাতে পারে না। Django বা Flask project-এ একই রাহিম ভাই বাড়তে থাকে, সাধারণত utils.py বা helpers.py নামে:

# student_service.py — Before: one class, three bosses
class StudentService:
    def prepare_marksheet(self, student):       # exam team owns this
        percent = sum(m.score for m in student.marks) / len(student.marks)
        grade = "A+" if percent >= 90 else "A" if percent >= 75 else "B"
        return f"MARKSHEET\n{student.name}\n{percent:.1f}% — {grade}"
 
    def fee_receipt(self, student, paid_on):    # accounts owns this
        fine = 50 if paid_on.day > 10 else 0
        return f"RECEIPT\n{student.name}\nTotal: {1200 + fine}"
 
    def order_trophies(self, event, winners):   # sports owns this
        return [f"Trophy: {event} - Rank {i + 1} - {w.name}"
                for i, w in enumerate(winners)]
 
# After: one small class per reason — ExamOffice, AccountsDesk, SportsDesk

ভাষা বদলায়, diagnosis-এর প্রশ্ন বদলায় না: কে প্রতিটা method বদলাতে বলবে? উত্তর আলাদা হলে class ভাগ করতে হবে।

Divergent Change বনাম Shotgun Surgery

Change Preventers পরিবারের সবচেয়ে গুরুত্বপূর্ণ তুলনা এটা। student-রা এই দুটো সবসময় গুলিয়ে ফেলে। সহজে মনে রাখো: এই দুটো একদম উল্টো।

প্রশ্নDivergent ChangeShotgun Surgery
যন্ত্রণার রূপONE class, MANY কারণে বদলায়ONE কারণ, MANY class বদলাতে হয়
গল্পের versionএক কেরানি, সব department বিরক্ত করছেএক address change, দশটা office-এ যেতে হচ্ছে
কী ভুলclass-এ কাজ বেশিকাজটা codebase-এ ছড়িয়ে আছে
কখন টের পাচ্ছোপ্রতি ধরনের PR-এ একই file"ছোট" change কিন্তু দশটা file খুঁজতে হচ্ছে
সমাধানের দিকভাগ করো — class ভেঙে দাওজড়ো করো — ছড়ানো piece এক জায়গায় আনো
মূল refactoringExtract Class, Move MethodMove Method, Move Field, Inline Class
কোন principle ভাঙছেSingle Responsibility (অনেক বেশি কারণ)Single ownership (এক concept-এর ঘর নেই)
বেশি ঠিক করলেবেশি ভাগ করলে Shotgun Surgery হয়বেশি জড়ো করলে Divergent Change হয়
Figure 9: উল্টো দুটো smell — এক class-এ অনেক কারণ, বনাম এক কারণ অনেক class-এ

diagnosis-এর প্রশ্নটা beautifully symmetric। দুটো জিনিস জিজ্ঞেস করো:

১. এক class-এ কত ধরনের change আসছে? অনেক হলে → Divergent Change → ভাগ করো। ২. এক ধরনের change-এ কতটা class edit করতে হচ্ছে? অনেক হলে → Shotgun Surgery → জড়ো করো।

যেকোনো সন্দেহজনক situation একটা সহজ map-এ দেখতে পারো। অনুভূমিক axis-এ কতটা class বদলাচ্ছে; উলম্ব axis-এ এক class-এ কতটা কারণ। সুস্থ code শান্ত কোণায়; প্রতিটা smell তার নিজের কোণায়; আর হ্যাঁ, সত্যিই দুর্ভাগ্যজনক codebase একসাথে দুটোই manage করে:

Figure 10: diagnosis map — তোমার situation plot করো আর smell পড়ো

আমাদের SchoolClerk বাঁ দিকে উপরে: এক class, অনেক কারণ — Divergent Change। দশটা file-এ ছড়ানো address format নিচে ডানে — Shotgun Surgery। আর একটা সুস্থ PricingEngine যেটা শুধু pricing কারণে বদলায়, একা বদলায় — সে শান্ত নিচে-বাঁয়ের কোণায়।

আর table-এর শেষ row-এর warning টা মনে রাখো। দুটো সমাধান উল্টো দিকে টানে। যদি বেশি enthusiastically ভাগ করো, সম্পর্কিত code ছড়িয়ে যাবে আর প্রতিটা change multi-file hunt হয়ে যাবে — এক smell-কে তার mirror-এ trade করলে। লক্ষ্য হলো balance: প্রতিটা class এক responsibility-র মালিক, আর প্রতিটা responsibility এক class-এ বাস করে।

real project-এ কোথায় লুকিয়ে থাকে

Divergent Change কিছু নির্দিষ্ট জায়গায় লুকিয়ে থাকতে ভালোবাসে। Fowler-এর Refactoring, refactoring.guru, আর অনেক code-quality blog একই জায়গায় বারবার পাচ্ছে:

  • "God" service class। UserService, OrderManager, AppHelper — vague নামের class যেখানে সবাই code dump করে গেছে। অনেক codebase-এ এরকম একটা class চুপচাপ validation, formatting, persistence, আর notification সব handle করছে।
  • Web app-এর controller। যে controller input validate করছে, business rule apply করছে, database-এ কথা বলছে, আর JSON shape করছে — সে চারটা আলাদা কারণে বদলায়। Framework tutorial প্রায়ই এই shape শেখায়, তাই ছড়িয়ে পড়ে।
  • Utility class। Utils.ts আর Helpers.cs by design Divergent Change — একটা drawer যেখানে প্রতিটা department তার বেমানান জিনিস ফেলে যায়।
  • Configuration আর startup file। একটা বিশাল startup class যেটা logging, database, security, আর caching wire করছে — সেগুলোর যেকোনোটা বদলালে এই class বদলাতে হয়।
  • দীর্ঘজীবী legacy class। System-এর সবচেয়ে পুরনো class gravity-র মতো code টানে। "আরেকটা method এখানে add করে দিই" — এই সহজ পথ পাঁচ বছর ধরে নেওয়া হলে perfect রাহিম ভাই তৈরি হয়।

real team-এ ব্যবহৃত একটা practical detection trick: tool দিয়ে file গুলো distinct author-এর সংখ্যা আর commit-এর সংখ্যা দিয়ে rank করো। এই দুটোতে উপরে থাকা file সাধারণত Divergent Change-এর suspect — কারণ অনেক মানুষ অনেক ticket-এর জন্য একই file বদলাচ্ছে, এটাই smell-এর signature।

কখন উপেক্ষা করা ঠিক আছে

ভালো engineer সৎ: প্রতিটা smelly class আজই সার্জারির দরকার নেই। সৎ table দেখো।

Situationউপেক্ষা করবে?কারণ
class খুব কমই edit হয় (দুই বছরে দুইবার)হ্যাঁsmell theoretical; ভাগ করলে কোনো real লাভ নেই
Early prototype, requirement এখনো fuzzyহ্যাঁ, এখনকে জন্যসীমানা এখনো দেখা যাচ্ছে না; আগেভাগে ভাগ করলে ভুল ভাগ হবে
"আলাদা" responsibility গুলো সবসময় একসাথে বদলায়হ্যাঁএগুলো আসলে এক responsibility; ভাগ করলে artificial seam তৈরি হবে
ছোট script বা throwaway toolহ্যাঁছোট code-এ indirection-এর cost লাভের চেয়ে বেশি
file প্রতি sprint-এ আলাদা PR-এ আসছেনা — ঠিক করোsmell active আর প্রতি সপ্তাহে সুদ নিচ্ছে
দুটো team একই class-এ বারবার merge conflict করছেনা — ঠিক করোভাগ করলে team ownership-ও ভাগ হয়, conflict শেষ হয়

rule of thumb: edit history সিদ্ধান্ত নিতে দাও। Code smell hint, আইন না। অনেক responsibility আছে কিন্তু কখনো বদলায় না এমন class একটা academic সমস্যা। যে class আলাদা আলাদা commit-এ বারবার আসছে সেটা ব্যয়বহুল, active সমস্যা — ওটাই আগে ঠিক করো।

কোন refactoring ঠিক করে

Refactoringএখানে কী করেকখন ব্যবহার করবে
Extract Classএক পুরো responsibility নতুন, focused class-এ নিয়ে যায়মূল সমাধান — প্রতিটা change-এর কারণের জন্য একবার
Move Methodএকটা method তার কারণ যেখানে থাকে সেখানে নিয়ে যায়যখন method স্পষ্টতই একটা already-extracted ঘরে মানায়
Move Fielddata-কে সেই behavior-এর কাছে নিয়ে যায় যেটা ব্যবহার করেযখন extracted method পুরনো field-এর জন্য পেছনে হাত বাড়ায়
Extract Functionclass-level সার্জারির আগে multi-job method ভাগ করেযখন জট individual method-এর ভেতরেও আছে
Extract Superclass / Subclasssplit class গুলো organize করে যারা common behavior share করেযখন নতুন class গুলো natural sibling হয়ে ওঠে

ছোট, safe ধাপে কাজ করো: এক responsibility extract করো, test চালাও, commit করো, repeat। পুরো split এক বিশাল ঝুঁকিপূর্ণ change-এ করো না — এটা হবে ironically ওই smell-এর সাথে, যার পুরো শিক্ষাটাই ছিল change safe করা।

দ্রুত revision

+--------------------------------------------------------------+
|              DIVERGENT CHANGE — QUICK REVISION               |
+--------------------------------------------------------------+
| Story    : One school clerk, disturbed by EVERY department   |
| Smell    : ONE class changed for MANY different reasons      |
| Family   : Change Preventers                                 |
| Breaks   : Single Responsibility Principle (SRP)             |
| Spot it  : Same file in unrelated PRs; vague name; method    |
|            islands; import zoo; fails the one-sentence test  |
| Costs    : Risky edits, merge conflicts, slow tests,         |
|            no reuse, team afraid to change the file          |
| Cure     : SPLIT the class -> Extract Class, Move Method,    |
|            Move Field (one responsibility per class)         |
| Opposite : Shotgun Surgery (one reason, many classes)        |
| Memory   : Divergent = one desk, many bosses -> SPLIT        |
| Ignore   : If the class is stable and rarely edited          |
+--------------------------------------------------------------+

Practice করো

চলো তুমি সেই principal হও যে office ঠিক করে!

Exercise ১ — কারণ খোঁজো। নিচে একটা ছোট্ট দোকানের software-এর class আছে। প্রতিটা change-এর কারণ খোঁজো, আর বলো কোন "department" কোনটার মালিক।

class ShopAssistant {
  calculateBill(items: Item[]): number {
    const total = items.reduce((s, i) => s + i.price * i.qty, 0);
    return total > 1000 ? total * 0.95 : total; // 5% festival discount
  }
  printLabel(item: Item): string {
    return `${item.name} — Rs.${item.price} (MRP incl. all taxes)`;
  }
  reorderStock(item: Item): void {
    if (item.qty < 5) supplierApi.placeOrder(item.name, 50);
  }
  sendOfferSms(customer: Customer): void {
    smsGateway.send(customer.phone, "Festival sale! 5% off above Rs.1000");
  }
}

Exercise ২ — ভাগ করার plan করো। প্রতিটা কারণের জন্য একটা নতুন class-এর নাম propose করো (এক বাক্যে তার single কাজ describe করো)। তিনটা বা চারটা ছোট class পাওয়া উচিত।

Exercise ৩ — একটা extraction করো। শুধু billing responsibility-র জন্য Extract Class actually করো। ShopAssistant যেন তোমার নতুন class-কে delegate করে আর পুরনো সব caller কাজ করতে থাকে।

Exercise ৪ — pie আঁকো। তোমার লেখা যেকোনো project খোলো (college assignment হলেও চলবে) আর সবচেয়ে বড় class বেছে নাও। method গুলো sort করো কে সেটা বদলাতে বলবে অনুযায়ী। Figure 3-এর pie chart তোমার class-এর জন্য sketch করো। এক বড় slice? সুস্থ। রঙিন pizza? এখন জানো কী করতে হবে।

Exercise ৫ — diagnosis drill। নিচের প্রতিটা situation-এর জন্য বলো এটা Divergent Change, Shotgun Surgery, না কোনোটাই না:

১. একটা status value rename করতে ১১টা file বদলাতে হচ্ছে। ২. InvoiceManager class tax change-এ, PDF layout change-এ, আর email change-এ বদলায়। ৩. একটা ৪০ লাইনের class ১৮ মাস ধরে ছোঁয়া হয়নি। ৪. প্রতিটা discount rule change মানে PricingEngine বদলানো — শুধু PricingEngine

(উত্তর ভেবে দেখো: ১ হলো Shotgun Surgery — এক কারণ, অনেক file। ২ হলো Divergent Change — এক class, অনেক কারণ। ৩ হলো কোনো active smell নেই — stable code-এ সার্জারি লাগে না। ৪ হলো সুস্থ design — ঠিক এরকমই হওয়া উচিত!)

যদি তুমি কাউকে explain করতে পারো কেন ২ নম্বরটা ভাগ করতে হবে কিন্তু ১ নম্বরটা জড়ো করতে হবে — তুমি Change Preventers পরিবার সত্যিকারের বুঝে ফেলেছো। এরপর পড়ো উল্টো smell: Shotgun Surgery

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

Divergent Change মানে কী, এক কথায়?
Divergent Change মানে হলো একটা class বারবার অনেক আলাদা আলাদা কারণে edit হচ্ছে — আজকে exam-এর কারণে, কালকে fee-র কারণে, পরশু sports-এর কারণে। class-টার অনেক বেশি কাজ, তাই সব ধরনের change এসে এক জায়গায় পড়ে।
Divergent Change আর Shotgun Surgery-র পার্থক্য কী?
এই দুটো একদম উল্টো। Divergent Change মানে ONE class, MANY কারণে বদলায় — সমাধান হলো class ভেঙে ফেলা। Shotgun Surgery মানে ONE কারণে MANY class বদলাতে হয় — সমাধান হলো ছড়িয়ে থাকা code এক জায়গায় জড়ো করা।
Divergent Change ঠিক করতে কোন refactoring লাগে?
Extract Class হলো মূল সমাধান। প্রতিটা আলাদা responsibility আলাদা ছোট class-এ নিয়ে যাও। Move Method আর Move Field দিয়ে method আর data নতুন জায়গায় নিয়ে যেতে পারবে।
Divergent Change আর Single Responsibility Principle-এর সম্পর্ক কী?
Divergent Change হলো SRP ভাঙলে কেমন লাগে সেটার real-life অনুভূতি। SRP বলে একটা class-এর শুধু একটাই কারণ থাকা উচিত বদলানোর জন্য। Divergent Change-এ একটা class অনেক কারণে বদলায় — মানে সে by definition SRP ভাঙছে।
সব বড় class-ই কি Divergent Change?
না। একটা class বড় হতে পারে কিন্তু তার কাজ হতে পারে একটাই। Divergent Change মাপা হয় size দিয়ে না — মাপা হয় change-এর কারণ দিয়ে। git history দেখো: আলাদা আলাদা ধরনের request কি সবসময় একই class-এ এসে পড়ছে? তাহলে smell আছে। class যদি stable থাকে, কমই বদলায় — তাহলে ছেড়ে দাও।

আরো দেখো

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

Shotgun Surgery: এক জায়গায় পরিবর্তন, দশ জায়গায় দৌড়াদৌড়ি

Shotgun Surgery code smell শিখবে রুবেলের বাসা বদলের গল্পের মাধ্যমে — সহজ সংজ্ঞা, TypeScript আর C# এর example, Divergent Change এর সাথে পার্থক্য, আর practice সহ।

আরও পড়ুন

Parallel Inheritance Hierarchies: প্রতিটা জিনিসের একটা ছায়া থাকে

Parallel Inheritance Hierarchies code smell শেখো একটা মিষ্টির দোকানের গল্পের মাধ্যমে — mirrored class tree কেন সমস্যা, TypeScript আর C# example দিয়ে কীভাবে fix করবে, আর কখন এটা রেখে দেওয়া ঠিক আছে।

আরও পড়ুন

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

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

আরও পড়ুন

Extract Class: অতিরিক্ত কাজে ডুবে যাওয়া class-কে একটু সাহায্য করো

Extract Class refactoring শেখো একটা মজার school office-এর গল্পের মাধ্যমে। একটা overloaded class-কে দুটো focused class-এ ভাগ করো — প্রতিটার একটাই কাজ।

আরও পড়ুন