সরাসরি পর্বে যান
পর্ব ২৫ · ৭০-এর মধ্যে · মডিউল ৫

Modularity, coupling আর cohesion — ভালো নকশার মাপকাঠি

Modularity, Coupling and Cohesion — Information Hiding, Coupling and Cohesion Levels, Package Metrics and Dependency Cycles

Taught in Bangla, this lesson explains modularity and Parnas's information hiding with the KWIC example, the six levels of coupling and seven levels of cohesion shown in small code samples, connascence, package metrics Ca, Ce, instability, abstractness and distance from the main sequence, LCOM and fan-in/fan-out, and finding dependency cycles and change ripples, linked to microservice boundaries.

এই পর্বে যা শিখবেন

  • Modularity, abstraction, encapsulation আর information hiding-এর পার্থক্য — আর Parnas-এর KWIC উদাহরণের শিক্ষা
  • Coupling-এর ছয় স্তর আর cohesion-এর সাত স্তর ছোট কোড দেখে চিনতে পারা
  • Connascence-এর ভাষায় নির্ভরতা বর্ণনা করা
  • Ca, Ce, I, A, D আর LCOM হিসাব করা, “zone of pain” চেনা, fan-in/fan-out বোঝা
  • Dependency graph-এ cycle খোঁজা, পরিবর্তনের ঢেউ (ripple) গোনা, আর microservice-এর সীমার সাথে এসব মেলানো

১ · Modularity — বড় system কেন টুকরো করে বানাই

মডিউল ৪-এ আমরা ঠিক করেছি কী বানাতে হবে — requirement. মডিউল ৫ থেকে প্রশ্ন বদলায়: কীভাবে সাজাবেন, যাতে আজ বানানো যায় আর কাল বদলানো যায়? এই মডিউলের পথ:

পর্ববিষয়এই পর্বের সাথে সম্পর্ক
২৫ (এটি)Modularity, coupling, cohesionভালো নকশা মাপার মাপকাঠি
২৬DRY, KISS, YAGNI, separation of concerns, Law of Demeterমাপকাঠি থেকে আসা ব্যবহারিক নিয়ম
২৭–২৮SOLID ১ আর SOLID ২SRP হলো class-স্তরের cohesion; DIP stable abstraction-এর দিকে নির্ভরতা ঘোরায়
২৯–৩০UML class diagram আর behaviour diagramDependency আর সম্পর্ক আঁকার মানদণ্ড ভাষা
৩১Interface আর contractModule-এর “দরজা” কেমন হবে

একটি কাল্পনিক স্কুল ম্যানেজমেন্ট system দিয়ে পুরো পর্ব চলবে: উপস্থিতি, পরীক্ষার ফল, বেতন-ফি, অভিভাবককে SMS. প্রথম version-এ সব কোড একটি file-এ — ৪,০০০ লাইন। একদিন হেডস্যার বললেন, “রোল নম্বর এখন থেকে 2026-07-15 (বছর-শ্রেণি-রোল) আকারে হবে।” Developer খুঁজে দেখলেন, রোল নম্বর ৮৭টি জায়গায় সরাসরি ব্যবহার হয়েছে। এক লাইনের নিয়ম বদলাতে তিন দিন লাগল, আর তার পরও ফি-র রসিদে একটি ভুল থেকে গেল।

একটি বিয়েবাড়ির আয়োজন ভাবুন। রান্না, প্যান্ডেল, আলো আর অতিথি-আপ্যায়ন — চারটি দল। ভালো আয়োজনে প্রতিটি দলের একজন প্রধান থাকেন। বাবুর্চি ঠিক করেন কোন হাঁড়িতে কী রাঁধবেন; বাকিরা শুধু জানেন “রাত ৮টায় ৩০০ জনের খাবার আসবে”। মেনু থেকে রেজালা বাদ দিলে আলোর দলকে কিছু জানাতে হয় না। খারাপ আয়োজনে আলোর মিস্ত্রি নিজে রান্নাঘরে ঢুকে চুলার আঁচ কমান — তখন একটি পরিবর্তনে সবার কাজ ওলটপালট। Software-এ এই দলগুলোই module, আর “রাত ৮টায় ৩০০ জনের খাবার” হলো তাদের interface.

Modularity মানে system-কে এমন অংশে ভাগ করা, যাতে প্রতিটি অংশ আলাদাভাবে বোঝা, বানানো, বদলানো আর test করা যায়। ISO/IEC 25010-এ modularity হলো maintainability-র একটি উপ-বৈশিষ্ট্য — পর্ব ৩-এ (Software quality) আপনি এটি দেখেছেন। চারটি কাছাকাছি শব্দ প্রায়ই গুলিয়ে যায়:

Abstraction

দরকারি দিকটুকু দেখানো, বাকিটা বাদ। “sendSms(phone, text)” — কোন gateway, কোন retry, তা ব্যবহারকারীর মাথাব্যথা নয়।

কী দেখাব

Encapsulation

Data আর তার নিয়ম এক জায়গায়, বাইরের হাত বন্ধ। ফি-র খাতা শুধু FeeLedger-এর method দিয়েই বদলায়।

কীভাবে আটকাব

Information hiding

যে সিদ্ধান্ত বদলাতে পারে, তা একটি module-এর ভেতরে লুকানো। এটি নকশার নীতি; encapsulation তার একটি কৌশল।

কেন লুকাব

Module

Function-এর দল, class, file, package বা আলাদা service — যার একটি স্পষ্ট interface আছে।

কোন টুকরো

Abstraction আর encapsulation-এর ভাষাগত রূপ Python-এ class আর object বা Java-তে encapsulation আর access modifier পর্বে দেখতে পারেন। এই পর্বের প্রশ্ন ভাষার নয়, নকশার: কোন জিনিস কোন module-এ যাবে, আর module-রা একে অপরকে কতটা জানবে? এর দুটি মাপকাঠি:

মূল ধারণা

Coupling — দুটি module-এর মধ্যে নির্ভরতা কতটা; কম হলে ভালো। Cohesion — একটি module-এর ভেতরের অংশগুলো কতটা একই উদ্দেশ্যে; বেশি হলে ভালো। দুটি একই মুদ্রার দুই পিঠ: যা একসাথে বদলায় তা এক জায়গায় রাখলে (high cohesion) আলাদা জায়গার মধ্যে টান কমে যায় (low coupling).

২ · Parnas ১৯৭২ — information hiding আর KWIC

১৯৭২ সালের ডিসেম্বরে Communications of the ACM-এ David L. Parnas-এর একটি ছোট প্রবন্ধ বের হয়: “On the Criteria To Be Used in Decomposing Systems into Modules”. তখন প্রচলিত ধারণা ছিল, flowchart আঁকুন, প্রতিটি বড় ধাপকে একটি module বানান। Parnas একটি ছোট সমস্যার দুটি নকশা পাশাপাশি রেখে দেখান, এই প্রচলিত পথ বদলের সময় কত দামি পড়ে। প্রবন্ধটি software design-এর সবচেয়ে বেশি উদ্ধৃত লেখাগুলোর একটি; information hiding শব্দটি জনপ্রিয় হয় এখান থেকেই (Parnas এর কাছাকাছি সময়ে অন্য লেখাতেও ধারণাটি আলোচনা করেছিলেন)।

সমস্যাটি KWIC index (Key Word In Context): কিছু লাইন ইনপুট দেওয়া হবে; প্রতিটি লাইনের প্রথম শব্দ বারবার শেষে সরিয়ে সব “circular shift” বানাতে হবে, তারপর সব shift বর্ণানুক্রমে সাজিয়ে ছাপাতে হবে। যেমন “Pather Panchali” থেকে দুটি shift: “Pather Panchali” আর “Panchali Pather”. প্রবন্ধের সারকথা, সংক্ষেপে:

নকশা ১ — ধাপ ধরে ভাগনকশা ২ — সিদ্ধান্ত লুকিয়ে ভাগ
ModuleInput, Circular Shift, Alphabetizing, Output, Master Control — flowchart-এর প্রতিটি ধাপLine Storage, Input, Circular Shifter, Alphabetizer, Output, Master Control
Data-র ভাগাভাগিসব module মেমরিতে অক্ষরগুলো ঠিক কোন বিন্যাসে আছে তা জানে আর সরাসরি পড়েশুধু Line Storage বিন্যাস জানে; অন্যরা “i-তম লাইনের j-তম শব্দ দাও” জাতীয় function ডাকে
বদল: লাইন মেমরিতে না রেখে disk-এপ্রায় সব module বদলায়শুধু Line Storage
বদল: shift আগেভাগে না বানিয়ে শুধু index রাখাCircular Shift, Alphabetizing, Output — একাধিক moduleশুধু Circular Shifter
বদল: বর্ণানুক্রম একবারে না করে দরকারমতোAlphabetizing আর Outputশুধু Alphabetizer

দুটি নকশাই কাজ করে, আকারেও প্রায় সমান। পার্থক্য শুধু তখনই দেখা যায়, যখন কিছু বদলায়। Parnas-এর মূল বাক্যটির ভাবার্থ: প্রতিটি module একটি কঠিন বা বদলাতে-পারে এমন নকশা-সিদ্ধান্তকে বাকিদের কাছ থেকে লুকিয়ে রাখবে; interface এমনভাবে বানাতে হবে যাতে সেই সিদ্ধান্ত interface দিয়ে ফাঁস না হয়।

মুদি দোকানের হিসাবের খাতার কথা ভাবুন। মালিক খাতায় কীভাবে লেখেন — বাকির পাতা আলাদা, না একই পাতায় লাল কালিতে — তা কর্মচারীরা জানেন না। তাঁরা শুধু জিজ্ঞেস করেন, “করিম ভাইয়ের বাকি কত?” কাল মালিক খাতা ছেড়ে মোবাইল অ্যাপে চলে গেলেও কর্মচারীদের প্রশ্ন একই থাকে। খাতার বিন্যাস একটি লুকানো সিদ্ধান্ত।

আমাদের স্কুল system-এ এর মানে: রোল নম্বরের format একটি সিদ্ধান্ত যা বদলাতে পারে, তাই সেটি student module জানবে, বাকিরা শুধু rollLabel(s) ডাকবে। ফি-র হিসাবের নিয়ম আরেকটি সিদ্ধান্ত — fees-এর ভেতরে। SMS কোন কোম্পানির gateway দিয়ে যায় — notify-এর ভেতরে। অংশ ৮-এ এই সিদ্ধান্তের দাম আমরা গুনে দেখব।

