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

Feature Envy: যে method সারাদিন অন্যের class-এ বসে থাকে

Feature Envy code smell শেখো একটা সহজ স্কুলের গল্পের মাধ্যমে। যখন একটা method নিজের class-এর চেয়ে অন্য class-এর data বেশি ব্যবহার করে, তখন সেটা আসলে ওই অন্য class-এই থাকার কথা। সারানোর উপায় হলো Move Method।

23 মিনিট আপডেট: June 11, 2026beginner
code-smellscouplersfeature-envymove-methodencapsulationtypescriptcsharp

🎒 যে ছেলে সারাদিন পাশের class-এ কাটায়

ধরো তুমি ৭-বি class-এর রুবেলের গল্প শুনছ।

রুবেলের নিজের classroom আছে। নিজের desk, নিজের bench, নিজের blackboard, নিজের class teacher — মিসেস নাসরিন। কিন্তু অদ্ভুত ব্যাপার হলো: রুবেল কখনো সেখানে থাকে না। প্রতিদিন সকালে সে ৭-বিতে ব্যাগ রাখে, ৭-বির হাজিরা খাতায় সই করে, আর সাথে সাথে পাশের class ৭-এতে চলে যায়।

কেন? কারণ তার সব কাজের জন্য ৭-এর জিনিসপত্র দরকার। সে যে হাজিরা খাতা update করে সেটা ৭-এর। সে যে chart আঁকে সেটায় ৭-এর নম্বর লাগে। সে যে notice লেখে সেটা ৭-এর picnic-এর। সারাদিন সে ৭-এর আলমারিতে দাঁড়িয়ে থাকে, তাদের cupboard খোলে, তাদের chalk ধার নেয়, তাদের register পড়ে। দুপুরের মধ্যে ৭-এর ছাত্রছাত্রীরা সবাই জানে রুবেল কোথায় দাঁড়িয়ে থাকবে — তাদের আলমারিতে, তাদের register খুলে।

শুরুটা ছোট ছিল। প্রথম দিন রুবেল শুধু একটা chart ধার নিয়েছিল। পরের সপ্তাহে নম্বরের তালিকাও লাগল। মাস শেষে সে মুখস্থ করে ফেলল ৭-এ কোথায় কী রাখে — register-এর জন্য তৃতীয় তাক, chalk-এর জন্য বাম drawer, picnic-এর file অভিধানের পেছনে। সে প্রতিবেশীর classroom নিজের চেয়ে ভালো চেনে।

একটু ভাবো — এখন কী হচ্ছে, একজন একজন করে।

মিসেস ফাতেমা, ৭-এর class teacher, পরিদর্শনের আগে আলমারি সাজাতে চান। পারছেন না! register দ্বিতীয় তাকে সরালে রুবেলের পুরো routine ভেঙে পড়বে, আর picnic-এর notice দেরি হয়ে যাবে। নিজের classroom, অথচ একজন বাইরের ছেলের জন্য একটা তাকও নাড়ানো যাচ্ছে না।

৭-এর ছাত্রছাত্রীরা বিরক্ত: "এই ছেলেটা কে? সবসময় আমাদের আলমারি খুলছে কেন? এখন আমাদের সব খোলা রাখতে হচ্ছে শুধু তার জন্য।" যে আলমারি আগে তালা দেওয়া থাকত, সেটা এখন সারাদিন খোলা।

আর ৭-বিতে মিসেস নাসরিন হাজিরা register-এর দিকে তাকিয়ে আছেন: "রুবেল আমার তালিকায় আছে, কিন্তু আমি কখনো দেখি না। class-এর কোনো কাজে কি তাকে পাব?" পাবেন না। রুবেল শুধু কাগজে তার।

একদিন প্রধান শিক্ষিকা মিসেস সুমাইয়া corridor থেকে সব দেখলেন। কাউকে বকলেন না। শুধু একটা সহজ কথা বললেন: "রুবেলের সব কাজ যদি ৭-এর জিনিস দিয়ে হয়, তাহলে রুবেল ৭-এতেই আছে। ওকে transfer করো।"

একটাই transfer order। আলমারিতে আবার তালা পড়ল, মিসেস ফাতেমা স্বাধীনভাবে তাক সাজালেন, আর মিসেস নাসরিনের register অবশেষে সত্যি কথা বলল।

এটাই আজকের smell-এর পুরো শিক্ষা। কোডে আমরা প্রায়ই এমন একটা method দেখি যেটা ঠিক রুবেলের মতো। সেটা A class-এর মধ্যে লেখা, কিন্তু তার প্রতিটা লাইন B class-এর data ব্যবহার করে। B-র getter বারবার call করে। নিজের class প্রায় ছোঁয় না। এরকম method অন্য class-কে হিংসা করছে — আর সমাধানটা সাধারণত মিসেস সুমাইয়ার নির্দেশের মতোই: transfer করো।

চিত্র ১: রুবেলের স্কুলের দিন — প্রায় প্রতিটা কাজ ভুল classroom-এ হচ্ছে

ওই journey-তে satisfaction score দেখো। মাঝের দিকে সবাই অসুখী — যে ধার নিচ্ছে, যে দিচ্ছে, দুই শিক্ষিকা। একমাত্র সুখের মুহূর্ত শেষে transfer হওয়ার সময়। এই ছবিটা মনে রাখো। বাকি পোস্টটা শুধু এই যাত্রাটাই TypeScript আর C#-এ লেখা।

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

