Introduce Foreign Method: নিজের ব্যাগে রাখা স্ট্যাপলার
স্কুলের ফটোকপি মেশিনে স্ট্যাপলার নেই — এই গল্প দিয়ে Introduce Foreign Method শেখো। যে class তুমি বদলাতে পারছ না সেখানে method নেই? সেই method নিজের class-এ লিখো, foreign object-কে parameter হিসেবে নাও। TypeScript আর C# extension method-এর উদাহরণসহ।
রহিম আর স্ট্যাপলারবিহীন ফটোকপি মেশিন
ধরো রহিম দশম শ্রেণিতে পড়ে। এই সপ্তাহে সে তার প্রজেক্ট গ্রুপের ফটোকপি মানুষ। স্কুলে একটাই মেশিন, অফিসের কাছে, করিম চাচার কড়া পাহারায়। মেশিনটা দারুণ — দ্রুত, পরিষ্কার, দুই পিঠে কপি হয়। কিন্তু একটা সমস্যা: স্ট্যাপলার নেই।
বারোটা পেজ কপি করে আলগা শিট হাতে নিয়ে করিডোরে আসতেই ঝামেলা। সেই বিখ্যাত করিডোরের বাতাস। গত মঙ্গলবার বিজ্ঞান প্রজেক্টের ৭ নম্বর পেজ উড়ে টয়লেটে ঢুকে গেছে। কেউ আনতে যায়নি।
রহিম কি মেশিন ঠিক করতে পারবে? না। এটা স্কুলের সম্পত্তি। বার্ষিক maintenance contract আছে। করিম চাচা কাউকে কাছে ঘেঁষতে দেবেন না। মেশিনটা সোজা কথায়: রহিমের বদলানোর না।
তাহলে রহিম কী করলো? ৭ নম্বর পেজের ঘটনার পর সে নিজের ব্যাগে একটা ছোট স্ট্যাপলার রাখতে শুরু করলো। ফটোকপি করো, শিট তোলো, ঠাক — স্ট্যাপল, গোছানো, নিরাপদ। স্ট্যাপলারটা মেশিনের অংশ না। এটা রহিমের সাথে চলে। মেশিন যে কাজটা করার কথা ছিল কিন্তু পারেনি — সেই একটাই কাজ এটা করে।
ব্যাগের স্ট্যাপলার নিয়ে তিনটা কথা মনে রাখো:
- এটা দরকার হয়েছে কারণ মেশিনে এমন একটা feature নেই যেটা বারবার লাগছে। একবারের দরকার হলে রহিম ক্লিপ ধার নিতো।
- এটা মেশিনের output নিয়ে কাজ করে — যে public জিনিসটা সে পায় — মেশিনের ভেতরে ঢোকে না।
- এটা একটা ছোট, একটাই কাজের সমাধান। রহিম নতুন মেশিন বানায়নি। পাঁচ টাকার স্ট্যাপলার কিনেছে।
Programming-এও একদম একই ব্যাপার হয়। তুমি প্রতিদিন এমন class ব্যবহার করছ যেগুলো edit করা যায় না — standard library-র Date, framework-এর HttpResponse, generated API client। মাঝে মাঝে এরকম একটা class-এ একটা ছোট method মিস থাকে যেটা বারবার দরকার। class-এ যোগ করতে পারছ না। তাই method-টা নিজের ব্যাগে রাখো — নিজের class-এ — আর foreign object টা সেখানে pass করো।
এই ব্যাগ-স্ট্যাপলার method-কে বলে foreign method। আর এটাকে ইচ্ছাকৃতভাবে, একটা নাম আর একটাই definition দিয়ে লেখার কাজটাই হলো Introduce Foreign Method refactoring।
Introduce Foreign Method কী?
Introduce Foreign Method এসেছে Martin Fowler-এর Refactoring বই থেকে। পরিস্থিতিটা এরকম: তুমি যে server class ব্যবহার করছ সেখানে একটা method দরকার, কিন্তু class টা বদলানো যাচ্ছে না। সমাধান: নিজের client class-এ একটা method বানাও, যেখানে foreign class-এর instance টা প্রথম parameter।
Fowler-এর উদাহরণ date নিয়ে। code-এর জায়গায় জায়গায় "পরদিন" হিসাব করা হচ্ছে — যে date object-এর মালিক team না:
// Scattered across the codebase, again and again:
const dueDate = new Date(
invoice.getFullYear(), invoice.getMonth(), invoice.getDate() + 1
);
// ...somewhere else, the same dance:
const reminderDate = new Date(
start.getFullYear(), start.getMonth(), start.getDate() + 1
);"পরদিন" ধারণাটা clearly date class-এ থাকার কথা। কিন্তু date class foreign। তাই method টা নিজের class-এ রাখি আর সৎভাবে label করি:
class InvoiceScheduler {
// FOREIGN METHOD: belongs on Date; parked here because we cannot modify Date.
private static nextDay(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
}
dueDate(invoiceDate: Date): Date {
return InvoiceScheduler.nextDay(invoiceDate);
}
reminderDate(start: Date): Date {
return InvoiceScheduler.nextDay(start);
}
}সাথে সাথে তিনটা লাভ। operation-টার একটা নাম হলো (nextDay — উদ্দেশ্য বোঝা যাচ্ছে, arithmetic না)। একটাই definition আছে (bug fix একটা জায়গায় হবে)। আর comment একটা ঋণ রেকর্ড করে রাখছে: এই code আসলে অন্য জায়গায় থাকার কথা ছিল।
সবসময় marker comment লিখো — FOREIGN METHOD: belongs on X। এটা দুটো কাজ করে: পাঠকদের বলে কেন একটা date calculation একটা invoice class-এ বসে আছে, আর একটা searchable চিহ্ন রাখে। একদিন FOREIGN METHOD খুঁজে দেখতে পারবে তোমার codebase কোন কোন library gap patch করে রেখেছে।
কলেজ কর্নার: Foreign object-কে কেন প্রথম parameter রাখতে হবে? এটা একটা convention — আর এর সুফল আছে। nextDay(date) পড়লে receiver প্রথমে আসে, ঠিক যেভাবে foreign class থেকে call হওয়া উচিত ছিল: date.nextDay()। তাই যেদিন class খুলে যাবে বা extension method-যুক্ত language-এ switch করবে, migration টা mechanical হবে। C# এই convention-কে syntax-এ নিয়ে গেছে: extension method মানে static method যার প্রথম parameter-এ this modifier, আর compiler date.NextDay() কে compile time-এ DateTimeExtensions.NextDay(date)-এ রূপান্তর করে। Kotlin extension function একইভাবে কাজ করে — receiver parameter zero হয়ে যায়। ১৯৯৯-এর refactoring আর আধুনিক language feature একটাই ধারণা, শুধু syntax আলাদা।
কখন এটা লাগবে?
Introduce Foreign Method তখন use করো যখন তিনটা শর্ত একসাথে মিলছে:
- একই ছোট operation বারবার দরকার — duplication দুই, তিন, পাঁচ জায়গায় দেখা যাচ্ছে। বারবার inline snippet মানে Duplicate Code smell ছদ্মবেশে।
- Operation টার স্বাভাবিক জায়গা এমন একটা class যেটা বদলানো যাচ্ছে না — standard library type, sealed framework class, generated code, বা third-party package।
- মাত্র এক বা দুটো এরকম operation দরকার। এই refactoring হলো পাঁচ টাকার স্ট্যাপলার, workshop না।
কখন এটা ভুল tool?
| পরিস্থিতি | ভালো tool |
|---|---|
| তুমি আসলে class-এর মালিক | Move Method — যেখানে থাকার কথা সেখানে রাখো |
| একই type-এর helper বাড়তে থাকছে | Introduce Local Extension |
| Operation-এর জন্য foreign object-এর private state দরকার | কোনো client-side fix নেই — library-তে request পাঠাও |
| Snippet মাত্র একবার দেখা গেছে | হয়তো কিছুই না — একবার inline use এখনো pattern না |
একটু ভাবো — এই refactoring coupling refactoring-গুলোর মতো একই "সীমানা মানা" পরিবারের। Message Chains আর Middle Man বলে কে কার সাথে কথা বলবে, Introduce Foreign Method বলে behavior কোথায় থাকবে যখন স্বাভাবিক জায়গাটা locked। সবগুলোই একটা প্রশ্নের উত্তর: কীভাবে জ্ঞান সঠিক জায়গায় রাখব?
আর duplication এত গুরুত্বপূর্ণ কেন? কারণ copies সরে যায়। দেখো codebase বড় হলে inline snippet-এর কী হয়:
আগে ও পরে এক নজরে
// ---------- BEFORE: nameless arithmetic, copy-pasted ----------
function billingReport(invoiceDate: Date): string {
const due = new Date(
invoiceDate.getFullYear(), invoiceDate.getMonth(), invoiceDate.getDate() + 1
);
return `Pay by ${due.toDateString()}`;
}
function courierLabel(dispatchDate: Date): string {
const arrives = new Date(
dispatchDate.getFullYear(), dispatchDate.getMonth(), dispatchDate.getDate() + 1
);
return `Arrives ${arrives.toDateString()}`;
}// ---------- AFTER: one named foreign method ----------
// FOREIGN METHOD: belongs on Date; we cannot modify Date.
function nextDay(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
}
function billingReport(invoiceDate: Date): string {
return `Pay by ${nextDay(invoiceDate).toDateString()}`;
}
function courierLabel(dispatchDate: Date): string {
return `Arrives ${nextDay(dispatchDate).toDateString()}`;
}Foreign class বদলায়নি — বদলাতে পারে না। যেটা বদলেছে সেটা boundary-র আমাদের দিক: মিসিং concept টা এখন ঠিক একটাই definition পেয়েছে।
ধাপে ধাপে, নিরাপদ পথে
ধাপ ১ — Duplication ধরো। প্রতিটা জায়গা খোঁজো যেখানে operation টা inline-এ হচ্ছে। distinctive অংশ খোঁজো — এখানে getDate() + 1। call site-গুলো list করো।
ধাপ ২ — প্রথম ব্যবহারকারীর পাশে foreign method লিখো। যে client class-এ এটা সবচেয়ে বেশি দরকার, সেখানে একটা method যোগ করো। প্রথম parameter হবে foreign object। snippet-এর logic parameter-এর মাধ্যমে express করো:
class BillingService {
// FOREIGN METHOD: belongs on Date.
private static nextDay(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);
}
// ...nothing else changed yet
}Compile করো আর test করো। Green — method আছে কিন্তু কেউ এখনো call করছে না।
ধাপ ৩ — একটা call site replace করো। প্রথম inline snippet টা nextDay(...)-এর call দিয়ে বদলাও। test চালাও। behavior একই হতে হবে — এটা pure rename-and-centralize।
ধাপ ৪ — বাকি call site-গুলো একটা একটা করে replace করো। প্রতিটার পর test করো। কোনো snippet যদি একটু আলাদা হয় (ধরো + 2 এর বদলে + 1), থামো — সেটা আলাদা operation, আলাদা নাম দরকার। count কমাতে গিয়ে ভিন্ন logic একটা method-এ গুঁজে দেওয়া যাবে না।
ধাপ ৫ — Marker comment যোগ করো। "FOREIGN METHOD: belongs on Date।" ভবিষ্যতের পাঠকরা কৃতজ্ঞ থাকবে।
ধাপ ৬ — Count দেখো। একটা foreign method: ঠিক আছে। দুটো: ঠিক আছে। তৃতীয় আর চতুর্থটা যখন একই foreign type-এর জন্য দেখা দেয় — বিশেষত আলাদা client class-এ — Introduce Local Extension-এ upgrade-এর পরিকল্পনা করো।
এই জীবনচক্র — ছড়ানো copies থেকে নামযুক্ত স্ট্যাপলারে, তারপর যে মুহূর্তে স্ট্যাপলারগুলো ব্যাগে জায়গা নেয় না — এটাই দীর্ঘ codebase-এ প্রতিটা locked type-এর গল্প:
এই refactoring-এর সবচেয়ে common bug হলো copies-এর মধ্যে সূক্ষ্ম পার্থক্য। একটা call site মাস শেষে সঠিকভাবে roll over করে, আরেকটা করে না। একটা time zone handle করে, আরেকটা ignore করে। একত্রিত করার আগে প্রতিটা call site-এর CURRENT behavior-এর জন্য একটা দ্রুত test লিখো। দুটো copy সত্যিই আলাদা behave করলে সেগুলো একত্রিত করলে চুপে চুপে behavior বদলে যায় — এটা আর refactoring না, এটা চোখ বন্ধ করে editing।
একটা বড় বাস্তব উদাহরণ
ধরো তোমার team একটা third-party payments SDK ব্যবহার করছে। এটা PaymentReceipt object return করে — generated code, প্রতিটা SDK update-এ regenerate হয়, edit করা যাবেই না:
// Generated by the SDK. DO NOT EDIT.
class PaymentReceipt {
constructor(
public readonly amountPaise: number, // amount in paise!
public readonly status: "OK" | "PENDING" | "FAILED",
public readonly upiRef: string
) {}
}সবার টাকার পরিমাণ টাকায় দরকার, ঠিকমতো formatted। তাই সবাই নিজের মতো করে:
// In the orders module:
const label1 = `₹${(receipt.amountPaise / 100).toFixed(2)}`;
// In the refunds module — oops, integer division bug:
const label2 = `₹${Math.floor(receipt.amountPaise / 100)}`;
// In the email module — forgot the symbol:
const label3 = `Rs ${(receipt.amountPaise / 100).toFixed(2)}`;তিনটা copy, তিনটা সামান্য আলাদা behavior, একটা সত্যিকারের bug (refund email-এ ৪.৯৯ টাকার জায়গায় ৪ টাকা দেখাচ্ছে!)। locked type নিয়ে কাজ করা code-এ এরকম bug কোথা থেকে আসে? বেশিরভাগই এই ধরনের re-implementation থেকে:
shared payments client-এ একটা foreign method introduce করো:
class PaymentsGateway {
// FOREIGN METHOD: belongs on PaymentReceipt (generated SDK code).
static amountInRupees(receipt: PaymentReceipt): string {
return `₹${(receipt.amountPaise / 100).toFixed(2)}`;
}
// FOREIGN METHOD: belongs on PaymentReceipt.
static isSettled(receipt: PaymentReceipt): boolean {
return receipt.status === "OK";
}
}
// All three modules now:
const label = PaymentsGateway.amountInRupees(receipt);Bug একটা line-এ, একটা file-এ, তিনটা module-এর জন্য একসাথে fix। আর early-warning sign লক্ষ্য করো: ইতিমধ্যে PaymentReceipt-এর জন্য দুটো foreign method হয়ে গেছে। আরেকটা এলে — ধরো maskedUpiRef() — সৎ পদক্ষেপ হবে সেগুলো একটা proper local extension-এ জড়ো করা। ব্যাগ-স্ট্যাপলার পদ্ধতি দারুণ ঠিক কারণ আমরা সজাগ থাকি কখন এটা ছাড়িয়ে যাচ্ছি।
C#-এ একই refactoring
এখানেই গল্পটা মজাদার হয়ে ওঠে। C# foreign method ধারণাটা নিয়ে সরাসরি language-এ ঢুকিয়ে দিয়েছে: extension methods। একটা extension method হলো static method যার প্রথম parameter-এ this modifier — compiler তোমাকে এটাকে যেন foreign type-এরই method মনে করে call করতে দেয়।
Classic foreign method, C#-style:
public static class DateTimeExtensions
{
// The foreign method, native edition:
public static DateTime NextDay(this DateTime date) => date.AddDays(1);
public static bool IsWeekend(this DateTime date) =>
date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}
// Call sites read as if DateTime always had these:
var due = invoiceDate.NextDay();
if (due.IsWeekend()) due = due.NextDay().NextDay();Fowler-এর মূল ১৯৯৯-এর pattern-এর সাথে তুলনা করো — nextDay(previousEnd) plain static helper। C# version-এ mechanics একই, কিন্তু call site উল্টে যায়: NextDay(invoiceDate)-এর বদলে invoiceDate.NextDay()। method টা পড়তে মনে হয় type-এরই অংশ, যদিও type কখনো বদলায়নি। Kotlin extension function (fun LocalDate.nextDay() = plusDays(1)) আর Swift extension একই কাজ করে।
আর গল্প আরো এগিয়েছে: C# 14 (.NET 10-এ) extension members এসেছে — extension block যা শুধু method না, extension property এমনকি static extension member-ও দেয়:
public static class DateTimeExtensions
{
extension(DateTime date)
{
public DateTime NextDay => date.AddDays(1); // extension property!
public bool IsWeekend =>
date.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
}
}
// Now even property-style access works:
var due = invoiceDate.NextDay;দুটো সীমাবদ্ধতা Fowler-এর মতোই থেকে গেছে: extension member শুধু type-এর public surface দেখে, আর discoverability নির্ভর করে সঠিক namespace import-এ। Language syntax polish করেছে — design idea আর তার সীমানা pure Introduce Foreign Method-এরই।
কলেজ কর্নার: C#/Kotlin extension resolution নিয়ে একটা সূক্ষ্ম বিষয় — final year students-ও এখানে হোঁচট খায়। Extension method statically resolve হয়, compile time-এ, receiver-এর declared type আর imported namespace-এর উপর ভিত্তি করে। এরা virtual dispatch-এ অংশ নেয় না। একই signature-এর real instance method থাকলে সেটাই জেতে। আর base type-এর extension derived runtime type-এর জন্য specialize হয় না। এটাই foreign method-এর সীমাবদ্ধতার reflection: বাইরে থেকে লাগানো, তাই override করতে পারে না, protected state দেখতে পারে না, polymorphic হতে পারে না। এটা জানলে বুঝবে কখন এই technique কাজ শেষ করে — আর কখন wrapper দরকার।
Python-এ একবার
Python-এর culture foreign method-এর জন্য বেশ friendly। plain module-level function, foreign object প্রথমে — এটাই idiomatic, searchable, আর testable।
# FOREIGN METHOD: belongs on datetime.date; we cannot modify the stdlib.
def next_day(d: date) -> date:
return d + timedelta(days=1)
# Call sites:
due = next_day(invoice_date)
reminder = next_day(due)Python runtime-এ অনেক class-এ method monkey-patch করা যায় — কিন্তু foreign method-এর জন্য ওটা করো না। Patched method পাঠকদের কাছে অদৃশ্য। প্রতিটা import order অবাক হয়। আর datetime.date-এর মতো built-in type-এ (C-implemented) আবার নতুন attribute নেওয়াও হয় না। Receiver first নিয়ে সরল module function-ই হলো Pythonic স্ট্যাপলার।
স্ট্যাপলার নাকি কেবিন? Dial পড়ো
এই refactoring আর তার বড় ভাইয়ের মধ্যে সিদ্ধান্তটা count আর cohesion দেখে। নিজের পরিস্থিতি plot করো:
Hide Delegate আর Remove Middle Man-এর মতো, এই refactoring-এরও dial-এর অন্য পাশে একটা sibling আছে — বিপরীত না, escalation। Dial যে প্রশ্নের উত্তর দেয়: তুমি যে class edit করতে পারছ না তার জন্য কতটা machinery বানাবে?
- এক বা দুটো মিসিং operation → Introduce Foreign Method। ব্যাগে স্ট্যাপলার। সস্তা, দ্রুত, সৎ।
- মিসিং operation-এর বাড়তে থাকা পরিবার → Introduce Local Extension। একটা proper cabin বানাও — পুরো পরিবার ধরার জন্য একটা wrapper, subclass, বা extension class।
কম বানালে helper সব জায়গায় ছড়িয়ে যায়। বেশি বানালে একটা tiny method-এর জন্য wrapper class maintain করতে হয়। হালকা শুরু করো। count বলবে কখন upgrade করতে হবে।
সুবিধা আর ঝুঁকি
| সুবিধা | কেন গুরুত্বপূর্ণ |
|---|---|
| ছড়ানো copies-এর বদলে একটা definition | Bug fix আর rule change একটা জায়গায় হয় |
| Operation-এর একটা নাম হয় | nextDay(d) উদ্দেশ্য বলে; inline arithmetic কিছু বলে না |
| অত্যন্ত lightweight | নতুন type নেই, inheritance নেই, wrapper নেই — শুধু একটা method |
| Real design gap document হয় | Marker comment foreign class-এ কী মিস সেটা record রাখে |
| ঝুঁকি | কীভাবে সামলাবে |
|---|---|
| Method "ভুল" class-এ থাকে | ইচ্ছাকৃত, commented compromise হিসেবে মেনে নাও |
| Teammate খুঁজে না পেয়ে আবার duplicate করতে পারে | Predictable shared spot-এ রাখো; C#/Kotlin-এ real extension method ব্যবহার করো |
| Helper বাড়তে থাকে আর ছড়িয়ে পড়ে | দুটো company, তিনটা crowd — Introduce Local Extension-এ graduate করো |
| শুধু public interface পাওয়া যায় | Private state দরকার হলে client-side কোনো fix নেই; library-কে request করো |
কোন smell-গুলো এটা সারায়?
| Smell | Introduce Foreign Method কীভাবে সাহায্য করে |
|---|---|
| Duplicate Code | Foreign type-এ বারবার inline snippet একটা definition-এ collapse করে |
| Magic incantations / unnamed concepts | আগে raw mechanics হিসেবে পড়া logic-কে একটা নাম দেয় |
| Shotgun Surgery (সেই operation-এর জন্য) | Rule change একটা method-এ হয়, প্রতিটা call site-এ না |
| এক type-এর জন্য ছড়ানো helper | আংশিক — helper বাড়তে থাকলে Introduce Local Extension দরকার |
IDE support
- Visual Studio / Rider (C#): duplicated snippet select করে Extract Method ব্যবহার করো; তারপর helper কে extension method-এ convert করো (Rider "Convert to extension method" context action offer করে)। IntelliSense তখন পুরো team-এর জন্য foreign type-এ method surface করে — discoverability সমস্যা চলে যায়।
- IntelliJ IDEA (Java/Kotlin): Kotlin-এ Extract Function তারপর "Convert parameter to receiver" — helper extension function হয়ে যায়। Java-তে Extract Method দিয়ে utility class-এ, তারপর Move Static Method।
- VS Code (TypeScript): Extract to function in module scope selected snippet থেকে helper বানায়; Find All References বাকি duplicate copy খুঁজে পেতে সাহায্য করে।
- সব IDE: structural search বা regex search তোমার duplication detector — distinctive fragment (যেমন
getDate() + 1) খুঁজো নিশ্চিত হতে যে সব copy ধরেছ।
দ্রুত revision box
+----------------------------------------------------------------+
| INTRODUCE FOREIGN METHOD — CHEAT SHEET |
+----------------------------------------------------------------+
| Situation : class you CANNOT modify is missing a method |
| Move : write the method in YOUR class |
| Signature : foreign object = FIRST parameter |
| Rule : use only the foreign type's PUBLIC interface |
| Mark it : comment "FOREIGN METHOD: belongs on X" |
| Cures : Duplicate Code around library types |
| Limit : 1-2 methods only |
| Outgrown? : 3+ helpers -> Introduce Local Extension |
| Modern form : C# extension methods / Kotlin extension funcs |
| Own the class? Skip all this — use Move Method instead |
+----------------------------------------------------------------+Practice exercise
ধরো তোমার project একটা third-party SMS gateway ব্যবহার করছে। তার response type generated আর locked:
// Generated by the SMS SDK. DO NOT EDIT.
class SmsResult {
constructor(
public readonly phone: string, // "+8801876543210"
public readonly creditsUsed: number, // 1 credit = ৳0.18
public readonly deliveredAt: Date | null
) {}
}আর এই code, সামান্য variations সহ, তিনটা module-এ দেখা যাচ্ছে:
// dashboard module:
const cost1 = `৳${(result.creditsUsed * 0.18).toFixed(2)}`;
const masked1 = result.phone.slice(0, 3) + "XXXXX" + result.phone.slice(-2);
// billing module:
const cost2 = `Tk. ${result.creditsUsed * 0.18}`; // formatting differs!
const delivered = result.deliveredAt !== null;
// audit module:
const masked2 = result.phone.substring(0, 3) + "*****" + result.phone.slice(-2);তোমার কাজ:
SmsResult-এ distinct মিসিং operation-গুলো identify করো। (Hint: তিনটা — টাকায় cost, masked phone, was-delivered।)- লক্ষ্য করো দুটো masking copy আর দুটো cost copy আলাদাভাবে behave করছে। কোনটা সঠিক decide করো, আর একত্রিত করার আগে প্রতিটার জন্য একটা দ্রুত test লিখো।
SmsReportsclient class-এ foreign method-গুলো লিখো। Foreign object প্রথম parameter, marker comment সহ। সব call site একটা একটা করে replace করো, মাঝে test করো।- এই codebase-এর জন্য চিত্র ৬ sketch করো:
SmsResultএখন কোন state-এ, আর তুমি কোন arrow অনুসরণ করতে যাচ্ছ? SmsResult-এর জন্য foreign method count করো। তিনটা আছে। Dial অনুযায়ী এখন কোন refactoring বিবেচনা করা উচিত — আর নতুন type-এর নাম কী হবে?
৫ নম্বরের উত্তর যদি "Introduce Local Extension, হয়তো SmsReceipt wrapper বা SmsResultExtensions class" হয় — পরের পেজে যাও, সেটাই পরের পোস্ট। রহিম, তার অংশে, শেষে ব্যাগে এক স্ট্যাপলার, এক punch, এক folder আর এক roll tape বহন করা বন্ধ করে দিয়েছে। সে একটা ছোট project kit box কিনেছে। সেই kit box-ই আমাদের গল্পের পরের গন্তব্য।
সচরাচর জিজ্ঞাসা
- Introduce Foreign Method রিফ্যাক্টরিং কী?
- ধরো তুমি একটা library বা framework class ব্যবহার করছ — সেটা বদলানো যায় না। কিন্তু সেই class-এ একটা method দরকার যেটা বারবার লাগছে। তখন সেই method নিজের client class-এ লিখো, আর foreign object-টাকে প্রথম parameter হিসেবে নাও। ক্লাসিক উদাহরণ: Date class-এর জন্য nextDay(date) helper — যে class-এর মালিক তুমি না। ছড়িয়ে থাকা snippet-টা এভাবে একটা নাম আর একটাই definition পায়।
- এটাকে 'foreign' method বলে কেন?
- কারণ method-টা আসলে একটা foreign class-এ থাকার কথা — যেটা তোমার নিয়ন্ত্রণের বাইরে। কিন্তু physically সেটা তোমার নিজের code-এ থাকে। ব্যাপারটা অনেকটা অতিথি কর্মীর মতো: foreign object-এর public interface ব্যবহার করে কাজ করে, কিন্তু তোমার client class-এ বাস করে। একটা comment দিয়ে চিহ্নিত রাখো যে এটা আসলে foreign class-এই থাকার কথা ছিল।
- কখন Introduce Local Extension ব্যবহার করব?
- Fowler-এর সহজ নিয়ম: এক বা দুটো method মিস হলে Introduce Foreign Method। তিনটার বেশি হলে Introduce Local Extension-এ যাও — ওটা সব helper-কে একটা dedicated wrapper বা extension class-এ জড়ো করে। একই type-এর জন্য অনেক ছড়ানো foreign method মানে তুমি এই হালকা fix টা ছাড়িয়ে গেছ।
- C# extension method আর foreign method কি একই জিনিস?
- একই ধারণার আধুনিক language-supported রূপ। C# extension method হলো একটা static method যার প্রথম parameter-এ 'this' modifier থাকে। তাই তুমি date.NextDay() লিখতে পারো, NextDay(date)-এর বদলে। Kotlin extension function আর Swift extension একই কাজ করে। mechanics আলাদা, কিন্তু উদ্দেশ্য এক — যে type edit করতে পারছ না সেখানে behavior যোগ করা।
- Foreign method-এর সীমাবদ্ধতা কী কী?
- এটা শুধু foreign object-এর public interface দেখতে পায় — private state না। এটা 'ভুল' class-এ থাকে, তাই অন্য developer সেটা খুঁজে না পেয়ে আবার duplicate করে ফেলতে পারে। আর তুমি যদি class-এর মালিক হও, foreign method একদম ভুল tool — সরাসরি সেই class-এ method যোগ করো, বা Move Method ব্যবহার করো।
আরো দেখো
সম্পর্কিত পাঠ
Introduce Local Extension: ভাড়ার দোকানের পাশে নিজের কেবিন বানাও
Introduce Local Extension রিফ্যাক্টরিং শেখো একটা গল্পের মাধ্যমে — যেখানে ভাড়া করা দোকানের পাশে নিজের কেবিন বানানো হয় যেটা পরিবর্তন করা যায় না। যখন একটা লক করা class-এ অনেক method নেই, সেগুলো একটা extension type-এ জড়ো করো — subclass, wrapper, বা আধুনিক C#/Kotlin extension class হিসেবে। TypeScript এবং C#-এ সম্পূর্ণ walkthrough।
Duplicate Code: ৫০টা বিয়ের কার্ডে হাতে লেখা একই ঠিকানা
বিয়ের কার্ডের গল্প দিয়ে Duplicate Code smell বোঝো। DRY, Rule of Three, আর Extract Method দিয়ে copy-paste কোডের বিপদ থেকে বাঁচো।
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।
Move Method: কাজটা সেই class-এ নিয়ে যাও যেখানে সে আসলে থাকে
একটা স্কুলের গল্পের মাধ্যমে Move Method রিফ্যাক্টরিং শেখো। যে class-এর data method-টা সবচেয়ে বেশি ব্যবহার করে, সেখানেই সরিয়ে নাও — যাতে behaviour আর data একসাথে থাকে।