৩ · Coupling — নির্ভরতার ছয় স্তর

১৯৭০-এর দশকে IBM-এর Wayne Stevens, Glenford Myers আর Larry Constantine-এর “Structured Design” প্রবন্ধ (IBM Systems Journal, ১৯৭৪), পরে Edward Yourdon ও Constantine-এর একই নামের বই আর Myers-এর বইগুলো coupling আর cohesion-কে নকশার মাপকাঠি হিসেবে প্রতিষ্ঠা করে। Constantine এই ধারণাগুলো ১৯৬০-এর দশকের শেষ থেকেই নিয়ে কাজ করছিলেন বলে জানা যায়। নিচের ছয় স্তরের নাম আর ক্রম মূলত Myers-এর তালিকা থেকে, যা আজকের বেশিরভাগ বইয়ে চলে; Yourdon–Constantine-এর বইয়ে কিছু নাম আলাদা (যেমন stamp-কে কেউ “data-structured coupling” বলেন), আর external coupling কোথায় বসবে, তা নিয়ে বইভেদে মতভেদ আছে।

স্তর (খারাপ → ভালো)কী ভাগ হয়বিপদ
১ · Contentঅন্যের ভেতরের data বা কোডভেতরের যেকোনো বদলে ভাঙে
২ · CommonGlobal dataকে কখন বদলাল, খোঁজা কঠিন
৩ · Externalবাইরে থেকে চাপানো format/protocol/যন্ত্রবাইরের বদলে একসাথে অনেকে ভাঙে
৪ · ControlFlag বা mode, যা ভেতরের পথ ঠিক করেডাকনেওয়ালাকে ভেতরের যুক্তি জানতে হয়
৫ · Stampপুরো record, যদিও লাগে একটুRecord-এর অপ্রাসঙ্গিক বদলেও প্রভাব
৬ · Dataশুধু দরকারি সরল মানসবচেয়ে কম

একই কাজ — একজন ছাত্রের দেরির জরিমানা (late fine) হিসাব করে রিপোর্টে দেখানো — ছয়ভাবে লেখা যায়। “পরে” চাপুন:

report.jsContent coupling
import * as fees from "./fees.js";
fees._ledger[15].balance -= 500;  // অন্যের ভেতরে হাত
fees._rates.late = 0;             // অন্যের গোপন নিয়ম বদল
shared.jsCommon coupling
export const SCHOOL = { lateRate: 20, ledger: {} };
// fees.js আর report.js দুজনেই পড়ে আর লেখে
SCHOOL.lateRate = 0;   // কে করল? কখন?
fees.js + report.jsExternal coupling
// দুজনেই ব্যাংকের CSV নিজে নিজে পড়ে
const amount = Number(line.split(",")[3]);
// ব্যাংক একটি কলাম সরালে দুজনেই ভাঙে
report.jsControl coupling
fees.compute(student, "LATE");
fees.compute(student, "MONTHLY");
// fees-এর ভেতরে: if (mode === "LATE") … else if …
report.jsStamp coupling
fees.lateFine(student);
// student-এ নাম, ফোন, নম্বর, ঠিকানা…
// lateFine-এর লাগে শুধু student.daysLate
report.jsData coupling
fees.lateFine(student.daysLate, 20);
// lateFine(10, 20) → 200
// শুধু যা লাগে, সরল মান হিসেবে
  1. Content — সবচেয়ে খারাপ। report সরাসরি fees-এর ভেতরের _ledger বদলাচ্ছে আর নিয়মের মান শূন্য করছে। fees-এর লেখক ভেতরের নাম বা গঠন বদলালেই রিপোর্ট ভাঙবে — অথচ তিনি জানতেই পারবেন না যে কেউ ভেতরে হাত দিচ্ছে। আলোর মিস্ত্রি রান্নাঘরে ঢুকে চুলার আঁচ কমাচ্ছেন।
  2. Common — global data. একটি SCHOOL object সবাই পড়ে আর লেখে। কোনো এক সকালে জরিমানা শূন্য দেখালে কে দায়ী, খুঁজতে পুরো codebase পড়তে হবে। Test-ও কঠিন: একটি test global বদলালে পরের test-এর ফল বদলে যায়।
  3. External — বাইরের চাপানো format. ব্যাংকের পাঠানো CSV-র চতুর্থ কলাম টাকার অঙ্ক — এই জ্ঞান দুটি module-এ ছড়ানো। Format আমাদের হাতে নেই, তাই এই coupling পুরো এড়ানো যায় না; সমাধান হলো জ্ঞানটি একটি module-এ (একটি “bank file reader”) সীমিত রাখা।
  4. Control — flag দিয়ে পথ বাছাই। "LATE" বা "MONTHLY" পাঠিয়ে ডাকনেওয়ালা বলে দিচ্ছে ভেতরে কোন শাখা চলবে। নতুন mode যোগ করলে দুই পক্ষই বদলায়। সাধারণ সমাধান: প্রতিটি কাজের আলাদা function (lateFine, monthlyFee). এটি logical cohesion-এর যমজ, অংশ ৪-এ আবার দেখবেন।
  5. Stamp — পুরো record পাঠানো। lateFine-এর লাগে শুধু কত দিন দেরি, কিন্তু পাচ্ছে পুরো ছাত্র। ফলে lateFine test করতে একটি পূর্ণ ছাত্র বানাতে হয়, আর student-এর গঠন বদলালে এটিও প্রভাবিত হতে পারে। তবে মনে রাখুন: একটি ছোট, অর্থবহ object পাঠানো প্রায়ই ঠিকই আছে — দশটি আলাদা parameter-এর চেয়ে ভালো।
  6. Data — সবচেয়ে ভালো। শুধু দরকারি সরল মান: lateFine(10, 20) ফেরত দেয় 200. Test করা সহজ, ভুল বোঝার সুযোগ কম, আর student-এর গঠন বদলালেও lateFine জানে না। ছয় স্তরের পুরো সিঁড়ি এক কথায়: অন্যের সম্পর্কে যত কম জানতে হয়, তত ভালো।

Coupling শূন্য করা লক্ষ্য নয়। যে module কারও সাথে কথা বলে না, সে কোনো কাজেও আসে না। লক্ষ্য হলো দরকারি coupling-কে দুর্বল স্তরে নামানো আর সংখ্যায় কমানো। আর stable জিনিসের ওপর নির্ভরতা (যেমন ভাষার standard library) সাধারণত সমস্যাই নয় — অংশ ৬-এ এর মাপ দেখবেন।

৪ · Cohesion — একসাথে থাকার সাতটি কারণ

Coupling প্রশ্ন করে “বাইরের সাথে কতটা জড়ানো?”, cohesion প্রশ্ন করে “ভেতরের জিনিসগুলো একসাথে কেন?” Yourdon ও Constantine-এর বইয়ে সাতটি স্তর আছে, দুর্বল থেকে শক্ত। (Myers-এর তালিকায় কিছু নাম আলাদা, যেমন temporal-এর বদলে “classical”, আর একটি “informational” স্তর আছে; communicational আর sequential-এর ক্রম নিয়েও বইভেদে পার্থক্য দেখা যায়।) প্রতিটি ট্যাবে একটি ছোট উদাহরণ:

Coincidental — কোনো সম্পর্ক নেই, শুধু “কোথাও তো রাখতে হবে”।

class SchoolUtils {
  static formatDate(d) { … }
  static calcLateFine(days) { … }
  static sendSms(phone, text) { … }
  static backupDatabase() { … }
}

Logical — একই “ধরনের” কাজ, flag দিয়ে বাছাই।

function readInput(kind) {
  if (kind === "csv") …        // ব্যাংকের file
  else if (kind === "form") …  // ভর্তির ফর্ম
  else if (kind === "card") …  // উপস্থিতির কার্ড-রিডার
}

Temporal — একই সময়ে চলে, তাই একসাথে।

function startOfDay() {
  openDbConnection();
  resetAttendanceSheet();
  loadSmsTemplates();
  clearPrintQueue();
}

Procedural — নির্দিষ্ট ক্রমে চলে, কিন্তু data আলাদা।

function admitStudent(form) {
  checkSeatAvailable(form.classNo);
  printWelcomeLetter(form.name);
  updateNoticeBoard();
}

Communicational — একই data নিয়ে কাজ।

function studentSummary(record) {
  const avg = average(record.marks);
  const present = countPresent(record.attendance);
  return { avg, present };
}

Sequential — একটির output পরেরটির input.

function publishResult(rawSheet) {
  const marks = parseSheet(rawSheet);
  const grades = toGrades(marks);
  return renderTable(grades);
}

Functional — সবকিছু মিলে ঠিক একটি কাজ।

function gradeFromMarks(marks) {
  if (marks >= 80) return "A+";
  if (marks >= 70) return "A";
  …
}

একটি সহজ পরীক্ষা, Yourdon–Constantine-এর বইয়ের পরামর্শের কাছাকাছি: module-এর কাজ এক বাক্যে লিখুন। বাক্যে “এবং” বা কমা লাগলে (“জরিমানা হিসাব করে এবং SMS পাঠায়”) সম্ভবত sequential বা communicational; “প্রথমে… তারপর…” লাগলে procedural বা temporal; “সব রকম…” বা “নানা…” লাগলে logical বা coincidental; একটি পরিষ্কার ক্রিয়া-কর্মে বলা গেলে (“নম্বর থেকে grade বের করে”) functional. পর্ব ২৭-এর single responsibility principle এই চিন্তারই class-স্তরের রূপ।

এবার সবচেয়ে চেনা রোগটির চিকিৎসা: একটি utility class যেখানে ছয় মাসে আটটি অসম্পর্কিত function জমেছে। “পরে” চাপুন:

নকশার অবস্থা
কাজ
Module
Function
যারা import করে
Cohesion
  1. তালিকা করুন। আটটি function, তারিখ সাজানো থেকে database backup পর্যন্ত। নাম “Utils” নিজেই একটি সতর্কসংকেত: নামটি বলে না module কী করে, শুধু বলে “নানা কিছু”।
  2. কে ব্যবহার করে, দেখুন। নয়টি file এটি import করে, আর প্রত্যেকে পুরো class পায়। ফলে fan-in বেশি, আর SMS-এর কোড বদলালেও ফলাফল পাতার file আবার build আর test হয় — অথচ সে কখনো SMS পাঠায় না।
  3. প্রশ্ন করুন: কেন বদলাবে? জরিমানার নিয়ম বদলায় হিসাব শাখার সিদ্ধান্তে, grade-এর নিয়ম বদলায় পরীক্ষা বোর্ডের নীতিতে, SMS বদলায় gateway কোম্পানি বদলালে, backup বদলায় server বদলালে। বদলের ভিন্ন কারণ = ভিন্ন module — information hiding আর SRP-র একই কথা।
  4. কারণ ধরে দল বানান। পাঁচটি দল: দেখানোর format, ফি, পরীক্ষা, বার্তা, ফাইল আমদানি; আর backup কোনো business module-ই নয়, একটি পরিচালনার script. এখনো কোড সরানো হয়নি — শুধু কাগজে নকশা।
  5. ছোট ধাপে সরান। একবারে একটি দল সরান, প্রতিবার test চালান। পুরোনো SchoolUtils সাময়িকভাবে নতুন জায়গাগুলো থেকে re-export করুক, যাতে নয়টি file একসাথে না ভাঙে। এটি refactoring: আচরণ একই, কাঠামো ভালো — পূর্ণ কৌশল পর্ব ৪২-এ।
  6. Import ঠিক করে পুরোনোটি মুছুন। প্রতিটি file এখন শুধু যা লাগে তা import করে; fees.js আগে আটটি function দেখত, এখন দুটি। SMS gateway বদলালে এখন শুধু notify বদলাবে আর test হবে। খেয়াল করুন: format module-এ দুটি function থাকলেও তারা একই কাজের অংশ — “বাংলায় দেখানো” — তাই cohesive.

৫ · Connascence — নির্ভরতার আরও সূক্ষ্ম ভাষা

Coupling-এর স্তরগুলো structured design-এর যুগের, function আর global data-র কথা ভেবে বানানো। Object-oriented কোডের জন্য Meilir Page-Jones একটি আরও সূক্ষ্ম ভাষা দেন — connascence (১৯৯২-এর একটি প্রবন্ধে, পরে তাঁর OO design-এর বইগুলোতে; ২০০০-এর দশকের শেষে Jim Weirich-এর বক্তৃতায় এটি আবার জনপ্রিয় হয়)। সংজ্ঞা: দুটি অংশ connascent, যদি একটিকে বদলালে system সঠিক রাখতে অন্যটিকেও বদলাতে হয়। ধরনগুলোর প্রচলিত ভাগ (নাম আর তালিকা লেখকভেদে সামান্য আলাদা):

ধরনদুই পক্ষকে কোন বিষয়ে একমত হতে হয়স্কুল system-এ উদাহরণ
Static — কোড পড়েই দেখা যায় (সাধারণত দুর্বল থেকে শক্ত)
NameনামlateFine নাম বদলালে সব caller বদলায়
Typeধরনরোল number না string
Meaning (convention)একটি মানের অর্থstatus = 2 মানে “ছুটিতে”
Positionক্রমlateFine(days, rate) নাকি (rate, days)
Algorithmএকই হিসাবের নিয়মরসিদ আর সার্ভার একই নিয়মে checksum বানায়
Dynamic — চলার সময়ে দেখা যায়, সাধারণত বেশি বিপজ্জনক
Execution (order)কাজের ক্রমopenDb() আগে, save() পরে
Timingসময়SMS ৩ সেকেন্ডে না এলে timeout
Valueএকাধিক মান একসাথে মিলতে হবেমোট ফি = সব কিস্তির যোগফল
Identityএকই object-কে ধরাদুটি module একই ledger object বদলায়

Page-Jones তিনটি মাত্রায় বিচার করতে বলেন: শক্তি (dynamic আর “meaning”-এর মতো ধরন বেশি শক্ত, ভাঙলে ধরা কঠিন), দূরত্ব (একই function-এর ভেতরে শক্ত connascence চলে, দুই service-এর মধ্যে নয়), আর মাত্রা (কতগুলো অংশ জড়িত)। ব্যবহারিক নিয়ম: শক্ত connascence-কে দুর্বলে নামান (magic number 2 → নামযুক্ত constant ON_LEAVE, অর্থাৎ meaning থেকে name), আর শক্ত যা থাকে তা কাছাকাছি রাখুন।

৬ · Coupling মাপা — Ca, Ce, instability, abstractness আর D

“কম coupling” কথাটি সংখ্যায় আনার একটি জনপ্রিয় পদ্ধতি Robert C. Martin-এর (১৯৯৪-এর একটি প্রবন্ধে প্রস্তাব, পরে Agile Software Development: Principles, Patterns, and Practices, ২০০২, আর Clean Architecture, ২০১৭ বইয়ে বিস্তারিত)। একটি package-এর জন্য:

  • Ca (afferent, ভেতরে আসা) — বাইরের কতজন এই package-এর ওপর নির্ভর করে।
  • Ce (efferent, বাইরে যাওয়া) — এই package কতজনের ওপর নির্ভর করে।
  • Instability I = Ce / (Ca + Ce), ০ থেকে ১। ০ = সবাই আমার ওপর নির্ভর করে, আমি কারও ওপর নই — বদলানো কঠিন, তাই “stable”. ১ = আমি অন্যদের ওপর নির্ভর করি, কেউ আমার ওপর নয় — বদলানো সহজ।
  • Abstractness A = Na / Nc — মোট class-এর মধ্যে abstract class বা interface কতগুলো।
  • Distance D = |A + I − 1| — main sequence রেখা A + I = 1 থেকে দূরত্ব (Martin-এর normalized রূপ, ০ থেকে ১)।

Martin-এর মূল সংজ্ঞায় Ca আর Ce গোনা হয় class ধরে (বাইরের কতগুলো class এই package-এর class-এর ওপর নির্ভর করে)। এই পর্বের কোডে সরল রাখতে আমরা package গুনছি; tool-ভেদেও গোনার নিয়ম আলাদা হয়, তাই দুটি tool-এর সংখ্যা সরাসরি তুলনা করবেন না। এর পেছনে দুটি নীতি: Stable Dependencies Principle — নির্ভরতা কম stable থেকে বেশি stable-এর দিকে যাবে; আর Stable Abstractions Principle — যে package যত stable, তত abstract হওয়া উচিত, যাতে stable হয়েও বাড়ানো যায় (পর্ব ২৮-এর dependency inversion ঠিক এই কাজ করে)।

আমাদের কাল্পনিক স্কুল system-এর আটটি package-এর dependency graph:

স্কুল system-এর package dependency (কাল্পনিক)

Arrow = “depends on”. Red: zone of pain, amber: zone of uselessness

স্কুল system-এর package dependency graph ui attendance exam fees storage notify model plugins Ca = 6, Ce = 0 → I = 0 Ca = 3, Ce = 1 Ca = 2, Ce = 1
ছবি ২৫.১ — সব তীর নিচের দিকে, কোনো চক্র নেই। model-এর দিকে ছয়টি তীর আসে আর একটিও বেরোয় না — সবচেয়ে stable, তাই বদলানো সবচেয়ে কঠিন।

এবার প্রতিটি package-এর পাঁচটি সংখ্যা হিসাব করে দেখি, কে main sequence থেকে দূরে:

// Package metrics (Robert C. Martin) for a fictional school system.
// deps = packages this one uses; classes = total; abstract = interfaces/abstract classes.
// Simplification: we count PACKAGES, not classes, when computing Ca and Ce.
const pkgs = {
  ui:         { deps: ["attendance", "exam", "fees"], classes: 6, abstract: 0 },
  attendance: { deps: ["model", "storage", "notify"], classes: 4, abstract: 0 },
  exam:       { deps: ["model", "storage"],           classes: 5, abstract: 1 },
  fees:       { deps: ["model", "storage", "notify"], classes: 4, abstract: 0 },
  notify:     { deps: ["model"],                      classes: 3, abstract: 1 },
  storage:    { deps: ["model"],                      classes: 4, abstract: 3 },
  model:      { deps: [],                             classes: 5, abstract: 0 },
  plugins:    { deps: ["model"],                      classes: 4, abstract: 4 },
};
const ca = {};                                   // afferent: who depends on me
for (const p in pkgs) ca[p] = 0;
for (const p in pkgs) for (const d of pkgs[p].deps) ca[d]++;
const f = x => x.toFixed(2);
console.log("package     Ca Ce    I     A     D   verdict");
for (const [p, v] of Object.entries(pkgs)) {
  const Ce = v.deps.length, Ca = ca[p];
  const I = Ca + Ce === 0 ? 0 : Ce / (Ca + Ce);  // instability
  const A = v.abstract / v.classes;              // abstractness
  const D = Math.abs(A + I - 1);                 // distance from main sequence
  let verdict = "ok";
  if (D >= 0.5) verdict = A + I < 1 ? "ZONE OF PAIN" : "ZONE OF USELESSNESS";
  console.log(p.padEnd(11), String(Ca).padStart(2), String(Ce).padStart(2),
    f(I).padStart(5), f(A).padStart(5), f(D).padStart(5), " " + verdict);
}

আউটপুট:

package     Ca Ce    I     A     D   verdict
ui           0  3  1.00  0.00  0.00  ok
attendance   1  3  0.75  0.00  0.25  ok
exam         1  2  0.67  0.20  0.13  ok
fees         1  3  0.75  0.00  0.25  ok
notify       2  1  0.33  0.33  0.33  ok
storage      3  1  0.25  0.75  0.00  ok
model        6  0  0.00  0.00  1.00  ZONE OF PAIN
plugins      0  1  1.00  1.00  1.00  ZONE OF USELESSNESS

ui পুরোপুরি unstable আর concrete (I = 1, A = 0) — একদম main sequence-এর ওপর, কারণ কেউ এর ওপর নির্ভর করে না, তাই বদলানো নিরাপদ। storage বেশ stable (I = 0.25) কিন্তু বেশিরভাগ interface (A = 0.75) — এটিও রেখার ওপর। দুটি সতর্কসংকেত: model ছয়জনের ভিত্তি অথচ একটিও abstraction নেই (zone of pain) — এখানে একটি field-এর নাম বদলালে ছয়টি package নড়বে। আর plugins চারটিই interface অথচ কেউ ব্যবহার করে না (zone of uselessness) — সম্ভবত “পরে লাগবে” ভেবে বানানো (পর্ব ২৬-এর YAGNI). তবে রায় দেওয়ার আগে প্রসঙ্গ দেখুন: model-এ যদি শুধু খুব কম বদলানো value object থাকে (যেমন তারিখ বা টাকার পরিমাণ), zone of pain-এ থেকেও তা ক্ষতিকর নয় — Martin নিজেও standard library-র string class-এর উদাহরণ দেন। নিজে চেষ্টা করুন: model-এর লাইনে abstract: 2 লিখে আবার চালান — A = 0.40 হয়, কিন্তু D = 0.60, অর্থাৎ এখনো zone of pain-এ। দুটি interface যথেষ্ট নয়।