Feature Envy হলো একটা code smell যেখানে একটা method নিজের class-এর data-র চেয়ে অন্য class-এর data-তে বেশি আগ্রহী। সে যে "feature" হিংসা করছে সেগুলো হলো অন্য কারো object-এর field আর getter।

Martin Fowler তার Refactoring বইতে এটাকে বর্ণনা করেছেন এভাবে: এমন একটা method যেটা মনে হয় নিজের চেয়ে অন্য object-এর সাথে বেশি কথা বলছে। Classic লক্ষণ হলো একটা method যেটা কোনো মান বের করতে অন্য object-এর অর্ধ-ডজন getter method call করে।

এটা কেন smell, style choice নয়? কারণ object-oriented design-এর একটা সোনালি নিয়মের জন্য:

Data আর সেই data ব্যবহার করে এমন behaviour একই class-এ থাকা উচিত।

যখন তারা একসাথে থাকে, পরিবর্তন একটা জায়গায় হয়। যখন তারা আলাদা হয়ে যায়, data class-এ পরিবর্তন দূরের file-গুলোর code ভেঙে দেয়। Feature Envy হলো প্রথম সংকেত যে data আর behaviour আলাদা হয়ে গেছে।

💡

একটা দ্রুত মানসিক পরীক্ষা করো: method টা পড়ো আর জিজ্ঞেস করো, "অন্য object-কে ছোঁয় এমন প্রতিটা লাইন মুছলে কী বাকি থাকবে?" যদি প্রায় কিছুই না থাকে, তাহলে method টা আসলে এখানে নেই। এটা ওই object-এর সাথেই আছে যেটাকে সে বারবার ছুঁয়ে যাচ্ছে। Fowler-এর পরামর্শ সহজ: একটা method ওই class-এ থাকা উচিত যার data সে সবচেয়ে বেশি ব্যবহার করে।

আরেকটা গুরুত্বপূর্ণ কথা। Feature Envy Couplers নামের একটা smell পরিবারের অংশ। Coupling মানে দুটো class কতটা শক্তভাবে পরস্পরের সাথে জড়িয়ে আছে। Couplers — Feature Envy, Inappropriate Intimacy, Message Chains, আর Middle Man — সবই এমন class নিয়ে যেগুলো অনেক শক্তভাবে, বা ভুলভাবে জড়িয়ে আছে। Feature Envy প্রথম আর সবচেয়ে সাধারণটা: একমুখী টান, যেখানে একটা method বারবার অন্য class-এ হাত বাড়ায়।

এই smell-এর পেছনে একটা বিখ্যাত এক-লাইনের নীতিও আছে: "Tell, don't ask." সুস্থ code একটা object-কে কী করতে হবে বলেstudent.grade() — আর object-কে নিজের data ব্যবহার করতে দেয়। হিংসুটে code object-এর কাছ থেকে raw data চায় — নম্বর, সর্বোচ্চ নম্বর, হাজিরা — তারপর বাইরে নিজে চিন্তা করে। যখনই নিজেকে বারবার চাইতে দেখবে, সন্দেহ করো যে চিন্তাটা ভেতরে নিয়ে যাওয়া উচিত।

কলেজ কর্নার: গবেষকরা এই smell-কে একটা পরিমাপযোগ্য detection strategy-তে রূপ দিয়েছেন। Lanza আর Marinescu Object-Oriented Metrics in Practice বইতে একটা method-কে envious বলেন যখন তিনটা metric একসাথে মিলে যায়: ATFD (Access To Foreign Data — method টা অন্য class-এর কতগুলো field/getter ছোঁয়) বেশি, LAA (Locality of Attribute Accesses — method টা যে data ছোঁয় তার কতটুকু নিজের class-এর) কম (প্রায় এক-তৃতীয়াংশের নিচে), আর FDP (Foreign Data Providers — কতগুলো আলাদা class সেই বিদেশি data দিচ্ছে) ছোট, প্রায়ই মাত্র একটা। শেষের শর্তটা গুরুত্বপূর্ণ: ATFD বেশি + FDP এক মানে method-এর মন ঠিক একটা class-এর — একটা perfect Move Method candidate। JDeodorant আর code-quality linter-গুলো ঠিক এই formula-র variation implement করে। দেখো, formal metric-গুলো মিসেস সুমাইয়ার পর্যবেক্ষণকেই গাণিতিক ভাষায় বলছে: অনেক বিদেশি ছোঁয়া, প্রায় কোনো নিজস্ব ছোঁয়া নেই, সব একটা প্রতিবেশীর দিকে।

বিস্তারিত যাওয়ার আগে পুরো বিষয়টা এক নজরে:

চিত্র ২: Feature Envy এক নজরে — লক্ষণ, খরচ, সমাধান, আর যেসব ক্ষেত্রে ছেড়ে দেওয়া উচিত

👀 কীভাবে চিনবে

একটা সহজ checklist। যেকোনো method পড়ো আর বাক্সে টিক দাও:

  • method টা একটা বিদেশি object-এর getter অনেকবার call করে — order.lines, order.taxRate, order.currency — যেন একটা হাত বারবার অন্যের tiffin-এ ঢুকছে।
  • method টা নিজের class-এর field শূন্য বা একবার ব্যবহার করে।
  • method টা parameter হিসেবে একটা object নেয় আর তারপর field by field সেটাকে ভেঙে কিছু হিসাব করে।
  • কল্পনা করো method টাকে অন্য class-এ নিয়ে যেতে — সেখানে সেটা অনেক স্বাভাবিক মনে হবে — order.total() বনাম printer.computeTotalOf(order)
  • হিংসা করা class-এ অনেক getter আছে যেগুলো শুধু বাইরের কেউ data টেনে নিয়ে অন্য জায়গায় হিসাব করার জন্য।
  • একটা লম্বা method-এর এক block অন্য সবকিছুর চেয়ে একটা collaborator-কে অনেক বেশি উল্লেখ করে। সেই block-টাই হিংসুটে অংশ, বাকি method ঠিক থাকলেও।

