Move Method: কাজটা সেই class-এ নিয়ে যাও যেখানে সে আসলে থাকে
একটা স্কুলের গল্পের মাধ্যমে Move Method রিফ্যাক্টরিং শেখো। যে class-এর data method-টা সবচেয়ে বেশি ব্যবহার করে, সেখানেই সরিয়ে নাও — যাতে behaviour আর data একসাথে থাকে।
🏫 যে ছেলে ৬ক-তে বসে কিন্তু ৬খ-তে বাস করে
ধরো তুমি Sunrise Public School-এর ৬ক ক্লাসের রাহিমকে চেনো।
স্কুলের রেজিস্টারে রাহিম ৬ক-তে আছে। তার নাম ৬ক-এর হাজিরা খাতায়, তার ব্যাগ ৬ক-এর হুকে, তার বেঞ্চ জানালা থেকে তৃতীয় সারিতে। কিন্তু তাকে একটা পুরো দিন চোখে রাখো — যেভাবে প্রধান শিক্ষিকা একদিন রেখেছিলেন — তাহলে মজার একটা ঘটনা দেখবে।
সকাল ৭:৪০-এর ঘণ্টা বাজতেই রাহিম নিজের ক্লাসরুম পেরিয়ে সরাসরি ৬খ ক্লাসে চলে যায়। তার বেস্ট ফ্রেন্ডরা — করিম, ফাতেমা, আর রুবেল — সবাই ৬খ-তে। তার ফুটবল টিম প্রতি মঙ্গলবার আর বৃহস্পতিবার ৬খ-এর সাথে প্র্যাকটিস করে। তার সায়েন্স প্রজেক্টের পার্টনার ফাতেমা ৬খ-তে, আর তাদের আগ্নেয়গিরির মডেল ৬খ-এর জানালার কিনারে বসানো। সে যে নাটকের দলে অভিনয় করে? সেটা ৬খ-এর ক্লাস টিচার চালান। এমনকি তার টিফিন গ্রুপও ওয়াটার কুলারের কাছে ৬খ-এর করিডোরে বসে। রাহিম শুধু ৬ক-তে আসে একটাই কারণে — সকাল ৮টায় হাজিরার সময় "উপস্থিত ম্যাডাম!" বলতে।
এই এক ছেলে চুপচাপ দুটো ক্লাসরুমের জন্য ঝামেলা তৈরি করে। ৬ক-এর স্যার গ্রুপ ওয়ার্ক প্ল্যান করেন, কিন্তু রাহিমের গ্রুপের বাকিরা অন্য রুমে — তার গ্রুপ সবসময় একজন কম। ৬খ-এর ম্যাডাম বারবার একটা অতিরিক্ত ছেলে পান, তার সিটিং চার্ট আর গ্রুপ লিস্ট বারবার ঠিক করতে হয়। রাহিমের জন্য নোটিশ আগে ৬ক-তে যায়, তারপর দেরিতে ৬খ-তে পৌঁছায় — একবার সে একটা ফুটবল ট্রায়াল মিস করেছিল কারণ নোটিশ পেয়েছিল দুই পিরিয়ড পরে। করিম মজা করে বলে রাহিমের দৈনিক সীমান্ত পারাপারের জন্য পাসপোর্ট লাগা উচিত।
এক সোমবার প্রধান শিক্ষিকা চায়ের কাপ হাতে করিডোরে দাঁড়িয়ে দেখলেন রাহিম দুপুরের আগেই চারবার দুই রুমের মধ্যে আসা-যাওয়া করেছে। তিনি একটাই সহজ প্রশ্ন করলেন: "রাহিমের জীবন আসলে কোথায় কাটে?" উত্তর পরিষ্কার — ৬খ-তে। তাই তিনি স্বাভাবিক কাজটা করলেন। একটা ট্রান্সফার সই করলেন। রাহিমের নাম ৬খ-এর রেজিস্টারে গেল, তার ব্যাগ ৬খ-এর হুকে গেল, তার বেঞ্চ সেই রুমে চলে গেল যেখানে তার বন্ধু, প্রজেক্ট আর ফুটবল টিম আগে থেকেই ছিল।
এক সপ্তাহের মধ্যে সব শান্ত। আর দুই রুমের মধ্যে দৌড়াদৌড়ি নেই। দেরিতে নোটিশ আসে না। দুই টিচারকে এক ছেলের জন্য সামঞ্জস্য করতে হয় না। এক ছেলে, এক ক্লাসরুম, সব কাজ আর বন্ধু এক জায়গায়।
কোডে আমরা প্রায়ই রাহিমের মতো একটা method পাই। এটা class A-এর ভেতরে লেখা, কিন্তু তার body-র প্রতিটা লাইন class B-এর সাথে কথা বলে। এটা B-এর field পড়ে, B-এর getter ডাকে, B-এর data দিয়ে সিদ্ধান্ত নেয়। রেজিস্টারে এক class, জীবন অন্য class-এ। সমাধান হলো প্রধান শিক্ষিকার সমাধান: ট্রান্সফার করো। সেই ট্রান্সফারের একটা নাম আছে — Move Method।
🤔 Move Method কী?
Move Method হলো একটা refactoring যেখানে আমরা এক class-এ বসবাসকারী একটা method-কে অন্য class-এ নিয়ে যাই — সেই class-এ যার data সে সবচেয়ে বেশি ব্যবহার করে। Martin Fowler-এর Refactoring (২য় সংস্করণ) বইয়ে এই একই idea Move Function নামে এসেছে, কারণ নতুন সংস্করণে JavaScript ব্যবহার করা হয়েছে যেখানে function class-এর বাইরেও থাকতে পারে। idea কিন্তু একটাই।
একটা নিঃশ্বাসে recipe:
- নতুন class-এ (target) method-টা তৈরি করো।
- body কপি করো, target-এর নিজস্ব field সরাসরি ব্যবহার করতে ঠিক করো।
- পুরনো method-টাকে একটা ছোট forwarder বানাও — শুধু নতুনটাকে ডাকবে। অথবা কেউ আর দরকার না হলে সম্পূর্ণ মুছে দাও।
প্রতিটা refactoring-এর সবচেয়ে গুরুত্বপূর্ণ নিয়ম মনে রেখো: আগে আর পরে program হুবহু একইভাবে কাজ করবে। Move Method কোনো feature যোগ করে না, কোনো bug ঠিক করে না, কিছু দ্রুত করে না। শুধু কোথায় একটা logic থাকবে সেটা বদলায় — যাতে code পড়া আর আগামীকাল পরিবর্তন করা সহজ হয়। Refactoring হলো রাহিমের ট্রান্সফারের মতো: স্কুলে একই সংখ্যক ছাত্র, একই সিলেবাস, একই পরীক্ষা। শুধু বসার ব্যবস্থাটা মানানসই হলো।
একটা method-এর ঘর এত গুরুত্বপূর্ণ কেন? কারণ object-oriented design-এর সোনালি নিয়ম: data আর সেই data ব্যবহার করা behaviour একসাথে থাকা উচিত। ঠিকানার নিয়ম যখন Address class-এ থাকে, যে কেউ Address খুললে দেখতে পায় একটা ঠিকানায় কী কী হতে পারে। সেই নিয়ম যখন দূরের কোনো DeliveryPlanner ফাইলে থাকে, এটা একটা অদৃশ্য বোমা হয়ে যায় — Address বদলাও আর দূরের কিছু চুপচাপ ভেঙে পড়ে।
একটা method-এর আসল ঘর খুঁজে পাওয়ার দ্রুত পরীক্ষা: body পড়ো আর প্রতিটা field access-এ মানসিকভাবে দাগ দাও। বেশিরভাগ দাগ যদি অন্য object-এর দিকে যায় — student.marks, student.attendance, student.grade — তাহলে সেই অন্য class-ই method-এর আসল ঘর। Fowler-এর সহজ নিয়ম: একটা method সেই class-এ থাকা উচিত যার data সে সবচেয়ে বেশি ব্যবহার করে।
পরীক্ষা আর interview-র জন্য আরেকটা নামের কথা জেনে রাখো: Move Method Moving Features Between Objects নামের refactoring পরিবারের সদস্য। এর সবচেয়ে কাছের ভাই-বোন হলো Move Field (একই idea-র data version), Extract Class (যখন অনেক সদস্য একটা নতুন ঘরে যেতে চায়), আর Inline Class (যখন একটা পুরো class অন্যটায় মিশে যাওয়া উচিত)।
কলেজের জন্য একটু বেশি: software metrics ভাষায়, রাহিমের মতো একটা method coupling বাড়ায় আর cohesion কমায়। Coupling Between Objects (CBO) গোনে একটা class কতগুলো অন্য class-এর উপর নির্ভর করে — envious method যতটা বিদেশি getter ডাকে প্রতিটা একটা edge যোগ করে। Lack of Cohesion of Methods (LCOM) মাপে একটা class-এর method তার নিজের field কতটুকু শেয়ার করে — নিজের কোনো field না ছোঁয়া method LCOM বাড়ায়। Move Method সেই বিরল edit যা একসাথে দুটো নম্বরই উন্নত করে: source class একটা dependency edge হারায়, আর target class এমন একটা method পায় যে সত্যিই তার field ব্যবহার করে। যখন software engineering পেপার জিজ্ঞেস করে "এমন একটা refactoring বলো যা একসাথে coupling কমায় আর cohesion বাড়ায়" — এটাই textbook উত্তর।
📋 কখন এটা দরকার হয়?
এই smell-গুলোর একটা পেলে Move Method করো:
১. Feature Envy — প্রধান কারণ। class A-এর একটা method বারবার class B-এর getter ডাকছে, কিন্তু নিজের class-এ প্রায় কিছুই করছে না। method-টা B-এর data-র প্রতি "envious"। এটাই Feature Envy smell, আর Move Method হলো এর এক নম্বর চিকিৎসা। method-এর শুধু একটা অংশ envious হলে, আগে Extract Method দিয়ে সেই অংশটা কেটে বের করো, তারপর সরাও।
২. দুটো class যেগুলো একসাথে বদলায়। ঠিকানার নিয়ম যতবার বদলায়, ততবার Address আর DeliveryPlanner দুটোই edit করতে হয়। marks-এর formula যতবার বদলায়, Student আর ReportPrinter দুটোই edit করতে হয়। একটা পরিবর্তন সবসময় অনেক ফাইল ছুঁলে এটা Shotgun Surgery। Move Method দিয়ে ছড়িয়ে থাকা method-গুলো এক class-এ জড়ো করলে পাঁচটা edit একটায় পরিণত হয়।
৩. একটা class যেটা খুব মোটা অথবা খুব পাতলা। কখনো একটা Large Class-এ এমন method জমে যায় যেগুলো আসলে অন্য জায়গার — সেগুলো সরিয়ে দেওয়াই হলো slimming-এর প্রথম ধাপ। উল্টোটাও হয়: একটা Lazy Class বা data-only class field নিয়ে বসে থাকে কিন্তু কোনো behaviour নেই, কারণ তার সব behaviour অন্য class-এ লেখা হয়েছে। method-গুলো ঘরে ফিরিয়ে আনলে data class একটা আসল কাজ পায়।
৪. বড় কোনো refactoring-এর আগে। Move Method খুব কম সময় একা কাজ করে। এটা হলো মূল "ইট" যেটা Extract Class আর Inline Class ব্যবহার করে। এই ইটটা ভালো করে শিখলে বড় refactoring সহজ হয়ে যায়।
একটু ভাবো, gradeOf method-এর জন্য কোন class-এর data কতবার ছুঁচ্ছে:
| method যে data ছোঁয় | কার কাছে আছে | কতবার |
|---|---|---|
student.marksObtained | Student | ১ |
student.totalMarks | Student | ১ |
percent (local variable) | method-এর নিজের | ৩ |
ReportPrinter-এর field (নিজের class) | ReportPrinter | ০ |
নিজের class-এ শূন্য স্পর্শ, অন্যের class-এ দুইটা। রায় নিজেই লিখে যায়। সেই রায়টা একটা চার্টে:
কখন method সরাবে না, এমনকি envious মনে হলেও?
- Printing, saving, আর serialising code স্বাভাবিকভাবেই অন্য object-এর field পড়ে।
toHtml()বাsaveToDb()domain class-এ সরালে domain-কে UI বা database-এর সাথে আটকে দেওয়া হবে — envy-এর চেয়ে খারাপ সমস্যা। - কিছু design pattern ইচ্ছা করেই behaviour আর data আলাদা রাখে। Strategy আর Visitor algorithm-কে data class-এর বাইরে রাখে। এগুলো "ঠিক" করতে যেও না।
- method-টা subclass-এ override হলে সরালে polymorphism ভাঙতে পারে। আগে hierarchy চেক করো।
সিদ্ধান্তটা দুটো প্রশ্নের উপর নির্ভর করে: method কার data ব্যবহার করে, আর কতজন caller তার বর্তমান ঠিকানার উপর নির্ভর করে:
নিচের-ডানের method হলো শান্ত দিনের রাহিম: স্পষ্টতই ভুল রুমে, ট্রান্সফার সহজ। উপরের-ডানের method হলো রাহিম যদি অর্ধেক স্কুল তাকে চিঠি পাঠাত — তবুও সরানো উচিত, কিন্তু অনেক post redirect করতে হবে, তাই একটু সময় forwarder রাখো। বামদিকের method-গুলো আগে থেকেই তাদের data-র সাথে আছে — শান্তিতে থাকতে দাও।
কলেজের জন্য একটু বেশি: automated smell detector এই quadrant-কে নির্ভুল করে তোলে। Lanza-Marinescu-এর classic detection strategy Feature Envy flag করে যখন তিনটা metric একসাথে fire করে: ATFD (Access To Foreign Data — method অন্য class-এর অনেক attribute পড়ছে), LAA (Locality of Attribute Accesses — সে যে data ছোঁয় তার এক-তৃতীয়াংশেরও কম নিজের), আর FDP (Foreign Data Providers — বিদেশি data খুব কম class থেকে আসছে, আদর্শত একটা)। SonarQube, NDepend, আর JDeodorant এই নিয়মের variation implement করে — JDeodorant Move Method-এর target-ও suggest করে দেবে।
👀 আগে আর পরে এক নজরে
একটু ভাবো, একটা ReportPrinter ছাত্রের গ্রেড হিসাব করছে — কিন্তু প্রতিটা লাইন Student-এর data পড়ছে।
// BEFORE — the grade rule lives far away from the marks
class Student {
constructor(
public name: string,
public marksObtained: number,
public totalMarks: number,
) {}
}
class ReportPrinter {
gradeOf(student: Student): string {
const percent = (student.marksObtained / student.totalMarks) * 100;
if (percent >= 90) return "A";
if (percent >= 75) return "B";
if (percent >= 50) return "C";
return "D";
}
}gradeOf হলো রাহিম। রেজিস্টার বলে ReportPrinter, কিন্তু সারাটা জীবন কাটে Student-এর ভেতরে। এখন ট্রান্সফার করি:
// AFTER — the grade rule lives beside the marks it uses
class Student {
constructor(
public name: string,
private marksObtained: number,
private totalMarks: number,
) {}
grade(): string {
const percent = (this.marksObtained / this.totalMarks) * 100;
if (percent >= 90) return "A";
if (percent >= 75) return "B";
if (percent >= 50) return "C";
return "D";
}
}
class ReportPrinter {
gradeOf(student: Student): string {
return student.grade(); // simple delegation — or remove this entirely
}
}দেখো দুটো বোনাস পেলাম। প্রথমত, marksObtained আর totalMarks এখন private হতে পারে — বাইরে আর কারো সেগুলো দরকার নেই। বিনামূল্যে encapsulation উন্নত হলো। দ্বিতীয়ত, যেকোনো অন্য class এখন student.grade() জিজ্ঞেস করতে পারে — formula নতুন করে লিখতে হবে না।
আগে আর পরের class structure:
🪜 ধাপে ধাপে, নিরাপদ উপায়ে
কখনো একটা বড় লাফে method সরিও না। ছোট ছোট ধাপ follow করো, আর প্রতিটা ধাপের পরে program কাজ করছে কিনা দেখো। প্রধান শিক্ষিকা রাহিমের ব্যাগ জানালা দিয়ে ছুঁড়ে ৬খ-তে ফেলেননি — একটা register entry ছিল, একটা শান্ত settling-in সপ্তাহ ছিল, তারপরেই তার পুরনো হুক অন্য কাউকে দেওয়া হয়েছিল। এখানে সেই যত্নটাই একটা procedure হিসেবে, gradeOf উদাহরণ দিয়ে।
ধাপ ১ — method কী কী ব্যবহার করে দেখো। body-তে যে প্রতিটা field আর method আছে সেগুলো চিহ্নিত করো। target class ঠিক করো (এখানে: Student)। এছাড়া চেক করো: gradeOf কি ReportPrinter-এর কোনো superclass-এ declare করা বা কোনো subclass-এ override করা? হ্যাঁ হলে থামো আর ভাবো — সরালে polymorphism ভাঙতে পারে।
ধাপ ২ — target class-এ method কপি করো। এখনই কিছু মুছো না। Student-এ নতুন method তৈরি করো, আর parameter-এর বদলে this ব্যবহার করতে body ঠিক করো:
// INTERMEDIATE STATE — both copies exist; program still works
class Student {
// ...fields...
grade(): string {
const percent = (this.marksObtained / this.totalMarks) * 100;
if (percent >= 90) return "A";
if (percent >= 75) return "B";
if (percent >= 50) return "C";
return "D";
}
}
class ReportPrinter {
gradeOf(student: Student): string {
// old body still here for one more moment
const percent = (student.marksObtained / student.totalMarks) * 100;
if (percent >= 90) return "A";
if (percent >= 75) return "B";
if (percent >= 50) return "C";
return "D";
}
}Compile করো। Tests চালাও। সব সবুজ? ভালো।
ধাপ ৩ — পুরনো method-টাকে forwarder বানাও। পুরনো body-কে একটা single call দিয়ে replace করো:
class ReportPrinter {
gradeOf(student: Student): string {
return student.grade(); // delegation only
}
}আবার compile আর test করো। behaviour অভিন্ন, কিন্তু এখন real logic-এর মাত্র একটা copy আছে। এটা ট্রান্সফারের "settling in" সপ্তাহ: পুরনো caller-রা এখনো ৬ক-এর দরজায় কড়া নাড়ছে, আর দরজা ভদ্রভাবে করিডোরে নির্দেশ করছে।
ধাপ ৪ — forwarder-এর ভবিষ্যৎ ঠিক করো। ReportPrinter.gradeOf-এর যদি codebase জুড়ে অনেক caller থাকে, কিছুদিন forwarder রাখতে পারো। কিন্তু সঠিক শেষটা হলো প্রতিটা caller-কে সরাসরি student.grade() বলতে update করা, তারপর gradeOf মুছে দেওয়া। চিরকাল বেঁচে থাকা forwarder Middle Man smell হয়ে যায় — "রাহিম যদি আসে" ভেবে রাখা খালি ৬ক বেঞ্চ।
ধাপ ৫ — access শক্ত করো। যেকোনো getter বা public field যেটা শুধু পুরনো method-কে feed করার জন্য ছিল সেটা এখন private হতে পারে। এটাই Move Method-এর লুকানো পুরস্কার: target class তার দরজা বন্ধ করতে পারে।
ভুল ঘর থেকে শুরু করে settled হওয়া পর্যন্ত method-এর পুরো যাত্রা:
method-এর পুরনো class থেকেও data লাগলে কী হবে? তখন বিকল্প আছে: সেই data নতুন method-এ parameter হিসেবে পাঠাও, পুরো source object-টা parameter হিসেবে পাঠাও, অথবা সেই data-ও Move Field দিয়ে সরাও। plain value পাঠানো prefer করো — এতে নতুন method independent থাকে। দুটো class থেকেই অনেক কিছু লাগলে, এটা two-rooms সমস্যা: আগে Extract Method দিয়ে ভাগ করো, তারপর প্রতিটা টুকরো যেখানে মানায় পাঠাও।
প্রতিটা ছোট ধাপের পরে tests চালাও — copy করার পরে, delegate করার পরে, প্রতিটা caller update করার পরে, শেষ delete-এর পরে। Move Method mechanical মনে হয়, কিন্তু একটু পিছলালে — ভুল field ব্যবহার, একটা caller ভুলে যাওয়া — চুপচাপ behaviour বদলে যায়। method-এর চারপাশে tests না থাকলে, শুরুর আগে কয়েকটা সহজ লিখে নাও। Tests ছাড়া refactoring শুধু আশার উপর ভিত্তি করা editing।
🏏 একটা বড় বাস্তব উদাহরণ
ধরো একটা স্কুলে section object আছে, আর কেউ SectionA-এর ভেতরে রাহিমের "activity report" লিখেছে — কিন্তু এর প্রতিটা লাইন SectionB-এর জিনিস ব্যবহার করে।
// BEFORE
class SectionB {
constructor(
public footballTeam: string[],
public dramaGroup: string[],
public scienceProjects: Map<string, string>, // student -> project
) {}
}
class SectionA {
constructor(public students: string[]) {}
// Arjun's method: registered in 6A, but living in 6B
activityReportFor(name: string, other: SectionB): string {
const lines: string[] = [];
if (other.footballTeam.includes(name)) {
lines.push(`${name} plays football with 6B`);
}
if (other.dramaGroup.includes(name)) {
lines.push(`${name} acts in the 6B drama group`);
}
const project = other.scienceProjects.get(name);
if (project) {
lines.push(`${name} works on the 6B project: ${project}`);
}
return lines.join("\n");
}
}activityReportFor দেখো। এটা other.footballTeam, other.dramaGroup, other.scienceProjects ব্যবহার করে — SectionB-এ তিনটা স্পর্শ — আর নিজের class থেকে শুধু ঠিকানা ছাড়া কিছুই না। ATFD terms-এ: বিদেশি access তিনটা, নিজের access শূন্য, আর সব বিদেশি data এক provider থেকে। এটা স্পষ্ট target সহ pure Feature Envy। ট্রান্সফার অর্ডার দাও:
// AFTER
class SectionB {
constructor(
private footballTeam: string[],
private dramaGroup: string[],
private scienceProjects: Map<string, string>,
) {}
activityReportFor(name: string): string {
const lines: string[] = [];
if (this.footballTeam.includes(name)) {
lines.push(`${name} plays football with 6B`);
}
if (this.dramaGroup.includes(name)) {
lines.push(`${name} acts in the 6B drama group`);
}
const project = this.scienceProjects.get(name);
if (project) {
lines.push(`${name} works on the 6B project: ${project}`);
}
return lines.join("\n");
}
}
class SectionA {
constructor(public students: string[]) {}
// nothing about 6B activities lives here any more
}
// Any caller now writes:
// sectionB.activityReportFor("Arjun");কী উন্নত হলো দেখো:
SectionB-এর তিনটা collectionprivateহয়ে গেল। সরানোর আগে এটা সম্ভব ছিল না কারণSectionA-কে সেগুলো পড়তে হতো।- report logic তার পড়া data-র পাশে বসে। ৬খ-তে chess club যোগ হলে, শুধু
SectionBবদলাবে। SectionAতার আসল কাজে ছোট হয়ে গেল। ৬খ-এর ভেতরের shape বদলালে এটা আর recompile বা break হয় না।
আর সংখ্যাগুলো একমত। গোনো কোড কতবার এক class থেকে অন্য class-এর member ছোঁয় — রাহিম প্রতিদিন যতটা সীমান্ত পারাপার করত:
ষোলটা পারাপার তিনটায় নেমে আসে। আর যে তিনটা বাকি থাকে সেগুলো ভদ্র, single-method call একটা public দরজা দিয়ে — অন্য class-এর আলমারিতে হাতড়ানো নয়। "coupling কমানো" বাস্তবে গুনলে এইরকম দেখায়।
💻 C# আর Python-এ একই refactoring
প্রতিটা object-oriented language-এ আকৃতি একই। ধরো একটা courier company-র DeliveryPlanner delivery zone ঠিক করে — কিন্তু প্রতিটা লাইন Address-কে জিজ্ঞাসাবাদ করে।
// BEFORE
class Address
{
public string State { get; set; }
public string Pincode { get; set; }
public bool IsMetroCity { get; set; }
}
class DeliveryPlanner
{
public string ZoneFor(Address address)
{
if (address.IsMetroCity) return "Metro";
if (address.Pincode.StartsWith("1")) return "North";
if (address.State == "Kerala" || address.State == "Tamil Nadu")
return "South";
return "Standard";
}
}ZoneFor Address-এর পুরো ভেতরের আকৃতি জানে। আগামীকাল company pincode ভিন্নভাবে রাখলে, এই দূরের planner ভেঙে পড়বে। method-টা ঘরে নিয়ে যাও:
// AFTER
class Address
{
public string State { get; set; }
public string Pincode { get; set; }
public bool IsMetroCity { get; set; }
public string Zone()
{
if (IsMetroCity) return "Metro";
if (Pincode.StartsWith("1")) return "North";
if (State == "Kerala" || State == "Tamil Nadu") return "South";
return "Standard";
}
}
class DeliveryPlanner
{
public string ZoneFor(Address address) => address.Zone();
}DeliveryPlanner যদি একমাত্র caller হয়, চূড়ান্ত polish হলো ZoneFor মুছে সবাইকে সরাসরি address.Zone() জিজ্ঞেস করতে দেওয়া।
Python ছাত্রদের জন্য, move একই — আর Python-এ object-এ অবাধে ঢোকার অভ্যাসে envy আরো সহজে চোখে পড়ে:
# BEFORE — the fine rule lives in the desk, but uses only book data
class Book:
def __init__(self, title, daily_fine, max_borrow_days):
self.title = title
self.daily_fine = daily_fine
self.max_borrow_days = max_borrow_days
class LibraryDesk:
def fine_for(self, book, days_kept):
late_days = days_kept - book.max_borrow_days
return max(0, late_days * book.daily_fine)
# AFTER — the rule moves home; the desk just asks
class Book:
def __init__(self, title, daily_fine, max_borrow_days):
self.title = title
self._daily_fine = daily_fine
self._max_borrow_days = max_borrow_days
def fine_for(self, days_kept):
late_days = days_kept - self._max_borrow_days
return max(0, late_days * self._daily_fine)তিনটা ভাষায় একই পাঠ: সরানোর পরে data private হতে পারল — private TypeScript আর C#-এ, underscore convention Python-এ। কারণ একমাত্র যে method-টা সেগুলো ব্যবহার করত সে এখন ভেতরেই আছে।
🧰 IDE সাপোর্ট
সুখবর: খালি হাতে এটা করতে হয় না। আধুনিক IDE Move Method automate করে আর সব caller update করে দেয়।
| টুল | সাপোর্ট | কীভাবে |
|---|---|---|
| IntelliJ IDEA / JetBrains family | পূর্ণ | method select করো, F6 চাপো (Refactor → Move), অথবা Ctrl+Alt+Shift+T দিয়ে "Refactor This" |
| JetBrains Rider (C#) | পূর্ণ | Dedicated Move Instance Method refactoring প্রতিটা usage rewrite করে |
| ReSharper in Visual Studio | পূর্ণ | Visual Studio-তে Move Instance Method / Move Static Method যোগ করে |
| Visual Studio (plain) | আংশিক | "Move type to file", pull-up/push-down — পূর্ণ instance move ম্যানুয়াল |
| VS Code (TypeScript) | আংশিক | "Move to a new file" আর extract করে; পাঁচটা নিরাপদ ধাপ হাতে ভালোভাবেই কাজ করে |
স্মার্ট IDE-তেও tests চালাতে থাকো। IDE guarantee করে code compile হবে; শুধু তোমার tests guarantee করে behaviour টিকে আছে।
⚖️ সুবিধা আর ঝুঁকি
| সুবিধা ✅ | ঝুঁকি / খরচ ⚠️ | |
|---|---|---|
| Readability | Behaviour তার data-র পাশে থাকে; এক class পড়লে সেই concept-এর পুরো গল্প জানা যায় | দীর্ঘ move chain mid-review-এ teammate-দের বিভ্রান্ত করতে পারে — ছোট commit-এ সরাও |
| Encapsulation | Target class field private করতে পারে আর feed-me getter মুছতে পারে | সরানো method-এর এখনো source data লাগলে, parameter যোগ করতে হতে পারে |
| Coupling | একটা dependency edge সরে; দুটো class একসাথে বদলানো বন্ধ হয় | ভুল target-এ সরালে শুধু envy উল্টো দিকে flip হয় |
| Reuse | অন্য client-রা formula duplicate না করে natural home-এ method ডাকতে পারে | চিরকাল রাখা delegating wrapper Middle Man smell হয়ে যায় |
| Design patterns | প্রায়ই Extract Class আর Inline Class-এর প্রথম ইট | Strategy/Visitor structure থেকে method সরালে ইচ্ছাকৃত design ধ্বংস হয় |
মনে রাখার একটা balance: Move Method একটা member সরায়। পাঁচটা method আর চারটা field একই জায়গায় সরাচ্ছ দেখলে থামো — তুমি আসলে Extract Class করছ (জায়গাটা নতুন হলে) অথবা Inline Class করছ (পুরো class খালি হয়ে যাচ্ছে)। বড় নামের refactoring ব্যবহার করো যাতে intent স্পষ্ট হয়। আর মনে রেখো: ক্লাস বেশি করলে extract, কম করলে inline — Move Method হলো সেই plank যার উপরে দুটোই দাঁড়িয়ে।
কলেজের জন্য একটু বেশি: move কাজে লেগেছে কিনা মাপার ভালো উপায় হলো দুটো class-এ আগে ও পরে CBO আর LCOM compute করা। সফল Move Method-এ source class-এর CBO কমপক্ষে একটা কমা উচিত, আর target-এর LCOM কমা উচিত কারণ আসা method সত্যিই target-এর field ব্যবহার করে। বরং target-এর CBO যদি বাড়ে — সরানো method তিনটা নতুন import নিয়ে এসেছে — তুমি সম্ভবত ভুল target বেছেছ, আর Figure 4-এর quadrant chart তোমাকে সতর্ক করত।
🧪 কোন smell-গুলো এটা সারায়?
| Smell | Move Method কীভাবে সাহায্য করে |
|---|---|
| Feature Envy | সরাসরি চিকিৎসা — envious method যে class-কে ভালোবাসে সেখানে চলে যায় |
| Inappropriate Intimacy | অন্য class-এর private-এ খোঁচা দেওয়া method সেখানে সরে যায়, দরজা বন্ধ হয় |
| Shotgun Surgery | এক নিয়মের ছড়িয়ে থাকা টুকরো এক class-এ জড়ো হয়; এক পরিবর্তন, এক ফাইল |
| Large Class | কখনো আসেনি এমন method সরানো slimming diet শুরু করে |
| Data Class | শুধু field-এর class তার প্রাপ্য behaviour পায় আর আসল object হয় |
📦 দ্রুত revision
+--------------------------------------------------------------+
| MOVE METHOD — CHEAT CARD |
+--------------------------------------------------------------+
| Story : Arjun sits in 6A but lives in 6B -> transfer him |
| Problem : method in class A uses class B's data all day |
| Smell : Feature Envy (main), Shotgun Surgery, Intimacy |
| Fix : create method in B -> copy body -> delegate from |
| A -> update callers -> delete old -> privatise |
| Rule : a method belongs where its DATA lives |
| Test : run tests after EVERY small step |
| Metrics : CBO down, LCOM down = the move worked |
| Don't : move UI/DB logic into domain; break Strategy / |
| Visitor; ignore overridden methods in subclasses |
| Friends : Move Field, Extract Method, Extract Class |
+--------------------------------------------------------------+✏️ অনুশীলনের প্রশ্ন
কোনো সমাধান দেখার আগে নিজে চেষ্টা করো।
class Book {
constructor(
public title: string,
public dailyFine: number,
public maxBorrowDays: number,
) {}
}
class LibraryDesk {
fineFor(book: Book, daysKept: number): number {
const lateDays = daysKept - book.maxBorrowDays;
if (lateDays <= 0) return 0;
return lateDays * book.dailyFine;
}
}তোমার কাজ:
fineForযে data ব্যবহার করে প্রতিটায় দাগ দাও। এটা কার data —LibraryDesk-এর নাকিBook-এর? এই method-এর জন্য Figure 3-এর pie-এর নিজস্ব version আঁকো।- পাঁচটা নিরাপদ ধাপ ব্যবহার করে method-টা তার আসল ঘরে সরাও।
fineFor-এর একটা intermediate delegating version রাখো আর প্রতিটা ধাপের পরে একটা ছোট test চালাও (যেমনdaysKept = 20,maxBorrowDays = 14,dailyFine = 2দিলে12হওয়া উচিত)। - সরানোর পর
dailyFineআরmaxBorrowDaysprivate করো। Program কি এখনো compile হয়? এটা তোমাকে কী বলে কে সেগুলো ব্যবহার করছিল? - Figure 4-এর quadrant chart-এ
fineForকোথায় পড়ে: কতটা ডানে (বিদেশি data ব্যবহার), আর কতটা উপরে (caller-এর সংখ্যা)? তার quadrant কি তোমার সিদ্ধান্তের সাথে মেলে? - বোনাস প্রশ্ন: ধরো
fineForতখনো ৫০% ছাড় দিত যখনLibraryDesk-এ একটাfestivalWeekflag set থাকত। এখন method-টা দুটো class-এর data ব্যবহার করে। তুমি আগে কোন refactoring করতে, আর ছাড়ের নিয়মটা কোথায় রাখতে পারত?
যদি task 5-এর উত্তর দুই বাক্যে মুখে বলতে পারো, তুমি সত্যিই Move Method বুঝেছ — শুধু ধাপ নয়, এর পেছনের বিচারও। আর তোমার উত্তরে যদি "আগে ভাগ করো, তারপর প্রতিটা টুকরো পাঠাও" থাকে — অভিনন্দন, তুমি ইতিমধ্যে করিডোরে চায়ের কাপ হাতে প্রধান শিক্ষিকার মতো চিন্তা করছ।
সচরাচর জিজ্ঞাসা
- Move Method রিফ্যাক্টরিং সহজ কথায় কী?
- Move Method মানে হলো একটা method-কে এক class থেকে তুলে অন্য class-এ রাখা — সেই class-এ যার data method-টা আসলে ব্যবহার করে। সরানোর পর পুরনো class হয় method-টা মুছে ফেলে, নয়তো একটা এক লাইনের forwarder রাখে। Program-এর behaviour একটুও বদলায় না — শুধু method-এর ঘর বদলায়।
- কীভাবে বুঝব কোন class-এ method-টা যাওয়া উচিত?
- দেখো method-টা কার data ছুঁচ্ছে। যদি class B-এর তিন-চারটা field পড়ে কিন্তু নিজের class A-এর প্রায় কিছুই না, তাহলে class B হলো এর আসল ঘর। Fowler-এর সহজ নিয়ম: একটা method সেই class-এ থাকা উচিত যার data সে সবচেয়ে বেশি ব্যবহার করে।
- Move Method কোন code smell ঠিক করে?
- এটা Feature Envy-এর এক নম্বর চিকিৎসা — যখন একটা method নিজের class-এর চেয়ে অন্য class-এর data-তে বেশি আগ্রহী। এটা Inappropriate Intimacy আর Shotgun Surgery-তেও সাহায্য করে, কারণ সংশ্লিষ্ট logic এক class-এ জড়ো হলে একটা পরিবর্তনে একটাই ফাইল ছুঁতে হয়।
- যদি method-টা দুটো class-এর data সমানভাবে ব্যবহার করে?
- তাহলে যেকোনো class-এ সরালে অন্যটার প্রতি envy তৈরি হবে। আগে Extract Method দিয়ে method-টাকে ভাগ করো, তারপর প্রতিটা অংশ সেই class-এ পাঠাও যেটাকে সে ভালোবাসে। কখনো কখনো উত্তর হলো একটা নতুন class বানানো যে পুরো calculation ধরবে — এটাই Extract Class।
- subclass-এ override করা method সরানো কি নিরাপদ?
- সাবধান থেকো। method-টা যদি কোনো inheritance contract-এর অংশ হয় — superclass-এ declare করা বা subclass-এ override করা — তাহলে সরালে polymorphism ভাঙতে পারে। আগে পুরো class hierarchy চেক করো। overriding-এ অংশ নিলে হয় সেখানেই রাখো, নয়তো আগে hierarchy নতুন করে ডিজাইন করো।
আরো দেখো
সম্পর্কিত পাঠ
Move Field: ডেটা রাখো যেখানে সে কাজে লাগে
Move Field শেখো একটা মজাদার স্কুলের গল্প দিয়ে। ডেটাকে সেই class-এ সরাও যেটা আসলে ওই ডেটা ব্যবহার করে, যাতে state আর behaviour একসাথে বাস করতে পারে।
Extract Class: অতিরিক্ত কাজে ডুবে যাওয়া class-কে একটু সাহায্য করো
Extract Class refactoring শেখো একটা মজার school office-এর গল্পের মাধ্যমে। একটা overloaded class-কে দুটো focused class-এ ভাগ করো — প্রতিটার একটাই কাজ।
Inline Class: যে Class কিছুই করে না, তাকে মিলিয়ে দাও
Inline Class refactoring শেখো একটা school committee-র গল্পের মাধ্যমে। যে class কিছুই করে না তাকে তার user-এর সাথে মিলিয়ে দাও আর অকারণ layer মুছে ফেলো।
Feature Envy: যে method সারাদিন অন্যের class-এ বসে থাকে
Feature Envy code smell শেখো একটা সহজ স্কুলের গল্পের মাধ্যমে। যখন একটা method নিজের class-এর চেয়ে অন্য class-এর data বেশি ব্যবহার করে, তখন সেটা আসলে ওই অন্য class-এই থাকার কথা। সারানোর উপায় হলো Move Method।