সংখ্যাগুলো ছবিতে বসালে main sequence দেখা যায়:

Main sequence — abstractness বনাম instability

D = |A + I − 1|; shaded corners are D ≥ 0.5

Main sequence chart 00.51 Instability I 00.51 Abstractness A zone of pain zone of uselessness main sequence ui attendance, fees exam notify storage model plugins
ছবি ২৫.২ — সবুজ কর্ণ রেখার কাছে থাকা package-গুলো “ভারসাম্যপূর্ণ”: হয় stable আর abstract, নয়তো unstable আর concrete. দুই কোণের রঙিন অঞ্চলে D ≥ 0.5.

একটি package-এর হিসাব হাতে করে দেখি — exam:

exam package
Ca
Ce
I
A
D
  1. Ca — কারা আমার ওপর নির্ভর করে? ছবি ২৫.১-এ exam-এর দিকে আসা তীর একটি, ui থেকে। তাই Ca = 1.
  2. Ce — আমি কাদের ওপর নির্ভর করি? exam থেকে বেরোনো তীর দুটি: model আর storage. তাই Ce = 2.
  3. Instability. I = Ce / (Ca + Ce) = 2 / 3 ≈ 0.67. অর্থাৎ বেশ unstable — বাইরের দুটি জিনিস বদলালে exam-কে মানিয়ে নিতে হতে পারে, কিন্তু exam বদলালে শুধু ui প্রভাবিত।
  4. Abstractness. পাঁচটি class-এর একটি interface (ধরুন GradingPolicy). A = 1 / 5 = 0.20.
  5. Distance. D = |A + I − 1| = |0.20 + 0.67 − 1| = |−0.13| = 0.13 (দুই দশমিকে গোল করার আগে 2/3 ধরে হিসাব করলে 0.1333…)। ০-এর কাছে, তাই main sequence-এর কাছেই — চিন্তার কিছু নেই। ঋণাত্মক চিহ্নটি বলে বিন্দুটি রেখার নিচের দিকে, অর্থাৎ zone of pain-এর পাশে, তবে অনেক দূরে।

৭ · Cohesion মাপা — LCOM, fan-in আর fan-out

Class-এর cohesion মাপার সবচেয়ে পরিচিত পরিবার LCOM (Lack of Cohesion of Methods). নামেই আছে “lack” — তাই সংখ্যা যত বেশি, cohesion তত কম। সমস্যা হলো একই নামে কয়েকটি ভিন্ন সূত্র চলে:

  • Chidamber ও Kemerer (১৯৯১-এ প্রস্তাব, ১৯৯৪-এ IEEE Transactions on Software Engineering-এ সংশোধিত রূপ): method-জোড়া গুনুন — P = যে জোড়া কোনো attribute ভাগ করে না, Q = যে জোড়া করে; LCOM = P − Q, ঋণাত্মক হলে ০। প্রায়ই একে LCOM1 বা LCOM2 বলা হয় — সংখ্যাক্রম লেখকভেদে আলাদা।
  • Hitz ও Montazeri (১৯৯৫): method-গুলোকে node ধরুন; দুটি method যুক্ত, যদি তারা একটি attribute ভাগ করে বা একটি অন্যটিকে ডাকে। LCOM4 = এই graph-এর connected component-এর সংখ্যা। ১ মানে cohesive; ২ বা বেশি মানে class-টি ততগুলো স্বাধীন টুকরোয় ভাঙা যায়।
  • Henderson-Sellers-এর LCOM* (১৯৯৬) সহ আরও কিছু রূপ আছে, যা ০ থেকে ১-এর মধ্যে মান দেয়।

আমরা LCOM4 নেব, কারণ এর অর্থ সবচেয়ে সরাসরি: “কয়টি আলাদা class লুকিয়ে আছে?” কোডে প্রতিটি method কোন attribute ছোঁয় আর কে কাকে ডাকে, তা একটি ঘোষিত map-এ লেখা; আসল tool এই map কোড বিশ্লেষণ করে বানায়।

# LCOM4 (Hitz & Montazeri): number of connected groups of methods, where two
# methods are linked if they use a common attribute or one calls the other.
# 1 = cohesive; 2 or more = the class could be split into that many parts.
def lcom4(cls):
    uses, calls = cls.USES, cls.CALLS          # read the declared maps
    parent = {m: m for m in uses}
    def root(m):
        while parent[m] != m:
            m = parent[m]
        return m
    ms = list(uses)
    links = [(a, b) for i, a in enumerate(ms) for b in ms[i + 1:]
             if uses[a] & uses[b]] + calls
    for a, b in links:
        parent[root(a)] = root(b)
    groups = {}
    for m in ms:
        groups.setdefault(root(m), []).append(m)
    return list(groups.values())
class SchoolOffice:     # before: method -> attributes it touches
    USES = {"add_student": {"students"}, "find_student": {"students"},
            "record_fee": {"ledger"}, "fee_due": {"ledger", "fee_rate"},
            "print_receipt": {"ledger"}, "send_sms": {"gateway"},
            "format_sms": {"template"}}
    CALLS = [("send_sms", "format_sms")]
def part(name, methods):  # after the split: a class with only these methods
    u = {m: SchoolOffice.USES[m] for m in methods}
    c = [p for p in SchoolOffice.CALLS if p[0] in u and p[1] in u]
    return type(name, (), {"USES": u, "CALLS": c})
after = [part("StudentRegistry", ["add_student", "find_student"]),
         part("FeeLedger", ["record_fee", "fee_due", "print_receipt"]),
         part("Notifier", ["send_sms", "format_sms"])]
for c in [SchoolOffice] + after:
    g = lcom4(c)
    print(f"{c.__name__}: LCOM4 = {len(g)}  " + " | ".join(", ".join(p) for p in g))

আউটপুট:

SchoolOffice: LCOM4 = 3  add_student, find_student | record_fee, fee_due, print_receipt | send_sms, format_sms
StudentRegistry: LCOM4 = 1  add_student, find_student
FeeLedger: LCOM4 = 1  record_fee, fee_due, print_receipt
Notifier: LCOM4 = 1  send_sms, format_sms

SchoolOffice-এর সাতটি method তিনটি বিচ্ছিন্ন দলে পড়ে: ছাত্রের তালিকা, ফি-র খাতা আর SMS. send_sms আর format_sms কোনো attribute ভাগ করে না, কিন্তু একটি অন্যটিকে ডাকে — তাই LCOM4-এ তারা এক দলে। ভাঙার পরে প্রতিটি নতুন class-এর LCOM4 = 1. একই আসল class-এ Chidamber–Kemerer-এর সূত্র দিলে: ২১টি method-জোড়ার মধ্যে attribute ভাগ করে Q = ৪টি, করে না P = ১৭টি, LCOM = ১৭ − ৪ = ১৩ — একটি সংখ্যা যার অর্থ সরাসরি বোঝা কঠিন, আর এটি method-এর ডাকাডাকি গোনেই না। এজন্যই রিপোর্টে সবসময় লিখবেন কোন LCOM. সতর্কতা: getter/setter বা কোনো attribute না ছোঁয়া method LCOM4-কে কৃত্রিমভাবে বাড়াতে পারে; tool-গুলো এদের বাদ দেয় কি না, তার নিয়ম আলাদা।

Fan-in আর fan-out

আরও পুরোনো আর সরল মাপ: fan-in = কতগুলো module এটিকে ডাকে, fan-out = এটি কতগুলোকে ডাকে। Package-স্তরে এরা প্রায় Ca আর Ce-র মতো, কিন্তু সাধারণত function বা module ধরে গোনা হয়। Sallie Henry ও Dennis Kafura (১৯৮১) এদের দিয়ে একটি “information flow” metric প্রস্তাব করেন, সাধারণত উদ্ধৃত রূপে: দৈর্ঘ্য × (fan-in × fan-out)²। ব্যবহারিক শিক্ষা: বেশি fan-in একটি ছোট, stable, cohesive function-এর জন্য ভালো খবর (অনেকে পুনর্ব্যবহার করছে) — কিন্তু “Utils”-এর মতো coincidental module-এর বেশি fan-in মানে একটি বদলে অনেকের ঝুঁকি। বেশি fan-out মানে module-টি অনেক কিছু জানে — প্রায়ই সে অনেক দায়িত্ব নিয়ে বসে আছে। জটিলতার আরেকটি মাপ, cyclomatic complexity, function-এর ভেতরের শাখা গোনে; সেটি আর অন্য code metric পর্ব ৫০-এ।

৮ · Dependency cycle, পরিবর্তনের ঢেউ আর microservice-এর সীমা

Dependency graph-এ সবচেয়ে ক্ষতিকর আকার হলো চক্র: fees নির্ভর করে student-এর ওপর, আর student নির্ভর করে fees-এর ওপর। তখন দুটিকে আলাদা করে বোঝা, build করা, test করা বা release করা যায় না — কার্যত তারা একটি বড় module. Martin-এর Acyclic Dependencies Principle বলে, package-এর graph একটি DAG হবে। পর্ব ১৯-এ (monorepo) দেখেছেন, “affected build” ঠিক এই graph ধরে চলে — চক্র থাকলে অনেক বেশি কিছু “affected” হয়।

চক্র খোঁজার প্রচলিত উপায় DFS (depth-first search), তিন রঙে: white = এখনো দেখা হয়নি, grey = এখন যে পথে আছি তার ওপর, black = শেষ। Grey কোনো node-এ আবার পৌঁছানো মানে একটি back edge, অর্থাৎ চক্র। DFS-এর মূল কৌশল DSA কোর্সের DFS পর্বে, আর চক্রহীন graph সাজানো topological sort পর্বে।