একটা ছোট তুলনা table পার্থক্যটা পরিষ্কার করবে:

জিজ্ঞাসাসুস্থ methodহিংসুটে method
কার data সবচেয়ে বেশি পড়ে?নিজের class-এর fieldঅন্য object-এর field
একটা বিদেশি object-এ কতবার getter call?শূন্য থেকে দুইতিন, চার, পাঁচ বা বেশি
logic কোথায় পড়তে সবচেয়ে স্বাভাবিক লাগবে?ঠিক যেখানে আছেঅন্য class-এর ভেতরে
অন্য class আকার বদলালে কী হবে?কিছু নাএই method ভেঙে পড়বে
অন্য class-কে কি শুধু এর জন্য public getter রাখতে হয়?নাহ্যাঁ — getter বাড়তে থাকে

গোনার কৌশলটা লক্ষ্য করো। অন্য object-এ একবার getter call করা স্বাভাবিক সহযোগিতা — বন্ধুরা কথা বলে। smell শুরু হয় যখন সংখ্যা আর ধরন দেখায় যে method-এর মন অন্য জায়গায়। একটা সাধারণ হিংসুটে method-এর data touch সত্যিই গুনলে ছবিটা এরকম:

চিত্র ৩: হিংসুটে method-এ touch গোনো — slice-গুলোই সব বলে দেয়

যখন সবচেয়ে বড় slice অন্য কারো class-এর, method টা রুবেল। tool-ও লাগবে না — একবার pencil হাতে method টা পড়ো আর দুটো tally mark রাখো: বিদেশি আর নিজস্ব

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

আসল খরচগুলো গুনি, মিসেস সুমাইয়া যেভাবে রুবেলের ঘণ্টা গুনেছিলেন।

১. এটা encapsulation ভাঙে। Encapsulation মানে একটা class তার ভেতরের বিষয় private রাখে আর স্বাধীনভাবে সেগুলো বদলাতে পারে। কিন্তু হিংসুটে method অন্য class-এর ঠিক ভেতরের গঠনের উপর নির্ভর করে। Order যদি tax রাখার পদ্ধতি বদলায়, কোনো দূরের InvoicePrinter file-এ একটা method ভেঙে পড়বে। Order edit করা মানুষ হয়তো জানেও না ওই file টা আছে। এটা মিসেস ফাতেমা নিজের তাক নাড়াতে না পারার মতো।

২. একটা ধারণা অনেক file-এ ছড়িয়ে পড়ে। ধরো পাঁচটা আলাদা class Order-এর data থেকে জিনিস হিসাব করে। এখন "order নিয়ে নিয়ম" পাঁচ জায়গায় থাকে। order-এ কী হতে পারে বুঝতে একজন নতুন programmer পুরো project খুঁজতে বাধ্য হবে।

৩. এটা বিস্তৃত, ফুটো interface বানাতে বাধ্য করে। হিংসুটে method-দের খাওয়াতে, Order-কে প্রতিটা field-এর জন্য getter expose করতে হবে। Private জিনিস public হয়ে যায়। একবার public হলে কখনো নিরাপদে বদলানো যায় না — কারণ কে পড়ছে কে জানে। এটা সেই আলমারি যেটা সারাদিন একজন বাইরের মানুষের জন্য খোলা থাকতে হয়।

৪. এটা duplication ডাকে। একবার data expose হলে, প্রতিটা client নিজের মতো করে "total" হিসাব করে। তিনটা সামান্য আলাদা total formula তিনটা file-এ দেখা যায়। একটায় bug আছে। খুঁজে বের করো।

এখানে ছবিটা। একটা method, ভুল class-এ বসে, সব তীর সীমানা পেরিয়ে যাচ্ছে:

চিত্র ৪: হিংসুটে method-এর সব তীর অন্য class-এ যাচ্ছে — রুবেল ৭-বিতে বসে কিন্তু সব কাজ ৭-এতে করছে

তীরগুলো গোনো। চারটা তীর বাইরে যাচ্ছে, শূন্যটা নিজের কাছে থাকছে। এই ছবিটাই Feature Envy। code পড়তেও হবে না — তীরগুলোই গল্প বলে।

আর duplication থেমে থাকে না। কয়েক মাস স্বাভাবিক development-এ একটা হিংসুটে formula-র কী হয় দেখো, যখন আরও service একই উত্তর চায়:

চিত্র ৫: একই হিংসুটে formula-র copy সময়ের সাথে বাড়তে থাকে — প্রতিটা copy একটা ভবিষ্যৎ bug

মাস ১: printer grade হিসাব করে। মাস ৩: promotion checker formula copy করে। মাস ৬: SMS service আর dashboard copy করে। মাস ৯: সাতটা copy, আর স্কুল grading নিয়ম বদলায়। কেউ এখন সাতটা file-এ treasure hunt খেলছে — আর সাধারণত ছয়টা খুঁজে পায়।

