Hide Method: গোপন মশলা রান্নাঘরেই থাকে
Hide Method সহজ ভাষায় — যে method শুধু class নিজেই ব্যবহার করে সেটা public মেনুতে রাখা উচিত না কেন, আর visibility কমিয়ে private বা internal করলে API ছোট হয়, ভেতরের জিনিস সুরক্ষিত থাকে, আর ভয় ছাড়াই code বদলানো যায়।
রান্নাঘরের গোপন মশলা কখনো বাইরে যায় না
ধরো ঢাকার পুরান ঢাকায় একটা বিখ্যাত বিরিয়ানির দোকান — নবাবের রান্নাঘর। মানুষ বলে এর বিরিয়ানির স্বাদ অন্য কোথাও পাওয়া যায় না। কারণ হলো house masala: উস্তাদ করিম, প্রধান বাবুর্চি, প্রতিদিন ভোরবেলা রান্নাঘরের দরজা বন্ধ করে এই মশলা বানান। ওয়েটাররা আসার আগেই কাজ শেষ।
এখন ভাবো দোকানটা কীভাবে চলে। রহিম, একজন নিয়মিত খদ্দের, ঢুকে একটা মেনু পায়: বিরিয়ানি, কাবাব, ফিরনি। রহিম মেনু থেকে অর্ডার করে — এটাই চুক্তি। রহিম কখনো রান্নাঘরে ঢুকে উস্তাদ করিমের মশলার ডাব্বা খুলতে পারে না। মশলা পেষা, ভাজা, গোপন অনুপাত — এগুলো সব রান্নাঘরের ভেতরে থাকে। রহিম জানেও না এই ধাপগুলো আছে, আর এটাই মূল কথা: কাল উস্তাদ করিম একটু বেশিক্ষণ সাউঁফ ভাজতে পারেন, একটা মরিচ বদলাতে পারেন — রহিমের অর্ডার তবুও ঠিকঠাক আসবে। কারণ রহিম কখনো ধাপগুলোর উপর নির্ভর করেনি, শুধু খাবারের উপর করেছে।
একবার এক নতুন ম্যানেজারের একটা "চমৎকার" আইডিয়া হলো: মেনুর পেছনে মশলার ধাপগুলো ছাপিয়ে দাও, আগ্রহী খদ্দেররা নিজেই মশলা বানাক। তিন সপ্তাহ চলল। খদ্দেররা খারাপ মশলা বানিয়ে দোকানকে দোষ দিল। আরও বড় সমস্যা, উস্তাদ করিম যখন ধাপ ৪ একটু বদললেন — বিশজন পুরানো খদ্দের চেঁচিয়ে বলল "মেনুতে তো অন্যরকম লেখা!" রান্নাঘর এখন বাইরের লোকের অভ্যাসে আটকে গেল। মালিক চুপচাপ নতুন মেনু ছাপালেন শুধু খাবার নিয়ে, উস্তাদ করিম রান্নাঘর ফিরে পেলেন।
Code-এ, তোমার class-এর public method হলো মেনু। আর helper method — মশলা পেষা, ভাজার ধাপ — এগুলো রান্নাঘরের ভেতরে থাকা উচিত, private করে। যখন কোনো helper ভুলে মেনুতে চলে আসে, যে refactoring সেটাকে আবার ভেতরে নিয়ে যায় তার নাম Hide Method।
Hide Method কী?
Hide Method মানে হলো একটা method-এর visibility কমানো — সাধারণত public থেকে private করা (বা protected, বা C#-এর internal) — কারণ class-এর বাইরে আসলে কেউ সেটা call করে না।
আগে — মেনুতে রান্নাঘরের ধাপ ছাপানো:
class Bill {
public subtotal(): number { /* ... */ return 540; }
public taxFor(amount: number): number { // only used inside this class!
return amount * 0.05;
}
public total(): number {
return this.subtotal() + this.taxFor(this.subtotal());
}
}পরে — ধাপটা আবার রান্নাঘরে:
class Bill {
public subtotal(): number { /* ... */ return 540; }
private taxFor(amount: number): number { // hidden: an internal step
return amount * 0.05;
}
public total(): number {
return this.subtotal() + this.taxFor(this.subtotal());
}
}একটা keyword বদলাল। এটা কেন এত গুরুত্বপূর্ণ? কারণ একটা class-এর public interface হলো একটা প্রতিশ্রুতি। প্রতিটা public method পুরো codebase-কে বলে: "তুমি আমাকে call করতে পারো। আমি থাকব, আমার নাম থাকবে, আমার behaviour থাকবে।" এই প্রতিশ্রুতি ব্যয়বহুল। একবার বাইরের কেউ taxFor-এর উপর নির্ভর করলে, তুমি আর এটার নাম বদলাতে পারবে না, parameter বদলাতে পারবে না, মুছতে পারবে না — ঠিক যেমন উস্তাদ করিম মেনুতে ছাপানোর পর ধাপ ৪ বদলাতে পারেননি।
একটা private method কাউকে কোনো প্রতিশ্রুতি দেয় না। Refactoring Guru catalog যেমন বলে, private method বদলানোর সময় শুধু current class না ভাঙলেই হলো — কারণ তুমি জানো এটা অন্য কোথাও ব্যবহার হতে পারে না। ইচ্ছেমতো নাম বদলাও, দুভাগ করো, বা মুছে ফেলো — রান্নাঘরটা তোমার।
দ্বিতীয় একটা নিরব সুবিধাও আছে: discoverability। ত্রিশটা public method সহ একটা class হলো ত্রিশ পাতার মেনুর মতো — নতুন teammate-রা খাবার আর রান্নার ধাপ আলাদা করতে পারে না। helper-গুলো hide করো, আর যে মেনু বাকি থাকে সেটাই আসল চুক্তি: ছোট, পরিষ্কার, শেখার যোগ্য।
কলেজ কর্নার — information hiding: এই refactoring হলো software engineering-এর সবচেয়ে পুরানো, গভীর ধারণার দৈনন্দিন রূপ। ১৯৭২ সালে David Parnas বললেন যে module-গুলোকে processing-এর ধাপ অনুযায়ী নয়, বরং প্রতিটা module অন্যদের থেকে যে design decision লুকায় তার ভিত্তিতে ভাগ করা উচিত। একটা module-এর গোপন তথ্য হতে পারে data structure, algorithm, file format — আর module-এর public interface-এ দেখাবে কী করে, কীভাবে করে সেটা লুকিয়ে রেখে। "কীভাবে" লুকানো থাকলে সেটা বদলানো যায় কোনো ঢেউ ছাড়াই। প্রতিটা private keyword হলো Parnas-style design-এর একটা ছোট পদক্ষেপ — তুমি ঘোষণা করছো "এটা আমার module-এর গোপন তথ্য; আমার মেনুর উপর নির্ভর করো, রান্নাঘরের উপর নয়।" একই ধারণা বড় স্কেলেও কাজ করে: class-গুলো method লুকায়, package-গুলো class লুকায় (C#-এর internal), service-গুলো database লুকায় API-এর পেছনে।
একটা দরকারী অভ্যাস: প্রতিটা নতুন method ডিফল্টে private করো, আর শুধু তখনই public-এ তোলো যখন বাস্তবে বাইরের কোনো caller দরকার হয়। পরে visibility বাড়ানো সহজ — compiler সেদিকে কোনো অভিযোগ করে না। কিন্তু পরে কমানো, যখন অপরিচিতরা call করা শুরু করে ফেলেছে, সেটা কঠিন। লুকানো দিয়ে শুরু করো; publicity অর্জন করো।
Visibility একবারের সিদ্ধান্ত নয়, এটা একটা lifecycle। একটা method কীভাবে এর মধ্য দিয়ে যায়:
নিচের দিকের তীরগুলো দেখো — ওটাই Hide Method। আর deletion শুধু Private state থেকেই নিরাপদ, যেখানে compiler প্রমাণ করতে পারে কেউ সেটা call করে না।
কখন এটা দরকার?
এই লক্ষণগুলো দেখলে সতর্ক হও:
১. একটা public method যার সব caller তার নিজের class-এর মধ্যে। Find Usages চালাও। যদি সব call ভেতর থেকে আসে, তাহলে public keyword একটা মিথ্যা প্রতিশ্রুতি — method-টা একটা রান্নাঘরের ধাপ যেটা মেনুর ব্যাজ পরে আছে।
২. ফুলে ওঠা public surface। ডজন ডজন public method সহ একটা Large Class-এ সাধারণত অনেক helper বাইরে চলে এসেছে। সেগুলো hide করা এই ধরনের class নিয়ন্ত্রণের সবচেয়ে মৃদু প্রথম পদক্ষেপ — কোনো logic সরে না, তবুও class অনেক সহজে বোঝা যায়।
৩. Public method যেগুলো কেউ call করে না। প্রায়ই এগুলো "যদি কেউ একদিন দরকার করে" ভেবে expose করা হয়েছিল — Speculative Generality smell। এখনই hide করো; পরে unused থাকলে delete করো।
৪. নাম পরিবর্তনের ভয়। তুমি একটা helper-কে ভালো নাম দিতে চাও কিন্তু ভাবছো "কোথাও কেউ হয়তো call করছে।" সেই ভয়টাই হলো মিথ্যা publicity-র মূল্য। method hide করো, rename করা free হয়ে যাবে।
৫. অন্য refactoring-এর পরে বাকি পড়ে থাকা helper। Refactoring নিজেই helper তৈরি করে: Long Parameter List সারাতে parameter object introduce করলে, বা Data Clumps ভাঙলে, পুরানো building-block method-গুলো আর বাইরে থেকে দরকার হয় না — কিন্তু inertia থেকে public থেকে যায়। প্রতিটা বড় refactoring-এর পরে sweep করো।
কখন এটা সঠিক পদক্ষেপ নয়? যখন বাস্তব বাইরের caller আছে এবং তারা legitimate — তখন method সত্যিই মেনুর অংশ। যখন subclass-গুলো সেটা দরকার — private নয়, protected ব্যবহার করো। আর যখন framework অদৃশ্যভাবে method call করে (reflection, DI, serialization callback) — hide করলে ভালোভাবে compile হবে কিন্তু runtime-এ app ভেঙে যাবে। hide করার আগে verify করো।
ধরো একটা team তাদের ReportCard class audit করল। মেনু-থেকে-রান্নাঘর অনুপাত এরকম দেখাল:
"public API"-এর অর্ধেকেরও বেশি ছিল রান্নাঘরের ধাপ, আর ষষ্ঠাংশ ছিল pure speculative generality — কোথাও কোনো caller নেই অথচ public।
যেকোনো individual method বিচার করার একটা দ্রুত chart — কে সেটা call করে, আর এর internals কতটা পরিবর্তন হবে বলে তুমি ভাবছো:
দেখো: taxFor — শুধু internal আর ঘনঘন বদলায় — "এখনই hide করো।" pricing chain-এর বাইরের caller আছে যারা নিজেই রেসিপি রান্না করছে, তাই প্রথমে তাদের একটা সঠিক dish দাও (নতুন public method), তারপর ধাপগুলো hide করো। bookTicket হলো সৎ মেনু: বাইরের caller, stable চুক্তি।
এক নজরে আগে এবং পরে
আগে — মেনুতে সব রান্নাঘরের ধাপ:
// BEFORE: 6 public methods — but only 2 are the real menu
class BiryaniKitchen {
public orderBiryani(plates: number): string { /* ... */ return "served"; }
public orderKebab(plates: number): string { /* ... */ return "served"; }
public prepareSecretMasala(): string { return "masala"; } // leaked!
public marinateMeat(masala: string): string { return "marinated"; }
public layerRice(meat: string): string { return "layered"; }
public slowCookOnDum(pot: string): string { return "biryani"; }
}
// somewhere in ordering-screen code, a "clever" shortcut appeared:
const masala = kitchen.prepareSecretMasala(); // a customer inside the kitchenপরে — মেনুতে শুধু খাবার দেখায়; ধাপগুলো অদৃশ্য:
// AFTER: 2 public methods (the menu), 4 private steps (the kitchen)
class BiryaniKitchen {
public orderBiryani(plates: number): string {
const masala = this.prepareSecretMasala();
const meat = this.marinateMeat(masala);
const pot = this.layerRice(meat);
return this.slowCookOnDum(pot);
}
public orderKebab(plates: number): string { /* ... */ return "served"; }
private prepareSecretMasala(): string { return "masala"; }
private marinateMeat(masala: string): string { return "marinated"; }
private layerRice(meat: string): string { return "layered"; }
private slowCookOnDum(pot: string): string { return "biryani"; }
}
// kitchen.prepareSecretMasala(); // compile error — kitchen door is closedClass আগে যা করত এখনও তাই করে — logic-এর একটা লাইনও বদলায়নি। কিন্তু এর আকৃতি সম্পূর্ণ বদলে গেছে। একজন পাঠক এখন সাথে সাথে দেখতে পায় এই class পৃথিবীকে দুটো জিনিস অফার করে, বাকি সবকিছু ভেতরের রান্না।
দেখো একটা অর্ডার কীভাবে system-এর মধ্য দিয়ে যায় — খদ্দেরের কথোপকথন সংক্ষিপ্ত; রান্নাঘরের নিজের সাথে নিজের কথোপকথন দীর্ঘ, কিন্তু অদৃশ্য:
ধাপে ধাপে, নিরাপদ পদ্ধতিতে
Hide Method দেখতে one-word edit-এর মতো, mechanically তাই-ই। কিন্তু যত্ন যায় আগে আর পরে verify করতে। এখানে পুরো রেসিপি।
ধাপ ১: candidate-গুলো তালিকা করো। class-এর প্রতিটা public method দেখো আর জিজ্ঞেস করো: "এটা কি একটা dish, নাকি রান্নার ধাপ?" একই class-এর অন্য method-গুলো শুধু যে helper call করে সেগুলোই candidate।
ধাপ ২: candidate-এর প্রতিটা caller খোঁজো। পুরো solution জুড়ে Find Usages / Find All References ব্যবহার করো, test project আর যেকোনো dependent repository সহ। caller-গুলো কোথায় তা অনুযায়ী ভাগ করো:
- সব একই class-এ →
private-এর candidate। - একই class plus subclass →
protected-এর candidate। - শুধু একই assembly/module (C#) →
internal-এর candidate। - বাস্তব বাইরের caller → থামো; এই method সত্যিই মেনুতে আছে।
ধাপ ৩: invisible caller চেক করো। Compile-time search reflection, dependency injection, serializer callback, বা framework lifecycle hook দেখতে পায় না। codebase-এ method-এর নামটা string হিসেবে খোঁজো, route table, DI registration, আর attribute চেক করো।
ধাপ ৪: modifier কমাও — সবচেয়ে restrictive level-এ যেটা সব legitimate caller-এর কাজ চালায়।
// the entire edit:
private taxFor(amount: number): number { /* unchanged body */ }ধাপ ৫: Compile করো। Compiler এখন তোমার হয়ে প্রতিটা call site পুনরায় চেক করে। মিস করা কোনো out-of-scope caller থাকলে, তুমি একটা পরিষ্কার, সম্পূর্ণ error list পাবে। প্রতিটার জন্য সিদ্ধান্ত নাও: সেই calling logic class-এর ভেতরে নিয়ে এসো, বা একটা সংকীর্ণ intentional public method expose করো যা caller আসলে চাইছিল।
ধাপ ৬: test চালাও। যদি কোনো test এখন-private method সরাসরি call করছিল, সেটাকে public behaviour-এর মাধ্যমে test করতে rewrite করো — dish অর্ডার করো আর খেয়ে দেখো; রান্নাঘরে ঢুকে মশলার চামচ চেটে দেখতে যেও না।
ধাপ ৭ (পুরস্কার): hidden method অবাধে উন্নত করো। এইজন্যই আমরা hide করি। এখন যেহেতু taxFor private, এটার নাম gstFor করো, parameter বদলাও, দুভাগ করো — compiler গ্যারান্টি দেয় blast radius হলো একটাই file।
Sweep-এর পর sweep, একটা class-এর public surface তার সৎ আকারের দিকে কমে:
Curve চারে সমতল হয় — আর এটাই সঠিক। লক্ষ্য শূন্য public method নয়। লক্ষ্য হলো ঠিক ততটুকু মেনু যতটা প্রতিশ্রুতি class সত্যিই রাখতে চায়।
বিপজ্জনক caller হলো যেগুলো compiler দেখতে পায় না। Reflection দিয়ে invoke করা, DI container দিয়ে call করা, serialization hook হিসেবে ব্যবহৃত, বা framework-এ নাম দিয়ে bound method — এগুলো Find Usages-এ দেখা যাবে না। সেগুলো hide করলে code perfectly compile হবে কিন্তু runtime-এ ভেঙে যাবে। framework-managed class-এ কিছু hide করার আগে framework-এর documentation চেক করো, আর method-এর নাম string হিসেবে grep করো। আর published library-তে মনে রেখো: visibility কমানো consumer-দের জন্য breaking change — এটা patch-এ নয়, major version-এ করতে হবে।
বাস্তব জীবনের একটা বড় উদাহরণ
ধরো ঢাকার একটা স্কুলে report card software চলছে। বছরের পর বছর ধরে ReportCard-এর প্রতিটা helper "যদি দরকার হয়" ভেবে public করা হয়েছে, আর বাইরের কেউ রান্নাঘরের ধাপগুলোর উপর নির্ভর করা শুরু করেছে:
// BEFORE: ten public methods; the real menu is only three
class ReportCard {
public generate(studentId: string): string { /* ... */ return "card"; }
public emailToParent(studentId: string): void { /* ... */ }
public classTopperList(): string[] { /* ... */ return []; }
// Helpers that leaked onto the menu:
public fetchMarks(studentId: string): number[] { /* ... */ return []; }
public computeAverage(marks: number[]): number { /* ... */ return 0; }
public assignGrade(avg: number): string { /* ... */ return "A"; }
public formatHeader(studentId: string): string { /* ... */ return ""; }
public formatMarksTable(marks: number[]): string { /* ... */ return ""; }
public applyGraceMarks(marks: number[]): number[] { /* ... */ return marks; }
public roundOffAverage(avg: number): number { /* ... */ return avg; }
}নিশ্চিতভাবেই, বাইরের code নিজেই রান্না শুরু করল:
// In the sports-day module, far from ReportCard:
const marks = reportCard.fetchMarks(studentId);
const avg = reportCard.computeAverage(reportCard.applyGraceMarks(marks));
const grade = reportCard.assignGrade(avg); // recomputed the recipe by hand!এটা রহিম বিশেষ কাউন্টারে নিজে মশলা মেশানোর মতো। sports module-এ এখন grading recipe-র নিজস্ব copy আছে — আর যখন স্কুল grace-marks নিয়ম বদলায়, report card update হয় কিন্তু sports list চুপচাপ ভিন্ন থাকে। একটা program-এ "সুমাইয়ার grade কী?" এর দুটো আলাদা উত্তর।
সমাধানের দুটো অংশ। প্রথমে, বাইরের caller আসলে যা চাইছিল তা দাও — উপাদান নয়, একটা dish:
class ReportCard {
// NEW: the dish the sports module was trying to cook for itself
public gradeFor(studentId: string): string {
const marks = this.applyGraceMarks(this.fetchMarks(studentId));
return this.assignGrade(this.roundOffAverage(this.computeAverage(marks)));
}
// ...
}
// Sports module, after:
const grade = reportCard.gradeFor(studentId); // one honest order from the menuদ্বিতীয়ত, প্রতিটা ধাপ hide করো:
// AFTER: menu of four; kitchen of seven
class ReportCard {
public generate(studentId: string): string { /* ... */ return "card"; }
public emailToParent(studentId: string): void { /* ... */ }
public classTopperList(): string[] { /* ... */ return []; }
public gradeFor(studentId: string): string { /* ... */ return "A"; }
private fetchMarks(studentId: string): number[] { /* ... */ return []; }
private computeAverage(marks: number[]): number { /* ... */ return 0; }
private assignGrade(avg: number): string { /* ... */ return "A"; }
private formatHeader(studentId: string): string { /* ... */ return ""; }
private formatMarksTable(marks: number[]): string { /* ... */ return ""; }
private applyGraceMarks(marks: number[]): number[] { /* ... */ return marks; }
private roundOffAverage(avg: number): number { /* ... */ return avg; }
}সুবিধাগুলো গণনা করো:
- একটা রেসিপি, একটা রান্নাঘর। Grading rules এখন ঠিক একটা জায়গায় চলে। grace-marks policy বদলালে স্কুলের প্রতিটা screen স্বয়ংক্রিয়ভাবে একমত হয়।
- শেখার যোগ্য class। নতুন teammate
ReportCardপড়ে চারটা public method দেখে আর দশ সেকেন্ডে class-এর পুরো কাজ বোঝে। - ভেতরে স্বাধীনতা। কাল
computeAverage-এর নামweightedAverageকরা যাবে, দুটো formatting helper merge করা যাবে, বা grading সম্পূর্ণ rewrite করা যাবে — এই file-এর বাইরে কিছু ভাঙতে পারবে না এই guarantee নিয়ে।
সম্পূর্ণ structure, class diagram হিসেবে — উপরে মেনু, নিচে রান্নাঘর, outsider-রা শুধু মেনুর সাথে connected:
এই পরিবর্তন involved মানুষদের কাছে কেমন লাগল:
C#-এ একই refactoring
C# বেশিরভাগ ভাষার চেয়ে সূক্ষ্মভাবে graded visibility ladder দেয়, তাই Hide Method সুনির্দিষ্টভাবে tune করা যায়:
public class ReportCard
{
// The menu — the promise to the whole world:
public string Generate(string studentId) { /* ... */ return "card"; }
// Kitchen step, used only inside this class:
private decimal ComputeAverage(IReadOnlyList<int> marks)
=> marks.Count == 0 ? 0 : (decimal)marks.Average();
// Needed by ReportCard subclasses (e.g., KindergartenReportCard), nobody else:
protected virtual string AssignGrade(decimal average)
=> average >= 90 ? "A+" : average >= 75 ? "A" : "B";
// Shared by other classes in THIS library, but not part of the public API:
internal string FormatHeader(string studentId) { /* ... */ return ""; }
// Subclasses within this assembly only — the strictest blend:
private protected int[] ApplyGraceMarks(int[] marks) { /* ... */ return marks; }
}সবচেয়ে hidden থেকে সবচেয়ে open পর্যন্ত ladder:
| Modifier | কে call করতে পারে | রান্নাঘরের ছবি |
|---|---|---|
private | শুধু এই class | প্রধান বাবুর্চির ব্যক্তিগত মশলার ডাব্বা |
private protected | Subclass, শুধু একই assembly | পারিবারিক রেসিপি — শুধু নিজের শাখায় share করা |
protected | এই class আর যেকোনো subclass | Franchise রান্নাঘরে দেওয়া রেসিপি |
internal | একই assembly-এর যেকোনো code | এই রেস্তোরাঁর সব কর্মীর কাছে খোলা, খদ্দেরের কাছে অদৃশ্য |
protected internal | একই assembly বা যেকোনো subclass | কর্মী plus সব franchise |
public | সবাই, চিরকাল | মেনুতে ছাপা — একটা প্রতিশ্রুতি |
দুটো C#-specific বিষয় মনে রাখার মতো:
internalহলো library code-এ Hide Method-এর সেরা বন্ধু। তোমার NuGet package-এর বেশ কয়েকটা class জুড়ে ব্যবহৃত helperinternalহতে পারে: package-এর ভেতরের প্রতিটা class সেটা দেখতে পায়; বাইরের কোনো consumer কখনো পায় না। Package-এর public API একটা পরিষ্কার, ছোট মেনু থাকে। (যদি test project-এর access দরকার হয়,[assembly: InternalsVisibleTo("MyLib.Tests")]ঠিক একটা trusted guest-এর জন্য রান্নাঘরের দরজা খুলে দেয়।)- Default visibility ইতিমধ্যে সাহায্য করে। C#-এ, কোনো modifier ছাড়া member ডিফল্টে
private— ভাষাটা নিজেই "start hidden"-এ বিশ্বাস করে। Hide Method প্রায়ই শুধু default যা দিত তাই restore করে।
// Consumer code, after hiding:
var card = new ReportCard();
card.Generate("BPL-0421"); // fine — on the menu
// card.ComputeAverage(...); // CS0122: inaccessible due to protection level
// card.FormatHeader(...); // CS0122 from outside the assemblyসেই CS0122 error compiler-এর রুক্ষতা নয়। এটা রান্নাঘরের দরজা, তার কাজ করছে।
Python-এর একটা বিশেষ note দেওয়া দরকার। Python-এ বিখ্যাতভাবে কোনো enforced private নেই — এর রান্নাঘরের দরজা তালা নয়, একটা পর্দা। community naming convention-এর উপর নির্ভর করে, আর সেগুলো আশ্চর্যজনকভাবে ভালো কাজ করে:
# Python: hiding by convention — the leading underscore
class ReportCard:
def generate(self, student_id: str) -> str: # menu
marks = self._apply_grace_marks(self._fetch_marks(student_id))
return self._format_card(student_id, marks)
# One underscore: "kitchen step — please do not call from outside."
def _fetch_marks(self, student_id: str) -> list[int]: ...
def _apply_grace_marks(self, marks: list[int]) -> list[int]: ...
def _format_card(self, student_id: str, marks: list[int]) -> str: ...
# Two underscores trigger name mangling — a stronger hint, rarely needed:
def __secret_weighting(self, marks: list[int]) -> float: ...Linter-গুলো _underscore method-এর বাইরের call flag করে, from module import * সেগুলো skip করে, আর IDE autocomplete সেগুলোকে কম গুরুত্ব দেয়। শিক্ষাটা উভয়দিকে যায়: এমনকি যেখানে compiler দরজা বন্ধ করতে পারে না, ঘোষণা করা যে কোনটা রান্নাঘর আর কোনটা মেনু সেটাও লাভজনক।
IDE সহায়তা
Tool-গুলো শুধু এই refactoring perform করে না — সেগুলো সক্রিয়ভাবে এটা suggest করে:
- JetBrains Rider / ReSharper: code inspection চালাও আর "Member can be made private"-style suggestion খোঁজো — analyzer সেই member detect করে যাদের actual usage তাদের declared visibility-র চেয়ে সংকীর্ণ, আর one-click fix দেয় (
Alt+Enter)। Find Usages (Shift+F12/Alt+F7) ধাপ ২-এর জন্য caller map দেয়। - IntelliJ IDEA (Java): classic inspection "Declaration access can be weaker" ঠিক এই method-গুলো highlight করে, Refactor → Change Signature বা quick-fix project জুড়ে safety check সহ modifier কমিয়ে দেয়।
- Visual Studio: Find All References (
Shift+F12) "এটা কে call করে?" এর উত্তর দেয়; Roslyn analyzer unused member flag করে (যেমন IDE0051 unused private member-এর জন্য)। Code-style rules explicit accessibility modifier require করতে পারে যাতে visibility সবসময় সচেতন choice হয়। - VS Code (TypeScript): Find All References (
Shift+F12) plus compiler যথেষ্ট:publicথেকেprivateকরো, আর সব out-of-scope caller Problems panel-এ তোমার সম্পূর্ণ to-do list হিসেবে দেখাবে।
একটা অভ্যাস এই সব tool-এর মূল্য বহুগুণ করে: প্রতিটা "can be made private" analyzer suggestion-কে একটা ছোট উপহার হিসেবে দেখো — প্রতিটা হলো একটা প্রতিশ্রুতি যা তুমি বিনামূল্যে ফিরিয়ে নিতে পারো।
সুবিধা এবং ঝুঁকি
| সুবিধা | ঝুঁকি / খরচ |
|---|---|
| ছোট, উদ্দেশ্যমূলক public API — নতুনরা মেনু থেকে class শেখে, রান্নাঘর থেকে নয় | reflection, DI, বা framework নাম দিয়ে call করা method hide করলে runtime-এ app ভাঙে কিন্তু cleanly compile হয় |
| Hidden method-গুলো গ্যারান্টিযুক্ত one-file blast radius সহ rename, reshape, বা delete করা যায় | subclass-এর দরকার হলে private-এ over-tighten করলে inheritor ভেঙে যায় — ইচ্ছাকৃতভাবে protected ব্যবহার করো |
| Outsider-রা আর internal recipe নিজে রান্না করে না — logic এক জায়গায় থাকে আর duplicate হতে পারে না | published library-তে visibility কমানো major version bump চায় কারণ এটা breaking change |
| Compiler সীমানা চিরকালের জন্য enforce করে — রান্নাঘরের বাইরে অপরিচিতদের রাখতে code review দরকার নেই | সরাসরি method call করা test-গুলো public behaviour-এর মাধ্যমে যেতে rewrite করতে হবে (সাধারণত উন্নতি, কিন্তু কাজ) |
| প্রতিটা hidden helper class-কে রাখতে হবে এমন প্রতিশ্রুতি কমায় — সময়ের সাথে change সস্তা হয় | Visibility একা badly designed class ঠিক করে না; hidden mess এখনও mess (প্রয়োজনে Extract Class-এর সাথে pair করো) |
এটা কোন smell সারায়?
| Smell | Hide Method কীভাবে সাহায্য করে |
|---|---|
| Large Class | bloated public surface সত্যিকারের contract-এ সংকুচিত হয়; গভীর surgery-র আগেই class পাঠযোগ্য হয় |
| Speculative Generality | "কেউ একদিন হয়তো দরকার করবে" method-গুলো ভেতরে ফিরে যায়; এখনও unused থাকলে unused-member analyzer মুছে ফেলার জন্য flag করে |
| Duplicate Code | Outsider-রা আর public helper থেকে ধাপে ধাপে recipe copy করতে পারে না — তাদের একটা real dish অর্ডার করতে হবে, তাই logic একক থাকে |
| Inappropriate Intimacy | Class-গুলো একে অপরের internal-এ খোঁচা দেওয়ার ক্ষমতা হারায়; শুধু intentional interface-এর মাধ্যমে কথোপকথন বাকি থাকে |
| Dead Code | Hiding হলো নিরাপদ deletion-এর ধাপ এক: একবার private হলে, unused helper provably unreachable আর ভয় ছাড়াই remove করা যায় |
একটা মানচিত্রে পুরো ধারণা — Hide Method Remove Setting Method আর Encapsulate Collection-এর মতো একই locking-down পরিবারে:
দ্রুত revision box
+=================================================================+
| HIDE METHOD — REVISION CARD |
+=================================================================+
| SMELL SIGN : public method whose callers all live in its |
| own class (a kitchen step on the menu) |
| PICTURE : secret masala stays INSIDE the kitchen; |
| customers only order dishes from the menu |
+-----------------------------------------------------------------+
| THE MOVE : 1. List helper-looking public methods |
| 2. Find Usages across the WHOLE solution |
| 3. Check invisible callers: reflection, DI, |
| serializers, framework hooks (grep the name!) |
| 4. Lower to the most restrictive level that works |
| private -> protected -> internal -> public |
| 5. Compile: errors = missed callers, fix each |
| 6. Test via PUBLIC behaviour, not private spoons |
| 7. Reward: rename/reshape the helper freely |
+-----------------------------------------------------------------+
| C# LADDER : private | private protected | protected | |
| internal | protected internal | public |
| REMEMBER : every public method is a PROMISE — |
| start hidden, earn publicity |
+=================================================================+অনুশীলন
ধরো ঢাকার একটা সিনেমা হলের ticket-booking system-এ এই class আছে:
class TicketCounter {
public bookTicket(showId: string, seats: number): string { /* ... */ return "PNR"; }
public cancelTicket(pnr: string): void { /* ... */ }
public findFreeSeats(showId: string): number[] { /* ... */ return []; }
public calculateBasePrice(showId: string, seats: number): number { /* ... */ return 0; }
public addWeekendSurcharge(price: number, showId: string): number { /* ... */ return price; }
public applyGstAt18Percent(price: number): number { /* ... */ return price; }
public generatePnr(): string { /* ... */ return ""; }
public sendSms(phone: string, text: string): void { /* ... */ }
}
// Found elsewhere in the codebase:
// 1. The kiosk screen calls findFreeSeats(showId) to show a seat map.
// 2. The "group booking" module computes prices by chaining
// calculateBasePrice -> addWeekendSurcharge -> applyGstAt18Percent itself.
// 3. Nothing anywhere calls generatePnr or sendSms except bookTicket.তোমার কাজ:
১. আটটা method-কে classify করো: menu (সত্যিকারের public), kitchen step (hide করো), বা "investigation দরকার।" প্রতিটার জন্য একটা লাইনে justify করো, আর চিত্র ৪-এর quadrant chart-এ রাখো।
২. generatePnr আর sendSms-এর বাইরে কোনো caller নেই। সেগুলো hide করো, compile করো, আর blast radius নিশ্চিত করো। কোনটা framework বা scheduled job সম্ভবত নাম দিয়ে call করতে পারে — আর hide করার আগে তুমি কীভাবে চেক করবে?
৩. group-booking module pricing recipe নিজে রান্না করছে (finding #2)। একটা সৎ public method design করো — নাম দাও আর signature লেখো — যা তিনটা pricing step hide করতে দেয়। GST rate বদলালে এটা কোন bug category দূর করে?
৪. findFreeSeats-এর একটা real outside caller আছে (kiosk)। এটা public থাকা উচিত, নাকি kiosk একটা ভিন্ন, সংকীর্ণ method পাওয়া উচিত? দুটো দিক নিয়ে দুটো করে বাক্যে যুক্তি দাও।
৫. applyGstAt18Percent-এর জন্য চিত্র ২ state diagram আঁকো: এর জীবনে এটা এ পর্যন্ত কোন state-গুলোর মধ্য দিয়ে গেছে, আর তুমি এখন কোন transition perform করতে যাচ্ছো?
৬. Bonus (C#): C# modifier ব্যবহার করে finished class sketch rewrite করো। pricing helper-গুলো private-এ রাখো, একই assembly-তে OnlineCounter নামের একটা sister class imagine করো যার generatePnr দরকার, আর এর জন্য সঠিক modifier বেছে নাও। test project-এর জন্য তুমি যে InternalsVisibleTo line ব্যবহার করবে তা যোগ করো। Bonus (Python): underscore convention ব্যবহার করে class rewrite করো আর বলো তুমি কী enforcement হারাচ্ছো।
৭. College-corner প্রশ্ন: Parnas-এর ভাষায়, TicketCounter-এর "secret" কী যা এর module লুকানো উচিত — আর class একটার বেশি secret লুকাচ্ছে এমন কোনো প্রমাণ আছে কি? hide করার পরেও, class এখনও booking, pricing, PNR generation, আর SMS sending করে। এটা কি একটা রান্নাঘর নাকি দুটো? তুমি পরবর্তীতে কোন refactoring-এর দিকে যাবে?
সচরাচর জিজ্ঞাসা
- আমি কীভাবে নিশ্চিত হব যে class-এর বাইরে কেউ এই method টা call করে না?
- অনুমান করবে না — tool-কে জিজ্ঞেস করো। IDE-এর Find Usages বা Find All References দিয়ে method-এ রান করো, দেখো প্রতিটা caller কোথায় আছে। তারপর change করো আর compile করো: compiler মিস করা যেকোনো caller-কে error হিসেবে দেখাবে — এটাই এই refactoring-এর safety net। reflection, dependency injection, serializer, আর framework callback-এর বিষয়ে বিশেষ সাবধান থাকো — এই caller-গুলো compiler দেখতে পায় না, তাই method-এর নামটা string হিসেবেও খুঁজো।
- আমার test কি private method সরাসরি call করতে পারবে?
- না করাই ভালো। যে test সরাসরি private helper call করে সে implementation detail-এর সাথে নিজেকে জুড়ে ফেলে — hiding ঠিক সেটাকেই মুক্ত করতে চায়। যে public method সেটা ব্যবহার করে, সেটার মাধ্যমে private logic test করো: বিরিয়ানি অর্ডার করো আর খেয়ে দেখো, রান্নাঘরে ঢুকে মশলা চেখে দেখতে যাবে না। কোনো private helper এতটাই জটিল মনে হলে যে সরাসরি test দরকার, সেটা প্রায়ই একটা সংকেত যে সেটাকে আলাদা ছোট class-এ extract করা দরকার।
- private, protected, আর internal-এর মধ্যে পার্থক্য কী — কোনটা বেছে নেব?
- সবচেয়ে restrictive level বেছে নাও যেটা সব legitimate caller-এর কাজ চালাতে পারে। private: শুধু এই class — helper-এর জন্য default choice। protected: এই class আর এর subclass — তখনই ব্যবহার করো যখন subclass সত্যিই দরকার। internal (C#): একই assembly-এর যেকোনো code কিন্তু বাইরে কেউ না — একটা library-এর ভেতরে shared helper-এর জন্য perfect। নিয়ম হলো private দিয়ে শুরু করো আর শুধু তখনই loosen করো যখন কোনো real caller বাধ্য করে।
- কোনো method hide করা কি breaking change?
- নিজের application-এর ভেতরে, না — compiler সব caller চেক করে। published library-তে, হ্যাঁ: সেই method call করা যেকোনো external code নতুন version-এ compile করতে পারবে না, তাই visibility কমানো একটা major version bump আর migration note দাবি করে। এইজন্যই শুরু থেকে public surface ছোট রাখা এত মূল্যবান — আজকে যে method expose করবে সেটা আগামীকালের একটা প্রতিশ্রুতি।
- আমার framework reflection বা dependency injection-এর মাধ্যমে একটা method call করে যদিও কোনো code সেটা reference করে না। তবুও কি hide করতে পারি?
- কখনো কখনো না। Lifecycle hook, serializer callback, DI-injected method, আর route handler runtime-এ নাম বা attribute দিয়ে invoke হতে পারে, কোনো compile-time reference ছাড়াই। সেগুলো hide করলে app ভেঙে যাবে কিন্তু তবুও cleanly compile হবে। hide করার আগে framework-এর requirement চেক করো। যখন কোনো method শুধু framework-এর জন্য public থাকতে হয়, comment বা attribute দিয়ে সেই intent স্পষ্ট করো যাতে কেউ এটাকে real API-এর অংশ না ভাবে।
আরো দেখো
সম্পর্কিত পাঠ
Large Class: যে স্কুলের ব্যাগে সব কিছু থাকে
Large Class code smell কী সেটা বুঝো — কেন god class বড় হয়, low cohesion কীভাবে চেনা যায়, আর Extract Class দিয়ে কীভাবে ছোট ছোট focused class-এ ভাগ করা যায়।
Speculative Generality: যে সুইমিং পুলের জন্য পাইপ বসালে, পুলটাই হলো না
বাড়ি বানানোর গল্প দিয়ে Speculative Generality smell বোঝো। YAGNI কী, ভবিষ্যতের অনুমানে কোড লেখা কেন ক্ষতিকর, আর অব্যবহৃত abstraction কীভাবে সরাতে হয় — সব পরিষ্কার হয়ে যাবে।
Remove Setting Method: কিছু জিনিস কলমে লেখা, পেন্সিলে না
Remove Setting Method সহজ ভাষায় — কেন এমন একটা field যেটা তৈরির পরে কখনো বদলানো উচিত না তার setter রাখা ঠিক না, আর কীভাবে read-only field, init-only property, আর record দিয়ে 'এটা বদলিও না' কথাটাকে compiler-এর গ্যারান্টিতে বদলানো যায়।
Long Parameter List: দশটা নির্দেশনার চায়ের অর্ডার
Long Parameter List কোড স্মেল সহজ ভাষায় — কেন বেশি argument-এর method বাগ তৈরি করে, আর কীভাবে parameter object দিয়ে call ছোট, পরিষ্কার আর নিরাপদ করা যায়।