// Find dependency cycles with depth-first search (white / grey / black colours).
// Grey = "on the current DFS path"; reaching a grey node again closes a cycle.
const graph = {
  ui:       ["fees", "exam"],
  fees:     ["student", "notify"],
  notify:   ["student", "template"],
  student:  ["fees"],              // student -> fees closes a cycle
  exam:     ["student", "report"],
  report:   ["exam"],              // report -> exam closes another cycle
  template: [],
};
function findCycles(g) {
  const colour = {}, path = [], cycles = [];
  for (const n in g) colour[n] = "white";
  function visit(n) {
    colour[n] = "grey"; path.push(n);
    for (const m of g[n]) {
      if (colour[m] === "grey")                       // back edge
        cycles.push([...path.slice(path.indexOf(m)), m].join(" -> "));
      else if (colour[m] === "white") visit(m);
    }
    colour[n] = "black"; path.pop();
  }
  for (const n in g) if (colour[n] === "white") visit(n);
  return cycles;
}
console.log("Cycles found:");
for (const c of findCycles(graph)) console.log("  " + c);
// Fix: student no longer calls fees; report gets exam data passed in.
graph.student = [];
graph.report = [];
const left = findCycles(graph);
console.log("After fix: " + (left.length ? left.join(", ") : "no cycles (graph is a DAG)"));

আউটপুট:

Cycles found:
  fees -> student -> fees
  exam -> report -> exam
After fix: no cycles (graph is a DAG)

DFS শুরু হয় ui থেকে, যায় fees → student; student-এর তীর fees-এ, যা তখনো grey — প্রথম চক্র। পরে exam → report → exam দ্বিতীয়টি। খেয়াল করুন, notify → student চক্র নয়: ততক্ষণে student black. চক্র ভাঙার তিনটি প্রচলিত উপায়: (১) নির্ভরতা উল্টো দিকে সরানো — student-কে ফি জানতে হলে fees তথ্যটি পাঠিয়ে দিক; (২) মাঝখানে একটি interface বসানো, যাতে দুজনেই একটি stable abstraction-এর ওপর নির্ভর করে (dependency inversion, পর্ব ২৮; injection-এর কৌশল পর্ব ৩৭); (৩) দুজনের ভাগ করা অংশ নতুন একটি নিচের package-এ নামানো। সতর্কতা: এই সরল পদ্ধতি প্রতিটি back edge-এ একটি চক্র দেখায়; জটিল graph-এ সব চক্রের তালিকা দেয় না। চক্রে জড়ানো সব module-এর দল খুঁজতে strongly connected component-এর algorithm (যেমন Tarjan-এর) ব্যবহার হয়।

পরিবর্তনের ঢেউ গোনা

অংশ ১-এর সেই রোল নম্বরের সমস্যায় ফিরি। একটি পরিবর্তনের ফলে অন্যত্র পরপর পরিবর্তন করতে হলে তাকে ripple effect বলে — পুকুরে ঢিল ফেলার ঢেউ। একই সাতটি file, দুই রকম নকশা:

// Change ripple: one change, how many files must be EDITED and RE-TESTED?
// Change: the roll number moves from a plain number (15) to "2026-07-15".
// "uses" = what each file touches directly; "imports" = files it depends on.
function ripple(name, files, changed) {
  const edit = Object.keys(files).filter(f => files[f].uses.includes(changed));
  const retest = new Set(edit), queue = [...edit];
  while (queue.length) {                       // walk dependents backwards
    const x = queue.shift();
    for (const f in files)
      if (files[f].imports.includes(x) && !retest.has(f)) { retest.add(f); queue.push(f); }
  }
  console.log(name + ": edit " + edit.length + " " + JSON.stringify(edit) +
    ", re-test " + retest.size + " of " + Object.keys(files).length);
}
// High coupling: everyone reads the raw record field student.roll
const tight = {
  student:    { uses: ["student.roll"], imports: [] },
  attendance: { uses: ["student.roll"], imports: ["student"] },
  exam:       { uses: ["student.roll"], imports: ["student"] },
  fees:       { uses: ["student.roll"], imports: ["student"] },
  idcard:     { uses: ["student.roll"], imports: ["student"] },
  report:     { uses: ["student.roll"], imports: ["exam", "student"] },
  dashboard:  { uses: [], imports: ["report"] },
};
// Low coupling: only student.js knows the format; others call rollLabel(s)
const loose = {
  student:    { uses: ["student.roll"], imports: [] },
  attendance: { uses: ["rollLabel()"], imports: ["student"] },
  exam:       { uses: ["rollLabel()"], imports: ["student"] },
  fees:       { uses: ["rollLabel()"], imports: ["student"] },
  idcard:     { uses: ["rollLabel()"], imports: ["student"] },
  report:     { uses: ["rollLabel()"], imports: ["exam", "student"] },
  dashboard:  { uses: [], imports: ["report"] },
};
ripple("tight", tight, "student.roll"); ripple("loose", loose, "student.roll");

আউটপুট:

tight: edit 6 ["student","attendance","exam","fees","idcard","report"], re-test 7 of 7
loose: edit 1 ["student"], re-test 7 of 7

উচ্চ coupling-এর নকশায় ছয়টি file হাতে বদলাতে হয় — ছয়টি জায়গায় ভুলের সুযোগ, ছয়জন মালিকের সাথে সমন্বয়। Information hiding-এর নকশায় বদলায় একটি: student.js, কারণ format-টি শুধু সে জানে। কিন্তু দ্বিতীয় সংখ্যাটি খেয়াল করুন: দুই নকশাতেই সাতটির সাতটিই আবার test করতে হয়, কারণ সবাই পরোক্ষভাবে student-এর ওপর নির্ভর করে, আর ছাপা রোলের চেহারা সত্যিই বদলেছে। শিক্ষা: low coupling হাতের সম্পাদনা কমায়, কিন্তু আচরণের ঢেউ পুরো থামায় না — সেই ঢেউ ধরার জন্য স্বয়ংক্রিয় test (মডিউল ৮) সস্তা আর জরুরি। rollLabel() যদি পুরোনো আকারই ফেরত দিত, তাহলে dependents-এর আচরণও বদলাত না।

Low coupling আর high cohesion — ভারসাম্য

দুটি দিকে বাড়াবাড়ি সম্ভব। এক দিকে “big ball of mud” — সব কিছু সবকিছুকে চেনে। অন্য দিকে এত ছোট ছোট টুকরো আর এত interface যে একটি সরল কাজ বুঝতে দশটি file খুলতে হয়; প্রতিটি টুকরো “cohesive”, কিন্তু কাজটি নিজেই ছড়িয়ে গেছে — যা আসলে নিম্ন cohesion-এর আরেক রূপ। কয়েকটি ব্যবহারিক নিয়ম:

  • যা একসাথে বদলায়, তা একসাথে রাখুন; যা আলাদা কারণে বদলায়, তা আলাদা করুন।
  • Stable জিনিসের ওপর নির্ভরতা সস্তা, অস্থির জিনিসের ওপর দামি। তাই coupling-এর দিক সংখ্যার মতোই গুরুত্বপূর্ণ।
  • Abstraction-এর একটি দাম আছে — প্রতিটি interface পড়তে, খুঁজতে আর রাখতে খরচ। দ্বিতীয় বাস্তব ব্যবহার না আসা পর্যন্ত অনেক দল নতুন interface বানায় না (পর্ব ২৬-এ YAGNI).

Microservice-এর সীমা — একই প্রশ্ন, বড় দামে

যখন module একটি আলাদা microservice হয়, coupling আর cohesion-এর প্রশ্ন বদলায় না, শুধু ভুলের দাম বাড়ে: function call হয়ে যায় network call, যা ধীর আর ব্যর্থ হতে পারে, আর একসাথে বদলানো মানে কয়েকটি দলের সমন্বিত deploy. System Design কোর্সের microservice পর্বে বিস্তারিত আছে; এখানে এই পর্বের ভাষায় মিল:

এই পর্বের ধারণাMicroservice-এ তার রূপ
Common coupling (global data)কয়েকটি service একই database table পড়ে-লেখে — সবচেয়ে চেনা ফাঁদ
Content couplingঅন্য service-এর ভেতরের table বা private API সরাসরি ব্যবহার
Connascence of timing/executionএকটি অনুরোধে পরপর অনেক synchronous call; একটি ধীর হলে সব ধীর
High cohesionএকটি ব্যবসায়িক দায়িত্বের পুরো মালিকানা (ফি, পরীক্ষা) — DDD-র bounded context, পর্ব ৪০-এ এর ধারণা
Dependency cycleService A B-কে ডাকে, B আবার A-কে — একসাথে ছাড়া deploy করা যায় না: “distributed monolith”