কলেজ কর্নার: coupling-এর ভাষায়, প্রতিটা হিংসুটে method তার class-এর CBO (Coupling Between Objects) বাড়ায় আর তার module-এর efferent coupling (বাইরের দিকে নির্ভরতা) বাড়ায়, আবার দুটো class-এরই cohesion কমায়। হিংসা করা class-এর data বেশিরভাগ বাইরে থেকে ব্যবহার হয় (high LCOM, Lack of Cohesion of Methods), আর হিংসুটে class নিজের field-এর সাথে সম্পর্কহীন logic বহন করে। Structured maintenance গবেষণা ধারাবাহিকভাবে দেখায় যে high ATFD-style envy score আছে এমন class-গুলো তাদের হিংসা করা class-এর সাথে গড়ের চেয়ে অনেক বেশি একসাথে পরিবর্তিত হয় — এই smell টা সত্যিই version-control co-change history-তে দেখা যায়। দুটো file সবসময় একই commit-এ থাকলে, দেখো একটা অন্যটাকে হিংসা করছে কিনা।

⚠️

Feature Envy সাধারণত একা থাকে না। ছেড়ে দিলে বাড়ে। হিংসা করা class আরও getter expose করে, আরও বাইরের কেউ সেগুলো দিয়ে হিসাব করতে শুরু করে, আর শীঘ্রই দুটো class দুদিক থেকেই একে অপরের মধ্যে হাত বাড়াচ্ছে। সেই পারস্পরিক সংস্করণের নিজস্ব নাম আছে — Inappropriate Intimacy — আর সেটা সামলানো অনেক কঠিন। envy এক-মুখী থাকতেই সারিয়ে নাও।

💻 বাস্তব জীবনের code উদাহরণ

রুবেলের গল্পটা code-এ রাখা যাক। ধরো আমাদের স্কুল app report card print করে। কেউ একটা ReportCardPrinter লিখেছে, আর তার মধ্যে একটা method যেটা student-এর grade হিসাব করে:

class Student {
  constructor(
    public name: string,
    public marks: number[],       // marks in each subject
    public maxMarksPerSubject: number,
    public attendancePercent: number,
  ) {}
}
 
class ReportCardPrinter {
  printGradeLine(student: Student): string {
    // Every single line below reads Student's data.
    const total = student.marks.reduce((sum, m) => sum + m, 0);
    const maxTotal = student.marks.length * student.maxMarksPerSubject;
    const percent = (total / maxTotal) * 100;
 
    let grade = "C";
    if (percent >= 90) grade = "A+";
    else if (percent >= 75) grade = "A";
    else if (percent >= 60) grade = "B";
 
    // Even the attendance rule reads Student's data!
    if (student.attendancePercent < 75) grade = grade + " (attendance short)";
 
    return `${student.name}: ${percent.toFixed(1)}% — Grade ${grade}`;
  }
}

printGradeLine ধীরে পড়ো। কতবার student ছুঁয়েছে? নাম, marks (দুইবার), max marks, attendance — ছয়বার ছোঁয়াReportCardPrinter-এর নিজের field কতবার ছুঁয়েছে? শূন্যবার। ছোঁয়ার মতো নিজের কোনো field নেই!

একটা কথোপকথন হিসেবে দেখো এই method-কে। এটা দুটো class-এর কথা বলার মতো না, বরং একটা জিজ্ঞাসাবাদের মতো:

চিত্র ৬: Printer student-কে জিজ্ঞাসাবাদ করছে — raw data-র জন্য পাঁচটা প্রশ্ন, তারপর বাইরে হিসাব

এই method রুবেল। এটা printer class-এ বসে আছে কিন্তু সব কাজ student-এর জিনিস দিয়ে করছে। আর ক্ষতি ইতিমধ্যে শুরু হয়েছে:

  • Grading নিয়ম (৯০ হলো A+, ৭৫ হলো A...) student-দের সম্পর্কে নিয়ম, কিন্তু একটা printing class-এ থাকছে।
  • কাল যদি একটা PromotionChecker class-ও grade দরকার হয়, সে এই formula copy করবে। দুটো copy। তারপর ParentSmsService আবার copy করবে। তিনটা copy।
  • স্কুল নিয়ম বদলালে (ধরো A+ ৯৫ হয়), কেউ প্রতিটা copy খুঁজে ঠিক করবে।
  • Student-কে সব field চিরকাল public রাখতে হবে, কারণ অজানা বাইরের কেউ সেগুলো পড়ছে।

একই রোগ প্রতিটা ভাষায় দেখা যায়। Python-এ তিনটা ছোট লাইনে দেখো — একটা "helper" function যেটা একটা parcel-কে ভাঙছে:

# Smelly: a free function interrogating someone else's object
def delivery_charge(parcel):
    charge = parcel.weight_kg * parcel.rate_per_kg      # foreign touch 1, 2
    if parcel.distance_km > 500:                         # foreign touch 3
        charge *= 1.5
    if parcel.is_fragile:                                # foreign touch 4
        charge += 50
    return charge
 
# Healthy: the parcel computes its own charge
class Parcel:
    def delivery_charge(self):
        charge = self._weight_kg * self._rate_per_kg
        if self._distance_km > 500:
            charge *= 1.5
        if self._is_fragile:
            charge += 50
        return charge

চারটা বিদেশি ছোঁয়া, শূন্য নিজস্ব ছোঁয়া। Python-এ smell টা আরও সহজে লেখা যায় কারণ কিছুই একটা function-কে কোনো attribute পড়তে বাধা দেয় না। শৃঙ্খলাটা তোমার কাছ থেকেই আসতে হবে।

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

সমাধান প্রধান শিক্ষিকার নির্দেশ মানে: ছেলেটাকে যেখানে তার কাজ সেখানে transfer করো। Refactoring ভাষায় সেটা হলো Move Method

ধাপ ১ — হিংসুটে অংশ খুঁজে বের করো। কখনো পুরো method হিংসুটে, আমাদেরটার মতো। কখনো একটা লম্বা method-এর ভেতরে শুধু একটা block হিংসুটে। সেক্ষেত্রে আগে Extract Method দিয়ে হিংসুটে block-কে নিজস্ব ছোট method-এ বের করো। এখন একটা পরিষ্কার টুকরো পাওয়া গেছে move করার জন্য।

ধাপ ২ — হিসাব যেখানে data থাকে সেখানে নিয়ে যাও। Grade সম্পূর্ণভাবে Student-এর data থেকে হিসাব হয়। তাই method টা Student-এ থাকার কথা:

class Student {
  constructor(
    public name: string,
    private marks: number[],            // can be private now!
    private maxMarksPerSubject: number,
    private attendancePercent: number,
  ) {}
 
  percent(): number {
    const total = this.marks.reduce((sum, m) => sum + m, 0);
    const maxTotal = this.marks.length * this.maxMarksPerSubject;
    return (total / maxTotal) * 100;
  }
 
  grade(): string {
    const p = this.percent();
    let grade = "C";
    if (p >= 90) grade = "A+";
    else if (p >= 75) grade = "A";
    else if (p >= 60) grade = "B";
    if (this.attendancePercent < 75) grade += " (attendance short)";
    return grade;
  }
}

ধাপ ৩ — পুরনো class শুধু জিজ্ঞেস করুক। Printer এখন শুধু printing করে — তার আসল কাজ:

class ReportCardPrinter {
  printGradeLine(student: Student): string {
    return `${student.name}: ${student.percent().toFixed(1)}% — Grade ${student.grade()}`;
  }
}

ধাপ ৪ — হিংসা করা class-কে আরও শক্ত করো। সুন্দর পার্শ্ব-প্রতিক্রিয়া দেখো: marks, maxMarksPerSubject, আর attendancePercent private হয়ে গেছে। বাইরের কেউ আর এগুলো দরকার নেই। class-এর গোপন কথা আবার গোপন হলো। এটাই refactoring-এর আসল পুরস্কার — শুধু সুন্দর code না, বরং পুনরুদ্ধার করা encapsulation। আলমারিতে আবার তালা পড়ল।

ধাপ ৫ — আটকে পড়া field-ও চেক করো। কখনো সমস্যা উল্টো হয়: একটা field ভুল class-এ থাকে আর অন্য class-এর method বেশিরভাগ সময় সেটা ব্যবহার করে। তখন সমাধান Move Field — data আর method একসাথে থাকার জন্য field সরিয়ে নাও। আর যদি দেখো যে সরানো method আর field মিলে একটা নতুন ধারণা তৈরি করছে (ধরো একটা GradingPolicy), তাহলে তাদের Extract Class দিয়ে নিজস্ব class-এ নিয়ে যাও।

পুরো refactoring, code যে অবস্থাগুলো পার হয় সেগুলো এঁকে দেখি:

চিত্র ৭: সমাধান একটা state machine হিসেবে — হিংসুটে method থেকে তালা দেওয়া আলমারি

আর শেষ গঠনটা, class diagram হিসেবে। একটা public প্রশ্ন ভেতরে যায়; বিস্তারিত সব data access সেই class-এর ভেতরে থাকে যেটার কাছে data আছে:

চিত্র ৮: Move Method-এর পরে — printer একটা reference রাখে আর একটা প্রশ্ন করে; data private
চিত্র ৯: transfer-এর পরে তীরগুলো — শুধু একটা ভদ্র প্রশ্ন সীমানা পার হয়

চিত্র ৯ আর চিত্র ৪ তুলনা করো। আগে চারটা তীর সীমানা পার হচ্ছিল। এখন শুধু একটা তীর পার হচ্ছে, আর সেটা একটা প্রশ্ন বহন করছে — private data-তে হামলা না। বিস্তারিত সব তীর Student-এর ভেতরে থাকছে, যেখানে তারা নিরাপদ।

🧪 C#-এ একই smell

C#-এও smell একইরকম দেখায়। ধরো একটা delivery app যেখানে BillingService Parcel-কে হিংসা করছে:

// Smelly: BillingService computes everything from Parcel's data
public class BillingService
{
    public decimal DeliveryCharge(Parcel parcel)
    {
        decimal charge = parcel.WeightKg * parcel.RatePerKg;
        if (parcel.DistanceKm > 500) charge *= 1.5m;
        if (parcel.IsFragile) charge += 50m;
        return charge;
    }
}

parcel-এর তিনটা পড়া, this-এর শূন্য পড়া। কাজটা transfer করো:

public class Parcel
{
    private readonly decimal _weightKg;
    private readonly decimal _ratePerKg;
    private readonly int _distanceKm;
    private readonly bool _isFragile;
 
    public decimal DeliveryCharge()
    {
        decimal charge = _weightKg * _ratePerKg;
        if (_distanceKm > 500) charge *= 1.5m;
        if (_isFragile) charge += 50m;
        return charge;
    }
}
 
public class BillingService
{
    public decimal DeliveryCharge(Parcel parcel) => parcel.DeliveryCharge();
}

Field গুলো private readonly হয়ে গেল। Pricing নিয়মের ঠিক একটা ঘর। অন্য যেকোনো service যদি charge দরকার হয়, parcel-কে জিজ্ঞেস করে — কোনো copy নেই, কোনো হিংসা নেই।

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

Feature Envy বাস্তব codebase-এর সবচেয়ে সাধারণ smell-গুলোর একটা। যেকোনো project-এ খুঁজলে এই ছদ্মবেশে পাবে:

১. Anemic domain model। অনেক project-এ entity class আছে যেগুলো শুধু property-র থলে — একটা Data Class getter, setter আর আর কিছু নেই। তারপর "service" class সব logic ধরে: OrderService.calculateTotal(order), OrderService.canCancel(order)। এই service method গুলো প্রতিটাই জোর করে হিংসুটে, কারণ entity নিজে কিছুই করতে রাজি না। এখানে smell একটা method না — পুরো architecture। সমাধান হলো ধীরে ধীরে entity-তে behaviour নিয়ে যাওয়া।

২. Report আর export builder। ReportBuilder, PdfExporter, CsvWriter নামের class-গুলো প্রায়ই নিরীহভাবে শুরু হয়, তারপর ধীরে ধীরে business calculation শুষে নেয়। তারা cost হিসাব করতে Shipment থেকে weight, distance, আর zone বের করে যেটা Shipment নিজেই হিসাব করার কথা। formatting builder-এ রাখো; calculating data-র ঘরে নিয়ে যাও।

৩. Helper আর util class। OrderUtils.getDisplayName(order), DateHelper.financialYearOf(invoice)। একটা static helper যেটা একটা object নেয় আর তাকে জিজ্ঞাসাবাদ করে — সেটা সবচেয়ে বিশুদ্ধ আকারে envy। এর কাছে নিজের data-র ভান করারও উপায় নেই।

৪. অর্ধ-শেষ refactoring-এর পরে। কেউ class A থেকে class B-তে field সরিয়েছে কিন্তু সেগুলো ব্যবহার করা method সরাতে ভুলে গেছে। আটকে পড়া method গুলো এখন নতুন সীমানা পার হয়ে হাত বাড়াচ্ছে। এখানে envy অসমাপ্ত কাজের দিকে আঙুল তুলছে।

৫. Controller আর handler। Web controller যেগুলো একটা request DTO নেয় আর তার field-এ business rule চালায়, domain object-এ হস্তান্তর করার বদলে। Controller-এর কাজ অনুবাদ করা আর হস্তান্তর করা, হিসাব করা না।

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

ভালো ডাক্তার প্রতিটা দাগে operation করেন না। কিছু "envy" সুস্থ আর ইচ্ছাকৃত। এই ক্ষেত্রগুলো সৎভাবে বিবেচনা করো:

পরিস্থিতিEnvy না স্বাভাবিক?কেন
Strategy object একটা context-এর data থেকে হিসাব করছেsmell নাStrategy-র পুরো উদ্দেশ্যই হলো পরিবর্তনযোগ্য logic data-র বাইরে রাখা। এটা ফেরত নিলে pattern ধ্বংস হয়ে যাবে।
Visitor একটা structure walk করছে আর প্রতিটা node-এ হিসাব করছেsmell নাVisitor ঠিক এই কারণেই আছে যাতে node class edit না করে operation যোগ করা যায়।
Serializer বা UI formatter অনেক field পড়ছেসাধারণত ঠিকJSON বা screen-layout logic domain class-এ টেনে আনলে সেটা একটা file format বা UI-এর সাথে জুড়ে যাবে। সেই সমাধান রোগের চেয়ে খারাপ।
একটা method দুটো object-কে সমানভাবে হিংসা করেবিচারসাপেক্ষদুটোতেই নিয়ে যেতে পারবে না। যার data বেশি ব্যবহার করে তার সাথে রাখো, বা নতুন class বের করো যেটা নিয়মটা নিজে রাখবে।
একটা collaborator-এ এক বা দুটো getter callস্বাভাবিকobject-দের কথা বলতে হয়। Envy হলো এক-পক্ষীয় ভারী ব্যবহারের ধরন, একটা ভদ্র প্রশ্ন না।
একটা method একটা foreign object-এর ৫+ field পড়ে মান বের করতেআসল envy — ঠিক করোএটা textbook case। Move Method এখনই ফল দেবে।

কোনো method নিয়ে অনিশ্চিত হলে জায়গা নির্ধারণের একটা সহজ উপায়: এটা নিজের data কতটুকু ব্যবহার করে বনাম বিদেশি data কতটুকু ব্যবহার করে সেটা plot করো।

চিত্র ১০: Placement chart — নিচে-ডানের কোণের method গুলো transfer candidate

Chart টা পড়ো যেভাবে একজন শিক্ষক আসন পরিকল্পনা পড়েন। উপরে-বাম নিজের এলাকা — method নিজের class ভারীভাবে ব্যবহার করছে, ছেড়ে দাও। নিচে-ডান Move Method zone — ভারী বিদেশি ব্যবহার, প্রায় কোনো নিজস্ব ব্যবহার নেই। উপরে-ডান (উভয়দিকে ভারী) মানে সাধারণত method দুটো কাজ করছে আর আগে Extract Method দিয়ে ভাগ করা উচিত। নিচে-বাম প্রায় কিছু না ছোঁয়া একটা trivial method — ক্ষতিকর নয়।

কলেজ কর্নার: "দুটো object সমানভাবে হিংসা করে" সারিটা একটু আনুষ্ঠানিক নোট দাবি করে। যখন একটা হিসাবের জন্য A আর B দুটো class থেকে সমান data দরকার, কোনো Move Method দিক জেতে না। মানক উত্তর হলো একটা missing abstraction চেনা — একটা তৃতীয় class যার দায়িত্ব ঠিক এই সম্পর্ক (যেমন Book আর Member-এর মধ্যে FinePolicy, বা দুটো Currency object-এর মধ্যে ExchangeRate)। এটা Single Responsibility Principle দ্বারা পরিচালিত Extract Class: যে নিয়ম স্বাধীনভাবে পরিবর্তিত হয় সেটার নিজস্ব ঘর হয়। Domain-Driven Design ভাষায় এরকম class প্রায়ই domain service — stateless object যেটা কোনো একটা entity-র জন্য না এমন behaviour ধরে। এটা জানলে দুটো class-এর মধ্যে একটা method ping-pong খেলতে খেলতে শেষ হওয়া থেকে বাঁচবে।

ℹ️

অনিশ্চিত হলে একটা কার্যকর অভ্যাস: পরবর্তী সবচেয়ে সম্ভাবনাময় পরিবর্তন কল্পনা করো। হিংসা করা class তার field বদলালে কোন file গুলো ভেঙে পড়বে? যদি উত্তর "অন্য মানুষের মালিকানার দূরের file" হয়, envy বিপজ্জনক — ঠিক করো। যদি উত্তর "শুধু এই একটা কাছের file, আর দুটো এমনিতেও একসাথে পরিবর্তিত হয়", তাহলে এখনকার মতো ছেড়ে দিতে পারো।

💊 কোন refactoring সারায়

Refactoringকখন ব্যবহার করবেকী করে
Move Methodপুরো method হিংসুটেmethod-কে যে class-এর data সে বেশি ব্যবহার করে সেখানে নিয়ে যায় — প্রধান সমাধান
Extract Methodএকটা method-এর ভেতরে শুধু একটা block হিংসুটেআগে হিংসুটে block আলাদা করে, তারপর পরিষ্কারভাবে move করা যায়
Move Fieldএকটা field নিজের চেয়ে অন্য class বেশি ব্যবহার করেdata-কে সেখানে সরিয়ে নেয় যেখানে behaviour আছে
Extract Classহিংসুটে code একটা missing concept প্রকাশ করেনতুন class তৈরি করে যেটা data আর নিয়ম দুটোই রাখে

ব্যবহারিক ক্ষেত্রে ক্রমটা গুরুত্বপূর্ণ: আগে Extract Method, তারপর Move Method হলো প্রতিদিনের combo। Extract envy আলাদা করে; Move সেটাকে ঘরে পাঠায়।

📦 দ্রুত revision বাক্স

+================================================================+
|                  FEATURE ENVY — QUICK REVISION                  |
+================================================================+
| STORY    : Ravi sits in 7-B but does all his work in 7-A.       |
|            Transfer him to 7-A!                                 |
|                                                                 |
| SMELL    : A method uses ANOTHER class's data far more          |
|            than its own class's data.                           |
|                                                                 |
| SPOT IT  : Count the touches —                                  |
|            foreign getters: many | own fields: ~zero            |
|                                                                 |
| COSTS    : breaks encapsulation, scatters rules,                |
|            forces public getters, invites duplicate formulas    |
|                                                                 |
| CURE     : Extract Method (isolate the envious part)            |
|            -> Move Method (send it to the data's home)          |
|            -> make the envied fields private again              |
|                                                                 |
| IGNORE   : Strategy / Visitor (envy on purpose),                |
|            serializers & UI formatters, two-object ties         |
|                                                                 |
| RULE     : Data and the behaviour that uses it                  |
|            should live in the SAME class.                       |
+================================================================+

✏️ অনুশীলনের কাজ

প্রধান শিক্ষিকা হওয়ার পালা। ধরো এখানে একটা ছোট library system:

class Book {
  constructor(
    public title: string,
    public dailyFine: number,
    public dueDate: Date,
    public isReferenceOnly: boolean,
  ) {}
}
 
class LibraryDesk {
  fineFor(book: Book, returnedOn: Date): number {
    if (book.isReferenceOnly) return 0;
    const msLate = returnedOn.getTime() - book.dueDate.getTime();
    const daysLate = Math.max(0, Math.ceil(msLate / 86_400_000));
    let fine = daysLate * book.dailyFine;
    if (daysLate > 30) fine += 100; // long-overdue penalty
    return fine;
  }
 
  receiptLine(book: Book, returnedOn: Date): string {
    return `${book.title} — fine: Rs. ${this.fineFor(book, returnedOn)}`;
  }
}

তোমার কাজ:

  1. Touch গোনো। fineFor-এ book কতবার পড়া হয়েছে? LibraryDesk-এর নিজের field কতবার পড়া হয়েছে? দুটো সংখ্যা লেখো আর বলো এটা Feature Envy কিনা।
  2. Move Method প্রয়োগ করো। Fine calculation Book-এ fineIfReturnedOn(returnedOn: Date): number হিসেবে নিয়ে যাও। Book-এর কোন field গুলো এখন private হতে পারবে?
  3. বাকিটা চেক করো। Move-এর পরে receiptLine দেখো। এটাও হিংসুটে, নাকি এটা desk-এর সৎ কাজ করছে (formatting)? একটা বাক্যে ব্যাখ্যা করো।
  4. Chart-এ রাখো। চিত্র ১০-এর axis ব্যবহার করে fineFor (আগে), receiptLine, আর তোমার নতুন fineIfReturnedOn plot করো। প্রতিটা কোন quadrant-এ পড়ে?
  5. সামনে চিন্তা করো। একটা নতুন SmsReminderService সদস্যদের সম্ভাব্য fine সম্পর্কে সতর্ক করতে চায়। তোমার refactored code দিয়ে fine পেতে কত লাইন লাগবে? পুরনো code দিয়ে কত লাগত?
  6. Bonus। ধরো স্কুল library আর শহরের library আলাদাভাবে fine হিসাব করে। fineIfReturnedOn কি Book-এ রাখবে, নাকি Extract Class দিয়ে আলাদা FinePolicy class-এ নিয়ে যাবে? দ্বিতীয় পছন্দ কখন ভালো হয়?

যদি ৬ নম্বর প্রশ্নের উত্তর "যখন নিয়ম data-র স্বাধীনভাবে পরিবর্তিত হয়" দিতে পারো, তাহলে শুধু smell না, design-ই বুঝে ফেলেছ। শাবাশ!

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

Feature Envy code smell সহজ ভাষায় কী?
Feature Envy হলো যখন একটা class-এ থাকা method বারবার অন্য class-এর data ব্যবহার করে — তার getter বারবার call করে — কিন্তু নিজের class-এর data প্রায় ছোঁয় না। মেথডটা অন্য class-কে হিংসা করছে। এর মানে সাধারণত method টা ভুল class-এ আছে, এটাকে যেখানে data আছে সেখানে নিয়ে যাওয়া উচিত।
আমার code-এ Feature Envy কীভাবে ধরব?
ছোঁয়ার সংখ্যা গোনো। কোনো method যদি অন্য object-এর field বা getter তিন, চার, পাঁচবার পড়ে কিন্তু নিজের class-এর field শূন্য বা একবার ব্যবহার করে, তাহলে সেই method হিংসুটে। আরেকটা চিহ্ন: method টা parameter হিসেবে একটা object নিচ্ছে আর তারপর field by field সেটাকে ভেঙে একটা calculation করছে।
Feature Envy ঠিক করার refactoring কোনটা?
Move Method হলো প্রধান সমাধান — হিংসুটে method টাকে যে class-এর data সে ভালোবাসে সেখানে নিয়ে যাও। method-এর শুধু একটা অংশ হিংসুটে হলে আগে Extract Method দিয়ে সেই অংশ আলাদা করো, তারপর সেটা move করো। কোনো field যদি ভুল জায়গায় থাকে, তাহলে Move Field ব্যবহার করো।
Feature Envy কি সবসময় খারাপ?
না। Strategy আর Visitor-এর মতো pattern ইচ্ছাকৃতভাবে behaviour-কে data class-এর বাইরে রাখে — এটাই তাদের উদ্দেশ্য। এছাড়া printing, saving, আর serialisation code স্বাভাবিকভাবেই অন্য object-এর field পড়ে। সেই logic-কে domain object-এ নিয়ে গেলে সেটাকে UI বা database-এর সাথে জুড়ে দেওয়া হয়, যেটা আরও খারাপ।
Feature Envy আর anemic data class-এর সম্পর্ক কী?
Anemic Data Class হলো এমন একটা class যেখানে field আর getter আছে কিন্তু কোনো behaviour নেই। class নিজে কিছু না করায়, তার data নিয়ে সব calculation অন্য জায়গায় থাকতে বাধ্য হয়। সেই বাইরের method গুলো সবই হিংসুটে হয়ে যায়। envy সারানো মানে data class-কে আসল method দেওয়া, যাতে data আর behaviour একসাথে থাকে।

আরো দেখো

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

Inappropriate Intimacy: দুটো class যারা একে অপরের রান্নাঘরে ঢুকে পড়ে

দুই প্রতিবেশীর গল্প দিয়ে Inappropriate Intimacy বোঝো — যারা একে অপরের রান্নাঘর সাজিয়ে দেয়। দুটো class যখন একে অপরের private অংশে হাত দেয়, তখন কেউ একা কিছু বদলাতে পারে না। Law of Demeter আর privacy ফিরিয়ে আনার refactoring শেখো।

আরও পড়ুন

Message Chains: বন্ধুকে জিজ্ঞেস করো, সে কাজিনকে জিজ্ঞেস করে, কাজিন চাচাকে জিজ্ঞেস করে

Message Chains code smell শেখো একটা মজার গল্পের মাধ্যমে — রুটি আছে কিনা জানতে চারজন মানুষের মধ্য দিয়ে যেতে হয়। a.getB().getC().getD() লিখলে caller পুরো রাস্তার সাথে coupled হয়ে যায়। Law of Demeter কী, আর Hide Delegate দিয়ে কীভাবে chain ঠিক করতে হয় সেটা শেখো।

আরও পড়ুন

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

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

আরও পড়ুন

Move Method: কাজটা সেই class-এ নিয়ে যাও যেখানে সে আসলে থাকে

একটা স্কুলের গল্পের মাধ্যমে Move Method রিফ্যাক্টরিং শেখো। যে class-এর data method-টা সবচেয়ে বেশি ব্যবহার করে, সেখানেই সরিয়ে নাও — যাতে behaviour আর data একসাথে থাকে।

আরও পড়ুন