Inline Method: যে Shortcut আসলে Shortcut ছিলই না
Inline Method শেখো ধাপে ধাপে। যখন একটা ছোট method-এর body তার নামের চেয়ে বেশি পরিষ্কার, তখন সেই body-টা সরাসরি caller-এ বসিয়ে দাও আর একটা বাড়তি hop সরিয়ে ফেলো।
সেই "Shortcut" যেটা আসলে লম্বা রাস্তা ছিল
ধরো তারিক নতুন school-এ ভর্তি হয়েছে। প্রথম দিন সে রুবেলকে জিজ্ঞেস করলো, "ভাই, library কোথায়?" রুবেল হাসিমুখে বললো, "আরে shortcut আছে! B corridor দিয়ে যাও, science lab-এর পাশের সিঁড়ি দিয়ে উপরে ওঠো, পুরো first floor পার হও, তারপর staff room-এর কাছের সিঁড়ি দিয়ে নামো। Library পেয়ে যাবে।"
তারিক মনোযোগ দিয়ে রাস্তা ধরলো। সিঁড়ি দিয়ে উপরে, computer lab পার হয়ে, art room পার হয়ে, আবার সিঁড়ি দিয়ে নিচে। ছয় মিনিট লাগলো। Library-র দরজায় পৌঁছাতে পৌঁছাতে ঘাম ছুটে গেছে, ঠিক তখনই বেল বাজলো।
পরদিন librarian নাসরিন আপা তাকে হাঁপাতে হাঁপাতে আসতে দেখে জিজ্ঞেস করলেন কেন উপর থেকে আসলো। তারিক গর্বের সাথে রুবেলের shortcut বললো। নাসরিন আপা হেসে তাকে বাইরে নিয়ে ground floor-এর corridor দেখালেন। "এই রাস্তায় সোজা যাও। ত্রিশ সেকেন্ড। কে তোমাকে দুটো সিঁড়ি বেয়ে উঠতে বললো?"
রুবেলের "shortcut" আসলে shortcut ছিলই না। উপরে, পার হয়ে, নিচে — শেষে একই জায়গায়। আর তারিক ইতোমধ্যে দুজন junior-কে এই রাস্তা শিখিয়ে দিয়েছে। এখন তিনজন ছেলে প্রতিদিন কারণ ছাড়া সিঁড়ি বাইছে।
Code-এও ঠিক এই ঘটনা ঘটে। একটা method আরেকটা method call করে, সেটা আরেকটা call করে, সেটা আবার একটা call করে — শেষে এক লাইনের আসল কাজ। Code পড়তে গিয়ে তোমাকে "সিঁড়ি বেয়ে উঠে আবার নামতে হয়" — method থেকে method-এ লাফাতে হয় — শুধু একটা জিনিস জানার জন্য যেটা এক লাইনেই পড়া যেত। প্রতিটা বাড়তি hop বোঝার উপর একটা ছোট কর। আর তারিকের মতো, এই খারাপ রাস্তা ছড়িয়ে পড়ে — নতুন developer-রা pattern copy করে, আর সিঁড়ি বছর বছর লম্বা হতে থাকে।
Inline Method refactoring হলো নাসরিন আপার সেই corridor দেখিয়ে দেওয়া। এটা ঘুরপথ সরিয়ে দেয়। আর মনে রাখো এই golden rule: refactoring মানে code-এর ভেতরটা ভালো করা, বাইরে থেকে যা দেখায় তা একই রাখা। পাঠকের যাত্রা ছোট হয়, program-এর কাজ একটুও বদলায় না। Library সবসময় একই জায়গায় ছিল — শুধু রাস্তাটা ঠিক করা হলো।
Inline Method কী?
সহজ definition টা শোনো।
যখন একটা method-এর body তার নামের মতোই পরিষ্কার — বা আরো বেশি পরিষ্কার — তখন body-টা নিয়ে সেই method call করা প্রতিটা জায়গায় সরাসরি বসিয়ে দাও, তারপর method-টা delete করো। Indirection চলে যায়, আর logic সেখানেই দেখা যায় যেখানে ব্যবহার হচ্ছে।
Indirection (layer-এর মধ্য দিয়ে call করা) একটা হাতিয়ার, কোনো virtue না। প্রতিটা method একটা ছোট্ট প্রতিশ্রুতি: "আমার নাম তোমাকে কিছু দরকারি কথা বলে, তাই body পড়তে হবে না।" যখন সেই নাম প্রতিশ্রুতি রাখতে পারে না — যখন নাম পড়লে আর body পড়লে একই কথা জানা যায় — তখন সেই method ভাড়া নিচ্ছে কিন্তু কাজ করছে না। বের করে দাও।
এক লাইনে মনে রাখার trick: Inline Method = body যদি নামের চেয়ে ভালো বলে, তাহলে call-এর জায়গায় body বসাও আর method delete করো। এটা Extract Method-এর হুবহু উল্টো।
নামের ব্যাপারে একটু বলি। Martin Fowler-এর Refactoring 1st edition (1999)-এ এটা Inline Method নামে ছিল। 2nd edition (2018)-এ তিনি নাম দিলেন Inline Function — কারণ Extract Method যেমন Extract Function হয়েছে, একই কারণে: এই technique শুধু class method-এ না, standalone function-এও কাজ করে। refactoring.com-এ Inline Function নামে পাবে; Refactoring.Guru আর বেশিরভাগ IDE menu-তে এখনো Inline Method বলে। একই refactoring, দুটো নাম।
Fowler-এর কথাটা মনে রাখার মতো: তিনি Inline Function ব্যবহার করেন যখন function-এর body তার নামের মতোই পরিষ্কার, আর যখন functions-এর একটা group খারাপভাবে ভাগ করা — তখন সব inline করে এক জায়গায় আনেন, পুরো ছবি দেখেন, তারপর ঠিকমতো Extract Method দিয়ে আলাদা করেন। তাই এই refactoring একসাথে cleaner আর reset button দুটোই।
পুরো বিষয়টা এক map-এ দেখো। Post পড়ার পরে ফিরে এসে দেখো প্রতিটা branch বোঝাতে পারো কিনা:
কখন দরকার হয়?
এই situation গুলো দেখলে সাবধান হও। প্রতিটা একটা সিঁড়ি যেটা corridor ভান করছে।
- নাম হুবহু body-র কথা বলছে।
isWeekend()যদিday === "Sat" || day === "Sun"return করে — এটা ঠিক আছে, নামটা একটা ধারণা compress করছে। কিন্তুmoreThanFiveLateDeliveries()যদি শুধুlateDeliveries > 5return করে — এটা body-র কথা হুবহু বলছে। পাঠক তখনও body-তে যাবে "নিশ্চিত হতে", আর কিছুই পাবে না। Inline করো। - ছোট ছোট forwarding method-এর chain। Method A, B-কে call করে, B, C-কে call করে, C এক লাইন কাজ করে। এটা রুবেলের সিঁড়ির রাস্তা, code-এ লেখা। মাসের পর মাস code সরাতে সরাতে এটা হয়, পুরনো wrapper কখনো clean up হয় না। কেউ ইচ্ছে করে সিঁড়ি বানায়নি; এটা নিজে থেকে গজিয়েছে।
- Middle Man। একটা class বা method যার একমাত্র কাজ অন্য object-এ call forward করা। Forwarder inline করলে caller সরাসরি আসল worker-এর সাথে কথা বলতে পারে — এটাই Remove Middle Man-এর মূল কথা।
- Speculative Generality। কেউ wrapper method যোগ করেছিল "পরে দরকার হতে পারে" ভেবে। পরে আর আসেনি। এই wrapper গুলো শুধু সিঁড়ি। Inline করো। রুবেলও হয়তো সত্যিই ভেবেছিল তার রাস্তা ভালো — বেশিরভাগ অপ্রয়োজনীয় indirection ভালো উদ্দেশ্য নিয়েই যোগ হয়।
- Re-extract করার আগে। এটা clever। ধরো একটা function-কে helper-এ ভাগ করা হয়েছে, কিন্তু ভাগটা ভালো হয়নি — প্রতিটা helper দুটো কাজের অর্ধেক করছে। সহজ সমাধান: সব inline করে এক বড় function-এ আনো, পুরো ছবি শান্তভাবে দেখো, তারপর Extract Method দিয়ে ঠিকমতো কাটো। Inline Method জায়গাটা পরিষ্কার করে দেয় যাতে Extract Method ঠিকমতো rebuild করতে পারে।
তাই Inline Method ছোট method-এর বিরুদ্ধে না। এটা আসলে নাম-এর quality check। যে method-এর নাম অর্থ যোগ করে সে থাকে। যে method-এর নাম শুধু hop যোগ করে সে যায়।
যখন তুমি একটা class খোলো যেটা forwarding-এ অভ্যস্ত হয়ে গেছে, line count একটা দুঃখের গল্প বলে। খুব কম code আসল কাজ করছে; বেশিরভাগই corridor:
প্রায় তিন-চতুর্থাংশ class হলো যাত্রা, গন্তব্য না। Inline Method যাত্রা শূন্যে নামিয়ে দেয়, গন্তব্য দাঁড়িয়ে থাকে।
CS students-দের জন্য মজার ব্যাপার: Compiler-রা Inline Method সারাদিন নিজে নিজেই করে — তারা এটাকে inlining বা inline expansion বলে। JVM-এর HotSpot JIT ছোট hot methods inline করে (মোটামুটি ৩৫ bytecodes-এর কম trivial method প্রায় সবসময়ই inline হয়), .NET-এর RyuJIT-ও একই কাজ করে, আর C++ compiler-রা -O2-তে এত aggressive inlining করে যে আজকাল inline keyword মূলত performance command না, linkage hint। Inlining-কে "সব optimization-এর মা" বলা হয় কারণ এটা আরো অনেক optimization-এর রাস্তা খুলে দেয়। কিন্তু মূল কথা হলো: machine এই performance benefit এমনিতেই পায়, তুমি refactor করো বা না করো। যখন তুমি হাতে Inline Method করো, তুমি সেটা করছো সম্পূর্ণ আলাদা কারণে — মানুষের জন্য। Machine inlining call overhead সরায়; human inlining চিন্তার overhead সরায়।
আগে আর পরে — এক নজরে
ধরো একটা delivery company তাদের driver-দের rate করে। "before" code পড়ো আর দেখো তোমার চোখ কতটা লাফাচ্ছে।
// BEFORE: one useless hop
class Driver {
private lateDeliveries = 0;
rating(): number {
return this.moreThanFiveLateDeliveries() ? 2 : 1;
}
private moreThanFiveLateDeliveries(): boolean {
return this.lateDeliveries > 5;
}
}rating() বুঝতে গেলে call পড়ো, moreThanFiveLateDeliveries-এ যাও, এক লাইন পড়ো, ফিরে আসো — হুবহু সিঁড়ি-উপরে-সিঁড়ি-নিচে। Helper-এর নাম body-র চেয়ে কিছু বেশি বলে না। এই বাজে round trip-টা draw করলে দেখতে এমন:
পুরো message exchange হলো একটাই comparison পৌঁছে দিতে যেটা rating()-এ সরাসরি থাকতে পারতো। তাহলে সেটা সেখানেই রাখি:
// AFTER: the straight corridor
class Driver {
private lateDeliveries = 0;
rating(): number {
return this.lateDeliveries > 5 ? 2 : 1;
}
}এক method, এক line, শূন্য hop। একই কাজ, কম furniture। Class diagram-এ দেখো কী গেলো:
ধাপে ধাপে, নিরাপদে
একটা method ধীরে ধীরে আর নিরাপদে inline করা যাক, প্রতিটা ধাপে code দেখতে দেখতে। শুরুর point — একটা school portal যেখানে forwarding chain আছে।
class LibraryService {
canIssueBook(student: Student): boolean {
return this.hasValidCard(student);
}
private hasValidCard(student: Student): boolean {
return this.cardNotExpired(student);
}
private cardNotExpired(student: Student): boolean {
return student.cardExpiry > new Date();
}
}তিনটা method মিলে একটা comparison বলছে! এটা Corridor B, science lab-এর সিঁড়ি, আর staff room-এর সিঁড়ি — সব মিলিয়ে এক লাইনের library। প্রথমে cardNotExpired inline করি, তারপর hasValidCard।
ধাপ ১ — নিশ্চিত হও method polymorphic না। Check করো: কোনো subclass-এ cardNotExpired override করা আছে কিনা? যদি থাকে, থামো — inline করলে subclass-এর behavior মুছে যাবে, কারণ callers আর dynamically dispatch করবে না। এই method private আর কোথাও override নেই। নিরাপদ।
ধাপ ২ — সব caller খোঁজো। IDE-এর "Find Usages" ব্যবহার করো, চোখ দিয়ে খুঁজো না, যাতে কোনো caller miss না হয়। cardNotExpired শুধু এক জায়গা থেকে call হচ্ছে: hasValidCard।
ধাপ ৩ — প্রতিটা call-এর জায়গায় method-এর body বসাও। Call site-এ body বসাও, কোনো variable-এর নাম clash করলে rename করো (এখানে কিছু নেই)।
class LibraryService {
canIssueBook(student: Student): boolean {
return this.hasValidCard(student);
}
private hasValidCard(student: Student): boolean {
return student.cardExpiry > new Date(); // body pasted in
}
private cardNotExpired(student: Student): boolean { // now unused
return student.cardExpiry > new Date();
}
}ধাপ ৪ — Compile করো আর tests run করো। পুরনো method এখনো আছে, তাই কিছু হারানোর সুযোগ নেই। Tests pass? ভালো।
ধাপ ৫ — এখন unused method delete করো।
class LibraryService {
canIssueBook(student: Student): boolean {
return this.hasValidCard(student);
}
private hasValidCard(student: Student): boolean {
return student.cardExpiry > new Date();
}
}আবার test করো। সবুজ। একটা সিঁড়ি গেলো। এবার একই পাঁচটা ধাপ hasValidCard-এর জন্য। এর নামও আসলে expression-টার কথাই বলছে, আর caller একটাই। দ্বিতীয় round-এর পরে:
class LibraryService {
canIssueBook(student: Student): boolean {
return student.cardExpiry > new Date();
}
}তিনটা method থেকে একটা, behavior একটুও বদলায়নি। পাঠক এখন "কখন বই দেওয়া যাবে?" এক নজরে বুঝতে পারে — ত্রিশ সেকেন্ডের corridor, ছয় মিনিটের tour না।
প্রতিটা call site replace করার পরে test করো, বা বড়জোর একটা ছোট batch-এর পরে। দশটা call site replace করে তারপর test করলে failure কোনটায় সেটা বুঝতে পারবে না। আর method delete করো শুধু যখন সব caller replace হয়েছে আর tests সবুজ — compiler leftover caller খুঁজে পাওয়া তোমার বন্ধু, কিন্তু passing test suite-ই আসল বিচারক।
আরেকটা সাবধানতা: method-এ একাধিক return statement থাকলে, বা recursion থাকলে, বা body parameter reassign করলে, সহজে paste করা কাজ করবে না। Body আগে ঠিক করো নয়তো রেখে দাও — কিছু method inline করা সত্যিই ঝামেলার, আর সেটা ঠিকই আছে।
একটা inlining-এর পুরো জীবন, state machine হিসেবে:
প্রথম transition লক্ষ্য করো। Polymorphism check আসে সবার আগে। এটাই একমাত্র gate যেটা বলতে পারে পুরো plan বাদ দাও — তাই এটা আগেই পার হও। ট্রেন চলছে কিনা না দেখে লাইন পার হয় না, তাই না?
বাস্তব উদাহরণ: School Navigator App
ধরো তারিকের corridor-এর গল্পটা ঠিকমতো code করা হলো। School-এর app হাঁটার direction দেয়। একজন well-meaning senior developer প্রতিটা ছোট ধাপকে আলাদা method-এ রেখেছে, "flexibility-র জন্য।" নতুন পাঠক যা দেখে:
// BEFORE: the staircase route in code
class SchoolNavigator {
directionsToLibrary(from: string): string {
return this.libraryRoute(from);
}
private libraryRoute(from: string): string {
return this.computeLibraryPath(from);
}
private computeLibraryPath(from: string): string {
if (this.isMainGate(from)) {
return this.mainGatePath();
}
return this.defaultPath(from);
}
private isMainGate(from: string): boolean {
return from === "main-gate";
}
private mainGatePath(): string {
return "Walk straight down Corridor A. Library is at the end.";
}
private defaultPath(from: string): string {
return `From ${from}, reach Corridor A, then walk straight to the library.`;
}
}সাতটা method। একটা answer trace করতে পাঠককে লাফাতে হয়: directionsToLibrary → libraryRoute → computeLibraryPath → isMainGate → ফিরে → mainGatePath। পাঁচটা সৎ line পড়তে পাঁচটা hop। প্রতিটা method সম্পর্কে একটাই প্রশ্ন জিজ্ঞেস করো: নামটা কি body-র চেয়ে বেশি কিছু বলছে?
| Method | সিদ্ধান্ত | কারণ |
|---|---|---|
directionsToLibrary | রাখো | Public entry point; পরিষ্কার, দরকারি নাম |
libraryRoute | Inline করো | Pure forwarding — "সিঁড়ি বেয়ে উঠো" ধাপ |
computeLibraryPath | Inline করো | Forwarding plus ছোট if; caller-এর কথাই বলছে |
isMainGate | Inline করো | from === "main-gate" হুবহু বলছে |
mainGatePath | Inline করো | একটা string return করে; নাম কিছু যোগ করে না |
defaultPath | Inline করো | একটা string return করে; নাম কিছু যোগ করে না |
একটা একটা করে কাজ করি, ভেতর থেকে বাইরে, প্রতিটার পরে test করি। isMainGate inline করো:
private computeLibraryPath(from: string): string {
if (from === "main-gate") {
return this.mainGatePath();
}
return this.defaultPath(from);
}Tests সবুজ। mainGatePath আর defaultPath inline করো:
private computeLibraryPath(from: string): string {
if (from === "main-gate") {
return "Walk straight down Corridor A. Library is at the end.";
}
return `From ${from}, reach Corridor A, then walk straight to the library.`;
}Tests সবুজ। এখন libraryRoute আর computeLibraryPath একে অপরের pure pass-through। Chain collapse করে public method-এ আনো:
// AFTER: the straight corridor
class SchoolNavigator {
directionsToLibrary(from: string): string {
if (from === "main-gate") {
return "Walk straight down Corridor A. Library is at the end.";
}
return `From ${from}, reach Corridor A, then walk straight to the library.`;
}
}সাতটা method থেকে একটা। "Direction কীভাবে কাজ করে?" জিজ্ঞেস করা পাঠক এখন পাঁচটা সৎ line পড়ে, ছটা hop-এর মধ্যে spelunking করে না। App-এর behavior একটুও বদলায়নি — আগে যে test pass করতো এখনো করছে। পাঠকের জন্য পার্থক্যটা তারিকের মতোই:
আর এটাও মনে রাখো: এই method পরে যদি চল্লিশ line-এ বড় হয়, বাসের route আর বৃষ্টির দিনের route যোগ হয়, তাহলে Extract Method দিয়ে আবার ভাগ করবো — কিন্তু এমন ভাগে যেগুলো নাম deserve করে, যেমন rainyDayRoute()। আগে inline করো, পুরো ছবি দেখো, তারপর আরো ভালোভাবে আলাদা করো। এটাই professional refactoring-এর rhythm। নাসরিন আপা সিঁড়ি বন্ধ করেন না; শুধু সেই সিঁড়ি বন্ধ করেন যেটা যেখান থেকে শুরু সেখানেই ফিরে আসে।
আগে-পরের pattern একটা flowchart-এ:
C# আর Python-এ একই Refactoring
C#-এ হুবহু একই কাজ, সংক্ষেপে।
// BEFORE
public class Driver
{
private int _lateDeliveries;
public int Rating()
{
return MoreThanFiveLateDeliveries() ? 2 : 1;
}
private bool MoreThanFiveLateDeliveries()
{
return _lateDeliveries > 5;
}
}
// AFTER
public class Driver
{
private int _lateDeliveries;
public int Rating()
{
return _lateDeliveries > 5 ? 2 : 1;
}
}Check করো override নেই (এটা private, তাই হতেই পারে না), একমাত্র call-এ body বসাও, helper delete করো, tests run করো। শেষ।
Python developer-রাও একই situation-এ পড়ে, ছোট wrapper function-এ:
# BEFORE: a wrapper that restates its body
def is_parcel(order):
return order.type == "parcel"
def packing_charge(order):
return 10 if is_parcel(order) else 0
# AFTER: the comparison sits where it is used
def packing_charge(order):
return 10 if order.type == "parcel" else 0এখানে একটা জরুরি judgment call: is_parcel যদি দশটা module-এ ব্যবহার হতো, তাহলে inline করলে "parcel" string literal দশ জায়গায় copy হতো — ভবিষ্যতের bug factory। একটা caller হলে inline করাই ভালো। অনেক caller হলে wrapper হঠাৎ দরকারি হয়ে ওঠে — সেই rule-এর একমাত্র ঘর হিসেবে। Caller count উত্তর বদলে দেয়।
IDE Support
IDE Inline Method সুন্দরভাবে automate করে: সব caller খুঁজে দেয়, body ঠিকমতো বসায়, clashing variable rename করে, আর original delete করার offer করে — একটা action-এই।
| IDE | কীভাবে inline করবে | Shortcut |
|---|---|---|
| IntelliJ IDEA / Rider / অন্য JetBrains IDEs | Method-এর নামে cursor রাখো → Refactor → Inline Method | Ctrl+Alt+N (Mac-এ Cmd+Option+N) |
| ReSharper in Visual Studio | Method-এ cursor → Refactor This → Inline Method | Ctrl+Shift+R তারপর Inline বেছে নাও |
| Visual Studio (built-in) | Method-এ cursor → Quick Actions → "Inline method" | Ctrl+. তারপর Inline বেছে নাও |
| VS Code | Symbol-এ Refactor menu খোলো; inline support language extension-এর উপর নির্ভর করে | Ctrl+Shift+R (Refactor) বা Ctrl+. |
JetBrains tools এমনকি জিজ্ঞেস করে সব usage inline করে method remove করবে, নাকি শুধু cursor-এর নিচেরটা — যখন অন্য caller-দের জন্য method রাখতে চাও তখন কাজে লাগে। আর সবসময়ের মতো: tool typing করে, কিন্তু তোমার test suite-ই verdict দেয়।
Inline করবো নাকি রাখবো?
দুটো প্রশ্ন প্রায় সব case-এ সিদ্ধান্ত করে: নামটা কি body-র বাইরে কিছু অর্থ যোগ করছে? আর কতটা জায়গায় এটা call হচ্ছে? সন্দেহজনক method-কে touch করার আগে এই map-এ কোথায় পড়ে দেখো:
Bottom-left হলো Inline Method-এর এলাকা: নাম body-র কথাই বলছে, আর মাত্র এক caller-কে pasted code পাবে। Top-right হলো পবিত্র জায়গা: অর্থপূর্ণ নাম সব জায়গায় ব্যবহার হচ্ছে, যেমন isLeapYear — inline করা মানে vandalism। অন্য দুটো corner-এ judgment দরকার: অর্থপূর্ণ নাম একটা caller-সহ সাধারণত থাকে (নাম সস্তা, clarity মূল্যবান), আর echo-name অনেক caller-সহ থামিয়ে দেওয়া উচিত — হয়তো নাম-ই সমস্যা, আর Rename Method হবে ভালো সমাধান।
University students-দের জন্য: এখানে software engineering-এর একটা classic tension লুকিয়ে আছে, exam-এ আসতে পারে। Indirection হলো abstraction-এর ভিত্তি — "computer science-এর সব সমস্যা আরেকটা layer of indirection দিয়ে সমাধান করা যায়।" কিন্তু এই কথার কম পরিচিত দ্বিতীয় অংশ হলো: "...কিন্তু সমস্যাটা হলো অনেক বেশি layer of indirection।" প্রতিটা abstraction layer-এর একটা comprehension cost আছে, আর সেই layer-কে এই cost pay করতে হয় — অর্থ, reuse, বা flexibility দিয়ে। Code comprehension-এর study দেখায় পাঠকরা পড়তে পড়তে mental call graph বানায়; অপ্রয়োজনীয় প্রতিটা hop সেই graph বড় করে আর কোনো তথ্য না দিয়ে working memory চাপিয়ে দেয়। Inline Method হলো layers audit করে যেগুলো cost-benefit test-এ fail করে সেগুলো delete করার discipline। ভালো engineer সে না যে সবচেয়ে বেশি abstraction যোগ করে — সে যে শুধু সেই abstraction রাখে যেটা আসলেই ভাড়া দেয়।
সুবিধা আর ঝুঁকি
| বিষয় | |
|---|---|
| ✅ | একটা indirection-এর layer সরায় যেটা পাঠককে পার হতে হতো — logic যেখানে ব্যবহার হচ্ছে সেখানেই দেখা যায়। |
| ✅ | এমন নাম delete করে যেটা আর কাজের না, class-এর noise কমে। |
| ✅ | খারাপভাবে কাটা helper গুলো এক জায়গায় আনে যাতে আরো ভালো seam-এ re-extract করা যায়। |
| ✅ | Middle Man dismantling-এর standard হাতিয়ার যেটা শুধু call forward করে। |
| ⚠️ | Polymorphic method কখনো inline করবে না। Subclass যদি override করে, সেটাই আসল কাজ; inline করলে সেই behavior মুছে যায়। |
| ⚠️ | অনেক caller-সহ method inline করলে body সব জায়গায় copy হয় — duplicate code, DRY-এর উল্টো। Inline করো শুধু যখন caller কম, বা যখন সাথে সাথে re-extract করার plan আছে। |
| ⚠️ | সত্যিকার অর্থপূর্ণ নাম-সহ ছোট method হলো documentation। isLeapYear(y) থাকুক, body এক লাইন হলেও। শুধু ছোট বলে inline করবে না। |
| ⚠️ | Recursive method আর একাধিক return-সহ body mechanically inline করা ঝুঁকিপূর্ণ বা অস্বস্তিকর। |
Extract Method-এর সাথে seesaw। Inline Method আর Extract Method হলো হুবহু উল্টো — একটা abstraction boundary সরায়, অন্যটা যোগ করে। একটা seesaw ভাবো: অনেক বেশি ছোট hop-method হলে Inline দিকে চাপো; একটা বিশাল bloated method হলে Extract দিকে চাপো। লক্ষ্য কখনো "বেশি method" বা "কম method" না। লক্ষ্য হলো code-এর প্রতিটা নাম clarity-র জন্য নিজের খরচ তুলতে পারছে কিনা। তারিকের school-এ corridor আর সিঁড়ি দুটোই দরকার — শুধু দরকার নেই সেই সিঁড়ির যেটা উপরে উঠে একই তলায় নামে।
কোন Code Smell সারায়?
| Smell | Inline Method কীভাবে সাহায্য করে |
|---|---|
| Middle Man | Forwarding method inline করো যাতে caller সরাসরি আসল object-এ পৌঁছাতে পারে। |
| Speculative Generality | "পরে লাগতে পারে" ভেবে যোগ করা wrapper সরায় যখন পরে আর আসেনি। |
| অতিরিক্ত extraction (অনেক বেশি trivial helper) | Noise method গুলো ভাঁজ করে ফিরিয়ে আনে; প্রায়ই একটা smarter re-extraction আসে পরে। |
| Long Method (পরোক্ষভাবে) | আগে inline করলে পুরো ছবি দেখা যায়, তাই পরের Extract Method আরো ভালো জায়গায় কাটে। |
Quick Revision Box
+--------------------------------------------------------------+
| INLINE METHOD — QUICK REVISION |
+--------------------------------------------------------------+
| WHAT : Replace calls to a trivial method with its body, |
| then delete the method. |
| 2ND ED : Fowler now calls it "Inline Function". |
| WHEN : Name restates the body, chains of forwarders, |
| Middle Man, before a smarter re-extraction. |
| STEPS : 1. Confirm NOT overridden anywhere (polymorphism) |
| 2. Find ALL callers with the IDE |
| 3. Replace each call with the body |
| 4. TEST after each replacement |
| 5. Delete the unused method, test again |
| NEVER : Polymorphic methods, many-caller methods, |
| well-named methods hiding real complexity. |
| INVERSE: Extract Method (the other end of the seesaw) |
+--------------------------------------------------------------+Practice করো
তোমার পালা! এই CanteenService corridor shortcut-এ ভরা। ঠিক করো কোন method বেঁচে থাকার যোগ্য আর কোনটা inline হওয়া উচিত। লক্ষ্য হলো সবচেয়ে কম method-এ শেষ করা যেখানে প্রতিটা বাকি নাম আসল অর্থ যোগ করছে।
class CanteenService {
billFor(order: Order): number {
return this.computeBill(order);
}
private computeBill(order: Order): number {
return this.itemsTotal(order) + this.packingCharge(order);
}
private itemsTotal(order: Order): number {
return order.items.reduce((sum, i) => sum + i.price, 0);
}
private packingCharge(order: Order): number {
return this.isParcel(order) ? 10 : 0;
}
private isParcel(order: Order): boolean {
return order.type === "parcel";
}
}Hint: computeBill হলো pure forwarder — inline করো। isParcel একটা comparison এক caller-এর কাছে বলছে — inline করো। কিন্তু itemsTotal নিয়ে একটু ভাবো: এর নাম একটা reduce expression compress করছে যেটা সাথে সাথে obvious না, তাই অনেক developer এটা রাখবে — এটা quadrant map-এর ডান দিকে। কোনো একটা perfect answer নেই; তুমি আসলে practice করছো কোন নাম নিজের খরচ তুলছে সেটা judge করতে। একটা একটা method inline করো, প্রতিটার পরে test করো, আর জোরে বলো: "tests pass, behavior একই।" এটা করতে পারলে বুঝে গেছো যা তারিক দুটো সিঁড়ি আর একজন দয়ালু librarian-এর কাছ থেকে শিখেছিল: সবচেয়ে ছোট রাস্তা সেটাই যেখানে কোনো বাড়তি hop নেই।
সচরাচর জিজ্ঞাসা
- Inline Method refactoring সহজ কথায় কী?
- Inline Method হলো Extract Method-এর উল্টো। যখন একটা method-এর body তার নামের মতোই পরিষ্কার, তখন সেই method-এর প্রতিটা call-এর জায়গায় সরাসরি body বসিয়ে দাও, তারপর method-টা delete করো। একটা বাড়তি layer উধাও হয়ে যায়।
- কখন কোনো method inline করা উচিত না?
- যে method subclass-এ override করা হয়েছে সেটা কখনো inline করবে না, কারণ dynamic dispatch-ই সেখানে আসল কাজ। অনেক জায়গায় call হয় এমন method-ও inline করা ঠিক না, কারণ body সব জায়গায় copy হয়ে যাবে। আর যে ছোট method-এর নাম আসলেই কঠিন logic বোঝায়, সেটাও রেখে দাও।
- Fowler তার 2nd edition-এ Inline Method-এর নাম কী রেখেছেন?
- Refactoring-এর 2nd edition (2018)-এ Martin Fowler এটার নাম রেখেছেন Inline Function। Extract Method যেমন Extract Function হয়েছে, ঠিক একই কারণে — এই technique শুধু class method-এ না, standalone function-এও কাজ করে।
- কেন কেউ method সরিয়ে দেবে? ছোট method কি ভালো না?
- ছোট method তখনই ভালো যখন তার নাম কিছু অর্থ যোগ করে। moreThanFiveLateDeliveries() নামের একটা method যদি শুধু lateDeliveries > 5 লেখে, সে শুধু একটা hop যোগ করছে, বোঝাপড়া না। এই ধরনের noise সরালে আসল logic দেখতে সহজ হয়।
- Inline Method কি শুধু একটা preparation step?
- হ্যাঁ, অনেক সময়ই। যখন একটা function খারাপভাবে ছোট ছোট helper-এ ভাগ করা থাকে, তখন developer-রা প্রথমে সব কিছু একজায়গায় inline করে, পুরো ছবিটা দেখে, তারপর Extract Method দিয়ে আরো ভালোভাবে আলাদা করে।
আরো দেখো
সম্পর্কিত পাঠ
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।
Inline Temp: একবারই ব্যবহার করা রাফ নোটটা ছুঁড়ে ফেলো
Inline Temp রিফ্যাক্টরিং শেখো একটা মজার রাফ পেপারের গল্প দিয়ে — TypeScript আর C# উদাহরণ, নিরাপদ ধাপ, IDE shortcut, আর কখন variable inline করা উচিত না সেটাসহ।
Remove Middle Man: পিয়ন শুধু ফরওয়ার্ড করলে, সরাসরি হেড স্যারের কাছে যাও
Remove Middle Man রিফ্যাক্টরিং শেখো একটা স্কুলের পিয়নের গল্প দিয়ে — যে প্রতিটা প্রশ্ন হেডমাস্টারের কাছে ফরওয়ার্ড করে, নিজে কিছু যোগ না করেই। যখন একটা class শুধু delegate-কে call ফরওয়ার্ড করে, তখন সেই ফরওয়ার্ডিং মুছে দাও আর client-দের সরাসরি delegate-এর সাথে কথা বলতে দাও। TypeScript আর C#-এ ধাপে ধাপে walkthrough।
Inline Class: যে Class কিছুই করে না, তাকে মিলিয়ে দাও
Inline Class refactoring শেখো একটা school committee-র গল্পের মাধ্যমে। যে class কিছুই করে না তাকে তার user-এর সাথে মিলিয়ে দাও আর অকারণ layer মুছে ফেলো।