Melvin Conway-এর পর্যবেক্ষণ (Conway's law, ১৯৬৮) এখানে সরাসরি খাটে: দলের যোগাযোগের কাঠামো system-এর কাঠামোয় প্রতিফলিত হয়। ভুল জায়গায় service-এর সীমা টানলে দুই দলকে প্রতি সপ্তাহে একসাথে release করতে হয়। তাই অনেক অভিজ্ঞ লেখক পরামর্শ দেন: আগে একটি ভালোভাবে module-এ ভাগ করা monolith, তারপর সীমা পরিষ্কার হলে আলাদা service. বড় architecture-এর ধরন পর্ব ৩৮-এ।

আরও গভীরে: Parnas-এর ১৯৭২-এর প্রবন্ধটি মাত্র কয়েক পাতার, মূল লেখা পড়া সার্থক। Yourdon ও Constantine-এর Structured Design (১৯৭৫ সালে প্রথম, ১৯৭৯ সালে Prentice-Hall সংস্করণ বেশি পরিচিত) coupling-cohesion-এর মূল উৎস। ISO/IEC 25010-এ modularity হলো maintainability-র উপ-বৈশিষ্ট্য (পর্ব ৩)। আধুনিক আলোচনার জন্য John Ousterhout-এর A Philosophy of Software Design (২০১৮) “deep module” — ছোট interface, বড় লুকানো কাজ — ধারণা দিয়ে Parnas-এর চিন্তা নতুন করে বলে। JDepend বা SonarQube-এর মতো tool Martin-এর metric আর LCOM হিসাব করে, তবে tool-ভেদে সংজ্ঞা আলাদা — documentation দেখে নিন।

পরীক্ষা ও ইন্টারভিউতে:

  • Coupling আর cohesion-এর পার্থক্য কী? — Coupling module-এর মধ্যে নির্ভরতা (কম ভালো), cohesion module-এর ভেতরের মিল (বেশি ভালো)।
  • Coupling-এর স্তর খারাপ থেকে ভালো ক্রমে বলুন। — Content, common, external, control, stamp, data.
  • Cohesion-এর স্তর দুর্বল থেকে শক্ত? — Coincidental, logical, temporal, procedural, communicational, sequential, functional.
  • Ca = 2, Ce = 6, ১০টি class-এর ৫টি abstract — I, A, D কত? — I = 6/8 = 0.75, A = 0.5, D = |0.5 + 0.75 − 1| = 0.25.
  • Information hiding আর encapsulation কি একই? — না; information hiding কী লুকাতে হবে সেই নকশা-নীতি (বদলাতে পারে এমন সিদ্ধান্ত), encapsulation তা প্রয়োগের ভাষাগত কৌশল।

৯ · AI-এর যুগে — AI-এর নকশা কতটা জড়ানো?

AI-কে “একটি স্কুল ম্যানেজমেন্ট system-এর কোড দাও” বললে কয়েক মিনিটে পূর্ণ project পাওয়া যায়। প্রায়ই তা চলে, কিন্তু নকশার দিক থেকে কয়েকটি চেনা সমস্যা থাকে:

AI-এর ভুলকীভাবে ধরবেন
সব helper একটি utils.js-এ, বা সব কাজ একটি বিশাল “Manager” class-এপ্রতিটি file-এর কাজ এক বাক্যে লিখে দেখুন; LCOM4 বা fan-in গুনুন
উল্টো দিকে: প্রতিটি class-এর সাথে একটি অপ্রয়োজনীয় interface আর factory — pattern-এর বাড়াবাড়িপ্রতিটি interface-এর কয়টি বাস্তবায়ন আছে? একটি হলে প্রশ্ন করুন
Global config বা singleton object সবাই বদলায় (common coupling)কোন file কোন global লেখে, খুঁজে দেখুন
Module-দের মধ্যে চক্র, বিশেষ করে বারবার “এটা যোগ করো” বলার পরDependency graph tool বা অংশ ৮-এর মতো একটি script চালান
Metric বা নীতির নাম, বছর, লেখক ভুল (যেমন LCOM-এর ভুল সংজ্ঞা, বানানো “standard”)মূল প্রবন্ধ বা tool-এর documentation মিলিয়ে নিন
অন্য project থেকে হুবহু কোড, licence ছাড়া; বা prompt-এ আসল ছাত্রদের dataকোডের উৎস জিজ্ঞেস করুন, licence মেলান; prompt-এ কাল্পনিক data দিন

নিচের prompt-এ AI-কে কোড নয়, নকশার বিশ্লেষণ দিতে বলা হয়েছে — আর প্রতিটি দাবি কোডের নির্দিষ্ট জায়গার সাথে মেলাতে বলা হয়েছে, যাতে আপনি যাচাই করতে পারেন। আসল project-এর কোড পাঠানোর আগে secret, API key আর ব্যক্তিগত তথ্য সরিয়ে নিন।

নিচে একটি কাল্পনিক দোকানের বিলিং অ্যাপের কয়েকটি JavaScript file দিলাম (secret আর আসল গ্রাহকের তথ্য সরানো)। [এখানে file-গুলো] ১. প্রতিটি file কোন কোন file import করে, তার একটি তালিকা দাও; তারপর প্রতিটি file-এর Ca, Ce আর I = Ce/(Ca+Ce) হিসাব করে টেবিলে দেখাও। কোন নিয়মে গুনলে (file ধরে, না function ধরে) তা স্পষ্ট লেখো। ২. Dependency cycle থাকলে প্রতিটি চক্র file-এর নামসহ দেখাও। ৩. প্রতিটি file-এর cohesion-এর স্তর (coincidental থেকে functional) বলো, আর কারণ হিসেবে কোডের নির্দিষ্ট function-এর নাম দাও। ৪. Coupling কমানোর জন্য সবচেয়ে ছোট তিনটি refactoring ধাপ প্রস্তাব করো, প্রতিটির পরে কোন test চালাতে হবে তা বলো। নতুন interface শুধু তখনই প্রস্তাব করবে, যদি অন্তত দুটি বাস্তব ব্যবহার থাকে। ৫. কোনো metric-এর সংজ্ঞা বা বইয়ের উল্লেখ করলে উৎস বলো; নিশ্চিত না হলে "নিশ্চিত নই" লেখো।

AI-এর দেওয়া নকশা হাতে পেলে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১Parnas-এর ১৯৭২-এর KWIC উদাহরণ এত ছোট একটি সমস্যা — তবু পঞ্চাশ বছর পরেও কেন একে software design-এর ভিত্তি ধরা হয়?

কারণ প্রবন্ধটি একটি প্রশ্নের উত্তর বদলে দিয়েছিল: “module-এ ভাগ করার মাপকাঠি কী?” ১৯৬০-এর দশকের শেষে আর ১৯৭০-এর শুরুতে প্রচলিত উত্তর ছিল flowchart — প্রোগ্রাম যে ধাপে চলে, প্রতিটি বড় ধাপ একটি module. এটি স্বাভাবিক লাগে, কারণ আমরা কাজকে সময়ের ক্রমে ভাবি। Parnas দেখান, এই ভাগ আজকের জন্য ঠিক, কিন্তু কালকের জন্য দামি: সব ধাপ একই data-বিন্যাস জানে, তাই বিন্যাস বদলালে সব ধাপ বদলায়। তাঁর বিকল্প মাপকাঠি ছিল “কোন সিদ্ধান্ত বদলাতে পারে?” — প্রতিটি এমন সিদ্ধান্ত একটি module-এর গোপন বিষয়।

আজকের প্রায় প্রতিটি নকশা-নীতিতে এই চিন্তা পাওয়া যায়। Object-oriented ভাষার private field আর public method এর ভাষাগত রূপ। একটি ভালো REST API ভেতরের database schema ফাঁস করে না — schema বদলালেও client ভাঙে না। Microservice-এর “প্রতিটি service-এর নিজের database” নিয়মটি আসলে Parnas-এর Line Storage module-এর বড় সংস্করণ: data-র বিন্যাস একজনের গোপন বিষয়। SOLID-এর single responsibility principle-কে Robert C. Martin পরে “বদলের একটি কারণ” হিসেবে ব্যাখ্যা করেন — যা প্রায় সরাসরি Parnas-এর মাপকাঠি।

প্রবন্ধটির আরেকটি সূক্ষ্ম শিক্ষা: দুটি নকশাই কাজ করে, আকারেও কাছাকাছি, প্রথম দিনে কোনো পার্থক্য চোখে পড়ে না। নকশার গুণ দেখা যায় শুধু পরিবর্তনের সময়। এজন্য code review-তে “এটা চলছে” যথেষ্ট যুক্তি নয়; প্রশ্ন হওয়া উচিত “কাল যদি X বদলায়, কয়টি file বদলাবে?” Parnas পরবর্তী লেখায় এও বলেছেন যে কোন সিদ্ধান্ত বদলাবে তা আগে থেকে অনুমান করা সহজ নয় — অভিজ্ঞতা আর domain জ্ঞান লাগে, আর সব সিদ্ধান্ত লুকানোর চেষ্টা নিজেই অপ্রয়োজনীয় জটিলতা তৈরি করে।

মূল উপলব্ধি: ভালো module কাজের ধাপ ধরে নয়, বদলের সম্ভাবনা ধরে ভাগ হয়। নকশা বিচার করুন “আজ চলে কি না” দিয়ে নয়, “কাল বদলানো কত সহজ” দিয়ে।

প্র ০২“Low coupling” যদি এত ভালো, তাহলে কি প্রতিটি class-এর সামনে interface আর প্রতিটি কাজে আলাদা module বানানো উচিত?

না — আর এটি অভিজ্ঞ developer-দের মধ্যেও একটি চেনা ভুল। Coupling কমানোর প্রতিটি কৌশলের একটি খরচ আছে। একটি interface মানে আরেকটি file, আরেকটি নাম, আর কোড পড়ার সময় “আসলে কোনটা চলছে?” খুঁজতে আরেকটি লাফ। ধরুন FeeCalculator-এর একটিই বাস্তবায়ন, আর ভবিষ্যতে দ্বিতীয়টির কোনো চিহ্ন নেই। তার সামনে IFeeCalculator, একটি FeeCalculatorFactory আর একটি configuration — এতে coupling-এর metric হয়তো একটু ভালো দেখায়, কিন্তু কোড বোঝা কঠিন হয়, আর ভুল হলে খুঁজতে বেশি সময় লাগে।

Parnas-এর মাপকাঠি এখানেও সাহায্য করে: লুকাতে হয় সেই সিদ্ধান্ত, যা বদলাতে পারে। SMS gateway কোম্পানি বদলানো বাস্তব সম্ভাবনা — তাই notify-এর পেছনে একটি সরু interface যুক্তিসংগত। কিন্তু “নম্বর থেকে grade” function-এর সামনে interface বসানো সাধারণত অপ্রয়োজনীয়; নিয়ম বদলালে function-টিই বদলাবে। John Ousterhout তাঁর বইয়ে “shallow module”-এর সমালোচনা করেন — যে module-এর interface প্রায় তার কাজের সমান বড়, সে কিছু লুকায় না, শুধু একটি স্তর যোগ করে।

অতিরিক্ত ভাগের আরেকটি ফল: একটি সরল ব্যবসায়িক কাজ দশটি ছোট টুকরোয় ছড়িয়ে যায়। প্রতিটি টুকরো আলাদাভাবে “cohesive”, কিন্তু কাজটির নিয়ম বুঝতে দশটি জায়গা পড়তে হয় — এবং নিয়ম বদলালে দশটিই বদলায়, অর্থাৎ coupling আসলে কমেনি, শুধু লুকিয়েছে। পর্ব ২৬-এর YAGNI আর KISS এখানে ভারসাম্য দেয়: আজকের বাস্তব প্রয়োজনে নকশা করুন, আর কোড এমন রাখুন যাতে প্রয়োজন এলে refactor করা সহজ হয় (পর্ব ৪২)। ভালো test থাকলে পরে interface বের করে আনা সস্তা, তাই আগেভাগে বানানোর চাপ কমে।

মূল উপলব্ধি: লক্ষ্য সবচেয়ে কম metric নয়, বরং সবচেয়ে কম মোট খরচ — আজ বোঝার খরচ আর কাল বদলানোর খরচ মিলিয়ে।

প্র ০৩একটি monolith-কে microservice-এ ভাগ করলে কি coupling আপনাআপনি কমে যায়?

না। Service-এ ভাগ করা শুধু module-এর সীমাকে process আর network-এর সীমায় রূপান্তরিত করে। সীমাটি যদি ভুল জায়গায় টানা হয়, coupling কমে না — বরং দামি হয়ে যায়। একটি monolith-এ দুটি জড়ানো module একটি function call-এ কথা বলে; দুটি জড়ানো service কথা বলে network-এ, যেখানে latency আছে, ব্যর্থতা আছে, version-এর অমিল আছে। এই অবস্থাকে অনেকে “distributed monolith” বলেন: আলাদা deploy করা যায় এমন service, কিন্তু বাস্তবে সবগুলো একসাথে বদলাতে আর release করতে হয়।

এই পর্বের ভাষায় চেনা ফাঁদগুলো: কয়েকটি service একই database table পড়ে-লেখে — common coupling, শুধু global variable-এর জায়গায় table. একটি service অন্যটির ভেতরের API বা table-এর বিন্যাস জেনে বসে আছে — content coupling. একটি অনুরোধে A → B → C → D পরপর synchronous call — connascence of timing আর execution; একটি ধীর হলে পুরো শিকল ধীর। আর A B-কে ডাকে, B আবার A-কে — dependency cycle, যা আলাদা deploy-কে প্রায় অসম্ভব করে।

উল্টো দিকে, ঠিক সীমায় ভাগ হলে microservice সত্যিই সুবিধা দেয়: একটি দল তার service স্বাধীনভাবে বদলায়, scale করে আর release করে। সেই “ঠিক সীমা” খোঁজার প্রশ্ন high cohesion-এর প্রশ্ন — কোন ব্যবসায়িক দায়িত্বগুলো একসাথে বদলায়? Domain-driven design-এর bounded context (পর্ব ৪০-এ ধারণা) এই উত্তর খোঁজার একটি পদ্ধতি। Conway's law মনে করিয়ে দেয়, সীমাটি দলের কাঠামোর সাথেও মিলতে হবে; দুই দলের মধ্যে ভাগ করা একটি service সাধারণত দুই দিকে টান খায়। এজন্য অনেক অভিজ্ঞ লেখক আগে একটি সুবিন্যস্ত modular monolith বানাতে বলেন: module-এর সীমা কোডে পরিষ্কার হলে, আর সত্যিই আলাদা scale বা আলাদা দলের প্রয়োজন হলে, তখন সেই সীমা ধরে service আলাদা করা সহজ ও কম ঝুঁকির। System Design কোর্সের microservice পর্বে এর বিস্তারিত trade-off আছে।

মূল উপলব্ধি: Architecture coupling সমস্যার সমাধান করে না; ভালো নকশা করে। খারাপ সীমা network-এ নিলে শুধু সমস্যার দাম বাড়ে।

প্র ০৪I, A, D বা LCOM-এর মতো metric দিয়ে কি নকশার মান বিচার করা যায়? দলের লক্ষ্য হিসেবে “D < 0.3” বেঁধে দিলে কী হবে?

Metric খুব ভালো ধোঁয়ার সংকেত (smoke detector), কিন্তু খারাপ বিচারক। এর কয়েকটি কারণ এই পর্বেই দেখেছেন। প্রথমত, সংজ্ঞা একরকম নয়: LCOM-এর অন্তত চার-পাঁচটি রূপ আছে, আর একই class-এ একটি সূত্র দেয় ৩, অন্যটি ১৩। Martin-এর Ca-Ce class ধরে গোনা হয়, অনেক tool package বা file ধরে গোনে। দুটি tool-এর সংখ্যা তাই সরাসরি তুলনীয় নয়।

দ্বিতীয়ত, প্রসঙ্গ ছাড়া সংখ্যা ভুল রায় দেয়। আমাদের model package zone of pain-এ, কিন্তু সেখানে যদি শুধু Money বা Date-এর মতো খুব কম বদলানো value class থাকে, তাতে ক্ষতি নেই — Martin নিজেই এ কথা বলেছেন। একটি data class-এর getter-setter LCOM বাড়িয়ে দেয়, অথচ class-টি ঠিকই আছে। আবার একটি বিশৃঙ্খল module-ও সংখ্যায় ভালো দেখাতে পারে, যদি সবকিছু একটি বড় function-এ থাকে।

তৃতীয়ত, Goodhart's law (অর্থনীতিবিদ Charles Goodhart-এর পর্যবেক্ষণের প্রচলিত রূপ): কোনো মাপ লক্ষ্য হয়ে গেলে সেটি আর ভালো মাপ থাকে না। “D < 0.3” বেঁধে দিলে দল খুব সহজে অপ্রয়োজনীয় interface যোগ করে A বাড়াবে, বা package ভেঙে-জুড়ে সংখ্যা মেলাবে — নকশা ভালো না করেই। পর্ব ৩-এ software metric নিয়ে একই সতর্কতা দেখেছেন। Metric-কে লক্ষ্য না করে প্রবণতা হিসেবে দেখা ভালো: কোনো package-এর D প্রতি মাসে বাড়ছে, বা নতুন একটি dependency cycle দেখা দিয়েছে — এগুলো code review-তে আলোচনার বিষয়।

কিছু metric অবশ্য প্রায় নিয়ম হিসেবেই চলে: অনেক দল CI-তে package-স্তরের dependency cycle-কে build ব্যর্থ করার কারণ বানায়, কারণ চক্র সংখ্যার ব্যাপার নয়, হ্যাঁ-না প্রশ্ন, আর এর ক্ষতি প্রায় সবসময় বাস্তব। কিছু দল layer-এর নিয়মও (যেমন “ui model-কে ডাকতে পারবে, model ui-কে নয়”) স্বয়ংক্রিয় test দিয়ে পাহারা দেয়।

মূল উপলব্ধি: Metric প্রশ্ন তৈরি করে, উত্তর নয়। সংখ্যা দেখে জায়গা চিনুন, তারপর কোড পড়ে আর “কী বদলাতে পারে?” জিজ্ঞেস করে সিদ্ধান্ত নিন।

প্র ০৫DRY বলে পুনরাবৃত্তি এড়াতে; কিন্তু দুটি module-এর জন্য একটি shared library বানালে নতুন coupling তৈরি হয়। এই দ্বন্দ্ব কীভাবে মেটাবেন?

দ্বন্দ্বটি বাস্তব, আর বড় system-এ প্রায় প্রতিদিন দেখা দেয়। ধরুন fees আর exam দুটোতেই “ছাত্রের নাম বাংলায় সাজানো”-র প্রায় একই দশ লাইন আছে। DRY-এর সরল প্রয়োগ: একটি common package বানান, দুজনেই সেখান থেকে নিক। এখন দুটি স্বাধীন module একটি তৃতীয় জিনিসের ওপর নির্ভর করে; exam-এর জন্য function-টি বদলালে fees-ও প্রভাবিত হয়। Microservice-এর জগতে এটি আরও কষ্টের: একটি shared library-র নতুন version বের হলে দশটি service-কে update আর release করতে হতে পারে।

মীমাংসার চাবি হলো প্রশ্নটি: এই দুটি কোড কি একই জ্ঞান প্রকাশ করে, নাকি শুধু আজ দেখতে একরকম? Andy Hunt ও Dave Thomas The Pragmatic Programmer-এ (১৯৯৯) DRY-কে মূলত জ্ঞানের পুনরাবৃত্তি এড়ানো হিসেবে সংজ্ঞায়িত করেছিলেন, অক্ষরের পুনরাবৃত্তি নয়। যদি দুটি কোড একই ব্যবসায়িক নিয়ম হয় — যেমন “দেরির জরিমানা দিনে ২০ টাকা” — তাহলে নিয়মটি এক জায়গায় থাকতেই হবে, নইলে একদিন একটি বদলাবে আর অন্যটি পুরোনো থাকবে। কিন্তু যদি দুটি আলাদা কারণে বদলাবে, তাহলে আজকের মিল কাকতালীয়; একত্র করলে coincidental cohesion-এর একটি নতুন “Utils” জন্ম নেয়।

Go ভাষার সম্প্রদায়ে একটি প্রবাদ চলে, Rob Pike-এর বক্তৃতায় জনপ্রিয় হয়েছে বলে উল্লেখ করা হয়: “a little copying is better than a little dependency”. এর মানে পুনরাবৃত্তির প্রশংসা নয়; মানে হলো, ছোট আর স্থির কোডের জন্য নতুন নির্ভরতার দাম কপির দামের চেয়ে বেশি হতে পারে। একটি প্রচলিত ব্যবহারিক নিয়ম “rule of three”: তৃতীয়বার একই জিনিস দরকার হলে, তখন মিলটা আসল কি না বোঝা যায়, তখন সাধারণ অংশ বের করুন। পর্ব ২৬-এ DRY-র এই সূক্ষ্মতা আবার আসবে।

মূল উপলব্ধি: একই জ্ঞান এক জায়গায় রাখুন, কিন্তু শুধু চেহারার মিলের জন্য স্বাধীন module-দের একটি shared জিনিসে বেঁধে ফেলবেন না।

অনুশীলন

হিসাব, শ্রেণিবিভাগ, আউটপুট অনুমান আর নকশার সমালোচনা — প্রতিটির পূর্ণ উত্তর দেওয়া আছে।

  1. I, A, D হিসাব করুন: একটি কাল্পনিক দোকানের বিলিং system-এ দুটি package. (ক) reports: ৩টি package এর ওপর নির্ভর করে, এটি নির্ভর করে ৫টির ওপর; ১০টি class-এর ২টি interface. (খ) core: ৯টি package এর ওপর নির্ভর করে, এটি নির্ভর করে ১টির ওপর; ১২টি class-এর ১টি abstract. প্রতিটির I, A, D বের করুন, কোনটি zone of pain-এ বলুন, আর তার জন্য কী করবেন?

    (ক) reports: Ca = ৩, Ce = ৫. I = 5 / (3 + 5) = 0.625. A = 2 / 10 = 0.20. D = |0.20 + 0.625 − 1| = |−0.175| = 0.175. Main sequence-এর কাছে — ঠিক আছে। অনেকের ওপর নির্ভর করে আর কম জন এর ওপর নির্ভর করে, তাই বদলানো তুলনামূলক নিরাপদ।

    (খ) core: Ca = ৯, Ce = ১. I = 1 / 10 = 0.10. A = 1 / 12 ≈ 0.083. D = |0.083 + 0.10 − 1| = 0.817 (দশমিকে গোল করে)। A + I < 1 আর D ≥ 0.5, তাই zone of pain: নয়জন নির্ভর করে, অথচ প্রায় সবই concrete.

    কী করবেন: প্রথমে দেখুন core-এর কোন অংশ সত্যিই বদলায়। যদি শুধু স্থির value class (দাম, পরিমাণ, তারিখ) থাকে, তাহলে কিছু না করাই ঠিক। যদি ঘনঘন বদলানো নিয়ম থাকে (যেমন VAT বা ছাড়ের হিসাব), তাহলে (১) সেগুলো নতুন একটি কম-stable package-এ সরান, অথবা (২) নির্ভরশীলরা যেটুকু ব্যবহার করে তার জন্য একটি interface দিন, আর বাস্তবায়ন আলাদা রাখুন (dependency inversion, পর্ব ২৮)। এতে core-এর A বাড়ে বা অস্থির অংশ বেরিয়ে যায়।

  2. Coupling আর cohesion চিনুন: প্রতিটির স্তর বলুন। (ক) printBill(order, "PREVIEW") — দ্বিতীয় argument ঠিক করে function ছাপাবে না শুধু দেখাবে। (খ) vat(amount, rate) ডাকা হয় শুধু দুটি সংখ্যা দিয়ে। (গ) class Helpers { toTaka(); resizeImage(); sendEmail(); } (ঘ) nightlyJobs() — রাত ১২টায় চলে: পুরোনো log মোছে, stock report বানায়, backup নেয়। (ঙ) discount.js সরাসরি cart._items array বদলায়। (চ) billFromScan(code): barcode পড়ে → পণ্য খোঁজে → দাম বের করে → বিলে যোগ করে, প্রতিটির ফল পরের ধাপে যায়।
    • (ক) Control coupling — flag দিয়ে ডাকনেওয়ালা ভেতরের পথ বাছাই করছে। সমাধান: previewBill আর printBill আলাদা function.
    • (খ) Data coupling — শুধু দরকারি সরল মান; সবচেয়ে ভালো স্তর।
    • (গ) Coincidental cohesion — টাকা সাজানো, ছবি ছোট করা আর email পাঠানোর মধ্যে কোনো সম্পর্ক নেই।
    • (ঘ) Temporal cohesion — কাজগুলো একই সময়ে চলে বলেই একসাথে। একটি scheduler আলাদা আলাদা তিনটি কাজ ডাকলে ভালো হয়।
    • (ঙ) Content coupling — অন্য module-এর ভেতরের data সরাসরি বদলানো; সবচেয়ে খারাপ। cart.applyDiscount(...) জাতীয় method দিন।
    • (চ) Sequential cohesion — একটি ধাপের output পরের ধাপের input. এটি বেশ শক্ত স্তর; প্রতিটি ধাপ নিজে আলাদা functional function হলে আরও ভালো।
  3. আউটপুট অনুমান করুন: অংশ ৮-এর findCycles function-টিকে (তিন রঙের DFS; grey node-এ পৌঁছালে বর্তমান পথ থেকে চক্র ছাপে; node আর তীর লেখার ক্রমেই দেখা হয়) এই graph দেওয়া হলো: login: [session], session: [user, audit], user: [session], audit: [log], log: [audit, login], report: [log]. for (const c of findCycles(g2)) console.log(" " + c); কী ছাপবে, কোন ক্রমে?
      session -> user -> session
      audit -> log -> audit
      login -> session -> audit -> log -> login

    DFS শুরু login থেকে (প্রথম key). পথ: login → session → user. user-এর তীর session-এ, যা grey: প্রথম চক্র session -> user -> session. user black হয়, ফিরে session-এর পরের তীর audit, তারপর log. পথ এখন login → session → audit → log. log-এর প্রথম তীর audit (grey): দ্বিতীয় চক্র। দ্বিতীয় তীর login, সেটিও grey (পথের শুরু): তৃতীয় চক্র, পথের login থেকে শেষ পর্যন্ত। শেষে report white, কিন্তু তার তীর log-এ যা ততক্ষণে black — নতুন কিছু নেই। খেয়াল করুন, report নিজে কোনো চক্রে নেই, যদিও চক্রে থাকা একটি node-এর ওপর নির্ভর করে।

  4. নকশার সমালোচনা: একটি কাল্পনিক দোকানের বিলিং অ্যাপে BillingManager class-এর method আর তারা যা ছোঁয়: add_item (cart), remove_item (cart), total (cart, vat_rate), print_bill (cart, printer; ভেতরে total ডাকে), save_stock (stock_db), low_stock_alert (stock_db, sms). (ক) LCOM4 কত? (খ) কীভাবে ভাগ করবেন? (গ) ভাগের পরে নির্ভরতার দিক কেমন হওয়া উচিত, কোথায় চক্রের ঝুঁকি?

    (ক) প্রথম চারটি method cart ভাগ করে (আর print_bill total-কে ডাকে) — এক দল। শেষ দুটি stock_db ভাগ করে — আরেক দল। দুই দলের মধ্যে কোনো ভাগ করা attribute বা ডাক নেই। তাই LCOM4 = ২: একটি class-এর ভেতরে দুটি class লুকিয়ে আছে।

    (খ) Cart (add_item, remove_item, total) আর StockKeeper (save_stock, low_stock_alert). print_bill-কে আলাদা BillPrinter-এ নেওয়া যুক্তিসংগত, কারণ printer বদলানো বদলের একটি আলাদা কারণ; এটি data হিসেবে cart-এর লাইনগুলো আর total নেবে (data বা ছোট stamp coupling). একইভাবে low_stock_alert-এর SMS-এর অংশ একটি Notifier-এর পেছনে রাখুন, যাতে gateway বদলালে stock-এর কোড না বদলায়।

    (গ) বিক্রি শেষ হলে stock কমাতে হবে — তাই checkout-এর ধাপ Cart থেকে StockKeeper-এ যায়। ঝুঁকি: কেউ যদি StockKeeper-এর ভেতর থেকে cart দেখে (“এই পণ্য কোন cart-এ আছে?”), তাহলে চক্র। সমাধান: একটি উপরের Checkout ধাপ দুজনকেই ডাকুক — Checkout → Cart, Checkout → StockKeeper, StockKeeper → Notifier — নিচের দিকে একমুখী, চক্রহীন graph.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমCoupling-এর স্তরগুলো সবচেয়ে খারাপ থেকে সবচেয়ে ভালো ক্রমে সাজান (▲▼ চেপে সরান)

  1. Content — অন্যের ভেতরের data বা কোডে সরাসরি হাত
  2. Common — একই global data সবাই পড়ে-লেখে
  3. External — বাইরে থেকে চাপানো একই format বা protocol
  4. Control — flag পাঠিয়ে ভেতরের পথ বাছাই
  5. Stamp — পুরো record পাঠানো, লাগে একটু
  6. Data — শুধু দরকারি সরল মান

একটি package-এর ওপর ২টি package নির্ভর করে, আর এটি নিজে ৬টি package-এর ওপর নির্ভর করে। এর ১০টি class-এর ৫টি abstract. Robert C. Martin-এর সূত্রে I আর D কত?

  • I = 0.25, D = 0.25
  • I = 0.75, D = 0.25
  • I = 0.75, D = 1.25
  • I = 3, D = 2.5

একটি function sendReport(student) পুরো student object নেয়, কিন্তু ভেতরে ব্যবহার করে শুধু student.phone. এটি কোন coupling?

  • Data coupling
  • Control coupling
  • Common coupling
  • Stamp coupling

একটি init() function প্রোগ্রাম শুরুর সময় database খোলে, SMS template লোড করে আর print queue খালি করে — কারণ তিনটিই শুরুতে দরকার। এর cohesion কোন স্তরের?

  • Temporal
  • Functional
  • Sequential
  • Communicational

Parnas (১৯৭২)-এর KWIC তুলনার মূল শিক্ষা কোনটি?

  • Flowchart ধরে ভাগ করা নকশা কাজই করে না
  • যত বেশি module, তত ভালো নকশা
  • Module ভাগ হওয়া উচিত বদলাতে পারে এমন নকশা-সিদ্ধান্ত লুকিয়ে রাখার ভিত্তিতে
  • সব module-এর একই data বিন্যাস জানা উচিত, যাতে দ্রুত চলে

একটি class-এ চারটি method: a আর b attribute x ব্যবহার করে, c ব্যবহার করে y, d ব্যবহার করে z, আর d ভেতরে c-কে ডাকে। LCOM4 (method-এর connected component, attribute ভাগ বা ডাক থাকলে যুক্ত) কত?

  • ১
  • ২
  • ৩
  • ৪

দুটি microservice একই database table সরাসরি পড়ে আর লেখে। Coupling-এর ক্লাসিক স্তরের ভাষায় এটি সবচেয়ে কাছাকাছি কোনটির?

  • Data coupling
  • Stamp coupling
  • Control coupling
  • Common coupling

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Module ভাগ করুন বদলাতে পারে এমন সিদ্ধান্ত ধরে, কাজের ধাপ ধরে নয় — Parnas-এর information hiding.
  2. Coupling খারাপ থেকে ভালো: content → common → external → control → stamp → data; লক্ষ্য দুর্বল স্তর আর কম সংখ্যা, শূন্য নয়।
  3. Cohesion দুর্বল থেকে শক্ত: coincidental → logical → temporal → procedural → communicational → sequential → functional; এক বাক্যে কাজ বলা যায় কি না দেখুন।
  4. I = Ce/(Ca+Ce), A = Na/Nc, D = |A + I − 1|; LCOM-এর কোন রূপ তা বলুন; metric প্রশ্ন তোলে, রায় দেয় না।
  5. Dependency graph হবে চক্রহীন; low coupling সম্পাদনার ঢেউ কমায়, test তবু লাগে; microservice-এ একই নিয়ম, বেশি দামে।

পরের পর্বে: মাপকাঠি পাওয়া গেল; এবার প্রতিদিনের নিয়ম। পর্ব ২৬-এ DRY, KISS, YAGNI, separation of concerns আর Law of Demeter — কখন মানবেন, আর কখন অন্ধভাবে মানলে উল্টো ক্ষতি।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান