Rename Method: দোকানের সাইনবোর্ড যেন সত্যি কথা বলে
Rename Method সহজভাবে বোঝানো — কেন method-এর নাম সত্যিটা বলতে হবে, কীভাবে নিরাপদে নাম বদলাতে হয় delegate পদ্ধতিতে, আর কীভাবে VS Code (F2) ও JetBrains (Shift+F6) দিয়ে এটা এক কীতেই সারা যায়।
মিথ্যা বলা দোকানের সাইনবোর্ড
ধরো আমাদের পাড়ায় একটা দোকান আছে। উপরের সাইনবোর্ডে বড় করে লেখা: "জামাল জেনারেল স্টোর"। গত শুক্রবার বিকেলে আমার ছোট ভাই রুবেল রোদে পনেরো মিনিট হেঁটে গেল সেখানে — সাবান, টুথপেস্ট, আর একটা প্যাকেট বিস্কুট কিনবে।
সে খালি হাতে ফিরে এল। মাথায় ঘাম, মুখে বিরক্তি। কেন? দোকানে শুধু মোবাইল রিচার্জ হয়। সাবান নেই, বিস্কুট নেই। শুধু রিচার্জ, SIM কার্ড, আর একটা কাচের কেসে ফোনের কভার। সাইনবোর্ডটা আঁকা হয়েছিল বছর পাঁচেক আগে, যখন জামাল চাচা সত্যিই চাল-ডাল-সাবান বিক্রি করতেন। তারপর পাশের মোড়ে সুপারশপ খুলল, মুদির ব্যবসা শুকিয়ে গেল, আর জামাল চাচা মোবাইল রিচার্জে চলে গেলেন। ব্যবসা বদলে গেল। সাইনবোর্ড বদলাল না।
এই পুরনো সাইনবোর্ড প্রতিদিন কী করছে একটু ভাবো:
- রুবেলের মতো মানুষরা রোদে পুরো একটা ট্রিপ নষ্ট করছে।
- জামাল চাচা সারাদিন বলছেন "না ভাই, শুধু রিচার্জ" — বলেছেন দিনে পঞ্চাশবারও হয়।
- যারা আসলে রিচার্জ চায় তারা দোকানের সামনে দিয়ে চলে যাচ্ছে, কারণ সাইনবোর্ডে রিচার্জের কথা নেই।
- একদিন একজন সাবানের পাইকারি বিক্রেতা এক ঘণ্টা নষ্ট করলেন "জেনারেল স্টোর"-কে তার মাল নেওয়াতে চেষ্টা করে।
সাইনবোর্ডটা শুধু অকেজো না। এটা দুই দিক থেকেই ক্ষতিকর — ভুল মানুষদের টেনে আনছে, সঠিকদের দূরে ঠেলছে। সমাধান? একটা বিকেল, একজন রঙ মিস্ত্রি: সাইনবোর্ড রঙ করে লেখো "জামাল মোবাইল রিচার্জ ও সিম পয়েন্ট"। এরপর থেকে রাস্তা থেকেই প্রতিটা মানুষ দোকানটা বুঝে যাবে — ভেতরে না ঢুকেই, চিরকালের জন্য।
আমাদের code-এর method-গুলোরও সাইনবোর্ড আছে — সেটা তাদের নাম। Caller "রাস্তা থেকে" (call site থেকে) নামটা পড়ে বুঝতে চেষ্টা করে method-টা কী করে, body না খুলেই। নাম যদি বলে check() কিন্তু method আসলে বিল calculate করে, তাহলে reader হলো বেচারা রুবেল — বারবার ভুল দোকানে হেঁটে যাচ্ছে।
Rename Method হলো সাইনবোর্ড নতুন করে রঙ করার কাজ। আমরা method-এর নাম বদলাই যাতে নামটা সত্যি কথা বলে — method আজকে কী করে, দুই বছর আগে কী করত সেটা না।
Rename Method কী?
Rename Method হলো Martin Fowler-এর Refactoring বই থেকে একটা refactoring। পুরো ব্যাপারটা এক লাইনে:
একটা method-এর নাম এমনভাবে বদলাও যাতে শুধু নামটাই বলে method-টা কী করে।
ব্যস, এটুকুই। কোনো logic পরিবর্তন নেই। কোনো behaviour পরিবর্তন নেই। Rename-এর আগে ও পরে program একদম একই কাজ করে। শুধু লেবেলটা উন্নত হয়।
এত ছোট পরিবর্তন বিখ্যাত বইয়ে পুরো একটা অধ্যায় পাওয়ার যোগ্য কেন? কারণ code লেখার চেয়ে অনেক বেশিবার পড়া হয়। একটা method একবার লেখো, কিন্তু তুমি আর তোমার teammates বছরের পর বছর এর call site পড়তে থাকো। যতবার কেউ const x = get(customer) পড়ে body খুলতে বাধ্য হয়, খারাপ নামটা একটা ছোট ট্যাক্স নিয়ে নেয়। একটা সঠিক নাম সেই ট্যাক্স চিরকালের জন্য মিটিয়ে দেয়। এটা পৃথিবীর সবচেয়ে সস্তা documentation — প্রতিটা call-এর সাথে ভ্রমণ করে, কখনো outdated হয় না, আর compiler নিজেই check করে।
একটা দ্বিতীয়, চুপচাপ উপহারও আছে। ভালো নাম খোঁজার চেষ্টা করাটা একটা design test। পাঁচ মিনিট বসে থেকেও যদি একটা method-এর জন্য একটাও সৎ নাম না পাও, তাহলে method-টাই স্বীকার করছে: "আমি দুটো ভিন্ন কাজ করি।" একটা method যদি calculate করে আর save করে আর notify করে, তাহলে সেটাকে একটা verb দিয়ে সত্যিকারভাবে নাম দেওয়া যাবে না। এই ব্যর্থ naming attempt বলে দেয় আগে method-টা ভাগ করো (Extract Method বা Separate Query from Modifier ব্যবহার করে), তারপর পরিষ্কার টুকরোগুলোর নাম দাও। জামাল চাচাও এই একই সমস্যায় পড়তেন: যদি তিনি এখনও মুদিখানা আর রিচার্জ দুটোই বিক্রি করতেন, কোনো ছোট সাইনবোর্ডই সত্যিটা বলতে পারত না।
পরীক্ষার জন্য একটা গুরুত্বপূর্ণ নোট: Refactoring-এর ২য় সংস্করণে Fowler প্রথম সংস্করণের কয়েকটা refactoring মিলিয়ে দিয়েছেন — Rename Method, Add Parameter, আর Remove Parameter — একটা umbrella refactoring-এ যার নাম Change Function Declaration। চিন্তাটা সহজ: একটা method-এর নাম আর তার parameter list একসাথে তার public "board" তৈরি করে, আর সেই board-এর যেকোনো অংশ বদলানো একই নিরাপদ mechanics অনুসরণ করে। তাই refactoring.com-এ "Change Function Declaration" দেখলে বিভ্রান্ত হয়ো না — Rename Method এর ভেতরেই আছে।
নিজেদের জন্য একটা নিয়ম: rename তখনই refactoring হয় যখন behaviour একদম অপরিবর্তিত থাকে। নাম বদলানোর সাথে সাথে যদি logic change ঢুকিয়ে দাও, একসাথে দুটো কাজ করলে, আর কোনো test fail করলে কোন পরিবর্তনটা ভেঙেছে বুঝতে পারবে না। এক ধাপ, এক উদ্দেশ্য।
পুরো বিষয়টা এক পাতায়, পরীক্ষার আগে নোটবুকের পেছনে sketch করার মতো:
কলেজ কর্নার: আমরা যেটাকে সাধারণভাবে "renaming" বলি সেটা industry scale-এ হলো API evolution। একটা public method নাম হলো একটা contract — অন্য টিম, অন্য service, বা published package একবার এটার উপর নির্ভর করলে নামটা শুধু তোমার থাকে না। পরিপক্ক ecosystem কখনো রাতারাতি public নাম মুছে না। তারা একটা deprecation lifecycle চালায়: নতুন নাম ঘোষণা করো, পুরনোটা deprecated mark করো (Java-র @Deprecated, C#-এর [Obsolete], JavaScript-এর JSDoc @deprecated), এক বা একাধিক release cycle উভয়কে জীবিত রাখো, এবং পরের major version-এ পুরনো নাম সরাও। তুমি নিচে যে gradual rename শিখবে সেটাই এই lifecycle, একটা codebase-এ সংকুচিত।
কখন এটা দরকার?
এই everyday signal-গুলো দেখো। প্রতিটা হলো দোকান থেকে সাইনবোর্ড সরে যাওয়ার লক্ষণ।
- নাম জন্ম থেকেই অস্পষ্ট।
process(),handle(),doStuff(),manage(),get()— এই নামগুলো প্রায় কিছুই বলে না। এগুলো শুধু "দোকান" লেখা সাইনবোর্ড। - Behaviour পরিবর্তন হয়েছে কিন্তু নাম হয়নি।
validateOrder()নামের method যেটা এখন order save-ও করে, সেটা মিথ্যা বলছে। Code বাড়ার সাথে এই drift স্বাভাবিকভাবে হয় — নাম নীরবে পচে, কারণ কিছুই সেটা update করতে বাধ্য করে না। এটাই জামাল চাচার সাইনবোর্ড: একসময় সত্যি, এখন মিথ্যা। - Call explain করতে comment লাগে। যদি প্রতিটা call site-এ
// এটা আসলে discount calculate করেমতো comment থাকে, তাহলে comment নামের কাজ করছে। Comments smell শেখায়: code কী করে সেটা explain করা comment প্রায়ই মানে নামটা ঠিক করা দরকার। - Method-টা নামের চেয়ে ভিন্নভাবে explain করো। Code review-এ বলো "এটা সর্বশেষ invoice fetch করে" — কিন্তু method-এর নাম
check()। স্বাভাবিকভাবে মুখ থেকে যে শব্দ বের হয় সেগুলোই সাধারণত সঠিক নাম। - Search ব্যর্থ হয়। Discount calculation খুঁজতে project-এ "discount" search করলে কিছু পাওয়া যায় না, কারণ method-এর নাম
calc2()। ভালো নাম code আবিষ্কারযোগ্য করে; খারাপ নাম লুকিয়ে রাখে। - নাম what না বলে how বলে।
loopThroughOrdersAndSum()mechanism describe করে। Caller-রা result নিয়ে চিন্তিত:totalOrderAmount()। Loop-কে পরে formula দিয়ে replace করলে "how" নামটা মিথ্যা হয়ে যাবে।
এই refactoring সবচেয়ে সরাসরি যে smell আক্রমণ করে সেটা হলো Mysterious Name smell — Fowler-এর ২য় সংস্করণে এটাকে বইয়ের একদম প্রথম smell হিসেবে রাখা হয়েছে, কারণ অস্পষ্ট নাম real codebase-এ সবচেয়ে সাধারণ আর সবচেয়ে সহজে ঠিকযোগ্য সমস্যা।
মিথ্যা সাইনবোর্ড পড়া মানুষগুলোর কথা ভাবো। গত মাসে "জামাল জেনারেল স্টোর"-এর ভিত্তিতে কাজ করা সবার একটা জরিপ করলে ছবিটা এরকম হত:
মাত্র ত্রিশ শতাংশ reader নাম যা বলেছিল তাই পেয়েছিল। Code-এ এই অনুপাত হলো bug-এর কারখানা: তোমার খারাপ নামের method call করা সত্তর শতাংশ developer হয় ভুল কারণে call করবে, নাহলে ভুল জিনিস pass করবে, নাহলে result ভুলভাবে handle করবে।
তাহলে এখনই rename করবে, নাকি পরে? দুটো প্রশ্ন ঠিক করে: নামটা কতটা মিথ্যা, আর কতজন caller আছে?
ডাক্তার যেভাবে chart পড়েন সেভাবে পড়ো: সক্রিয়ভাবে মিথ্যা নাম (উপরের অর্ধেক) জরুরি — দুইজন caller হোক বা দুইশো। শুধু প্রশ্ন হলো technique: কম caller হলে একটা IDE keypress, বেশি হলে gradual dance। সামান্য off নাম (নিচের অর্ধেক) পরের বার ওই file-এ হাত দেওয়া পর্যন্ত অপেক্ষা করতে পারে।
এক নজরে আগে ও পরে
দেখো TypeScript-এ মিথ্যা সাইনবোর্ড:
// BEFORE — the board says "general store"
class CustomerAccount {
private orders: Order[] = [];
private discountRate = 0.1;
// What does "get" get? Nobody knows without reading the body.
get2(): number {
const subtotal = this.orders.reduce((sum, o) => sum + o.amount, 0);
return subtotal * (1 - this.discountRate);
}
}
// At the call site, the reader learns nothing:
const x = account.get2(); // a lookup? a network call? a total? a mystery.আর সাইনবোর্ড রঙ করার পরে:
// AFTER — the board tells the truth
class CustomerAccount {
private orders: Order[] = [];
private discountRate = 0.1;
calculateDiscountedTotal(): number {
const subtotal = this.orders.reduce((sum, o) => sum + o.amount, 0);
return subtotal * (1 - this.discountRate);
}
}
// The call site now reads like a sentence:
const billAmount = account.calculateDiscountedTotal();লক্ষ্য করো কী পরিবর্তন হয়নি: body, return type, behaviour, tests। শুধু লেবেলটা বদলেছে — আর তবুও প্রতিটা call site-এর প্রতিটা ভবিষ্যৎ reader উপকৃত হবে।
নিরাপদভাবে ধাপে ধাপে
একটা file-এর ভেতরে private method-এর জন্য IDE এক keypress-এ সবকিছু rename করতে পারে। কিন্তু manual, gradual rename-ও জানা দরকার — method public হলে, অন্য টিম ব্যবহার করলে, বা তোমার tool দেখতে পায় না এমন code দিয়ে accessible হলে এটাই একমাত্র নিরাপদ পথ। Fowler এটাকে Change Function Declaration-এর ভেতরে migration approach বলেন।
পুরো পরিকল্পনা এক ছবিতে:
চলো সতর্কভাবে get2() থেকে calculateDiscountedTotal()-এ rename করি।
ধাপ ১ — Inheritance family check করো। কোনো superclass বা subclass কি একই signature-এর method declare করেছে? যদি হ্যাঁ, polymorphism নামের উপর নির্ভর করছে। পুরো family একসাথে rename করতে হবে, নাহলে base-class reference ধরে রাখা caller-রা নীরবে তোমার override-এ পৌঁছাতে পারবে না।
ধাপ ২ — ভালো নামে নতুন method তৈরি করো, body সেখানে নিয়ে যাও।
class CustomerAccount {
// NEW — the truthful name, holding the real body
calculateDiscountedTotal(): number {
const subtotal = this.orders.reduce((sum, o) => sum + o.amount, 0);
return subtotal * (1 - this.discountRate);
}
// OLD — still here, but now just a signpost
get2(): number {
return this.calculateDiscountedTotal();
}
}এটাই মূল কৌশল: পুরনো method এখন শুধু নতুনটাকে delegate করে। প্রতিটা existing caller এখনও কাজ করে। কিছুই ভাঙেনি। দোকানে কিছুক্ষণের জন্য দুটো সাইনবোর্ড — পুরনো ফ্যাকাশেটা আর নতুন পরিষ্কারটা — আর দুটোই একই কাউন্টারে নিয়ে যায়।
ধাপ ৩ — Compile করো আর test চালাও। Behaviour একদম একই হতে হবে। এখন কিছু fail করলে design ভুল না — typo করেছ, ঠিক করা সহজ।
ধাপ ৪ — Caller-দের একে একে migrate করো। প্রতিটা get2() call খুঁজে বের করো আর calculateDiscountedTotal()-এ পরিবর্তন করো। প্রতিটা caller migrate করার পরে আবার test চালাও।
// caller, before:
const x = account.get2();
// caller, after:
const billAmount = account.calculateDiscountedTotal();ধাপ ৫ — পুরনো method delete করো। যখন কোনো caller নেই, get2() পুরোপুরি সরিয়ে দাও। পুরনো ফ্যাকাশে সাইনবোর্ড নামিয়ে ফেলো।
ধাপ ৬ — Public API-এর জন্য delete-এর বদলে deprecate করো। বাইরের consumer যদি get2() call করে, সাথে সাথে সরিও না। Deprecated mark করো (TypeScript JSDoc-এ @deprecated, C#-এ [Obsolete]) যাতে তাদের tool সতর্ক করে, আর পরের major version-এ সরাও।
/** @deprecated Use calculateDiscountedTotal() instead. */
get2(): number {
return this.calculateDiscountedTotal();
}Migration window চলাকালীন, delegation runtime-এ এরকম দেখায় — পুরনো নামটা শুধু একজন ভদ্র receptionist যে visitor-দের নতুন অফিসে forward করছে:
উপর থেকে দেখলে, পুরো codebase তিনটা পরিষ্কার অবস্থার মধ্য দিয়ে যায়। মাঝের অবস্থায় একদিন বা ছয় মাস নিরাপদে থামতে পারো — সেখানে কিছু ভাঙা নেই:
প্রতিটা ছোট ধাপের পরে test চালাও — delegating method তৈরির পরে, আর প্রতিটা caller migrate করার পরে। Rename এত ছোট মনে হয় যে কিছু ভাঙতে পারে না — তাই মানুষ testing skip করে, তারপর broken build ship করে। Compiler সেই caller-গুলো দেখতে পায় না যারা নাম string হিসেবে ব্যবহার করে: reflection, JSON serialization, ORM mapping, RPC route, dynamic lookup। Rename-এর পরে পুরো repository-তে পুরনো নামটা plain text হিসেবে search করো। এই একটা অভ্যাস সবচেয়ে বেশি ক্ষতিকর bug ধরে।
কলেজ কর্নার: মাঝের অবস্থাটা (BothNames) API designer-রা backward compatibility বলেন — কোনো existing consumer না ভেঙে নতুন capability যোগ। বড় platform বছরের পর বছর এই মাঝের অবস্থায় থাকে। Java standard library এখনো দশকের পুরনো deprecated method বহন করে; HTTP API একই কারণে নিজেদের version করে (/v1/, /v2/)। এখানে যে skill চর্চা হচ্ছে সেটা "renaming" না — এটা এটার উপর নির্ভরকারীদের না ভেঙে contract পরিবর্তন করা। আজকে একটা method-এ দক্ষ হও; পরে পুরো service-এ পুনরায় ব্যবহার করবে।
একটা বড় বাস্তব উদাহরণ
ধরো একটা স্কুলের fee portal তিন বছরে বড় হয়েছে। এই class দেখো — প্রতিটা নাম drift করেছে:
// BEFORE — three lying boards in one class
class FeePortal {
// "check" actually CALCULATES the pending amount
check(studentId: string): number {
const fees = this.feeRepo.findByStudent(studentId);
return fees
.filter((f) => !f.paid)
.reduce((sum, f) => sum + f.amount, 0);
}
// "data" actually SENDS a reminder SMS
data(studentId: string): void {
const pending = this.check(studentId);
if (pending > 0) {
this.sms.send(studentId, `Pending fee: Rs.${pending}`);
}
}
// "handleStudent" actually MARKS a fee as paid
handleStudent(studentId: string, receiptNo: string): void {
const fee = this.feeRepo.findOpenFee(studentId);
fee.paid = true;
fee.receiptNo = receiptNo;
this.feeRepo.save(fee);
}
}এখন এই class-এর একটা caller পড়ো আর কষ্ট অনুভব করো:
// What is this code doing? Honestly — who can tell?
const a = portal.check(id);
if (a > 0) portal.data(id);
portal.handleStudent(id, receipt);তিনটা call, শূন্য বোঝাপড়া। প্রতিটা নাম reader-কে body খুলতে বাধ্য করে। চলো তিনটা সাইনবোর্ড নতুন রঙ করি, প্রতিটার জন্য gradual method ব্যবহার করে (নতুন নাম, delegate, migrate, delete)। চূড়ান্ত ফলাফল:
// AFTER — every board tells the truth
class FeePortal {
calculatePendingFee(studentId: string): number {
const fees = this.feeRepo.findByStudent(studentId);
return fees
.filter((f) => !f.paid)
.reduce((sum, f) => sum + f.amount, 0);
}
sendPendingFeeReminder(studentId: string): void {
const pending = this.calculatePendingFee(studentId);
if (pending > 0) {
this.sms.send(studentId, `Pending fee: Rs.${pending}`);
}
}
markFeeAsPaid(studentId: string, receiptNo: string): void {
const fee = this.feeRepo.findOpenFee(studentId);
fee.paid = true;
fee.receiptNo = receiptNo;
this.feeRepo.save(fee);
}
}আর একই caller, পরে:
const pending = portal.calculatePendingFee(id);
if (pending > 0) portal.sendPendingFeeReminder(id);
portal.markFeeAsPaid(id, receipt);জোরে পড়ো। প্রায় বাংলা বাক্যের মতো: pending fee calculate করো; কিছু থাকলে reminder পাঠাও; fee paid mark করো। Program-এর behaviour কিছুই বদলায়নি — কিন্তু এই code বোঝার খরচ "তিনটা method body খোলো" থেকে "তিনটা লাইন পড়ো"-তে নেমে গেছে।
দুটো class board পাশাপাশি তুলনা করো — structure একদম একই; শুধু লেবেলের সত্যতা পরিবর্তন হয়েছে:
আরও একটা bonus: data() rename করার সময় আমরা জিজ্ঞেস করতে বাধ্য হলাম "এটা আসলে কী করে?" — আর লক্ষ্য করলাম এটা pending amount পড়েও আর SMS পাঠায়ও। আজকে একটা নাম (sendPendingFeeReminder) সৎভাবে cover করে। কিন্তু এটা যদি কখনো reminder database-এও record করা শুরু করে, কোনো একটা নামই fit হবে না — আর সেই struggle আমাদের এটা split করতে ঠেলবে। Renaming বিনামূল্যে আমাদের design test করতে থাকে।
C#-এ একই Refactoring
C#-এ mechanics একদম একই। এখানে একটা payroll উদাহরণ, সাথে deprecation step — C# [Obsolete] attribute দিয়ে এটা বিশেষভাবে ভালো হয়।
// BEFORE — "Get" gets... what, exactly?
public class PayrollService
{
public decimal Get(Employee e)
{
var basePay = e.MonthlySalary;
var hra = basePay * 0.40m; // house rent allowance
var pf = basePay * 0.12m; // provident fund deduction
return basePay + hra - pf;
}
}ধাপ ১ — সত্যিকারের method যোগ করো; পুরনোটা delegate করুক আর সতর্ক করুক:
public class PayrollService
{
public decimal CalculateNetMonthlyPay(Employee e)
{
var basePay = e.MonthlySalary;
var hra = basePay * 0.40m;
var pf = basePay * 0.12m;
return basePay + hra - pf;
}
[Obsolete("Use CalculateNetMonthlyPay instead. Will be removed in v3.")]
public decimal Get(Employee e) => CalculateNetMonthlyPay(e);
}এখন Get() call করা প্রতিটা project এখনও compile হয় আর চলে, কিন্তু compiler প্রতিটা call site-এ warning দেখায় — আস্তে আস্তে প্রতিটা caller-কে নতুন নামের দিকে ঠেলে। টিমের build log-এ যখন শূন্য Obsolete warning দেখাবে, Get() delete করো।
// AFTER — final state
public class PayrollService
{
public decimal CalculateNetMonthlyPay(Employee e)
{
var basePay = e.MonthlySalary;
var hra = basePay * 0.40m;
var pf = basePay * 0.12m;
return basePay + hra - pf;
}
}C#-এর বিশেষ সতর্কতা:
- Method যদি কোনো interface member implement করে বা virtual method override করে, interface/base class-এও declaration rename করো — IDE-এর rename স্বয়ংক্রিয়ভাবে পুরো family-তে করে।
- Reflection (
typeof(PayrollService).GetMethod("Get")) আর serializer attribute-এ সতর্ক থাকো — এগুলো পুরনো নাম string হিসেবে ধরে রাখে আর নীরবে ভাঙবে।
Python-এ একটু টেস্ট
Dynamic language-এ একই ওষুধ, একটু বাড়তি সতর্কতা সহ: compiler তোমার জন্য ভাঙা caller-এর তালিকা দেবে না, তাই delegating step আরও বেশি গুরুত্বপূর্ণ। Python-এর warnings module [Obsolete]-এর ভূমিকা পালন করে:
import warnings
class FeePortal:
def calculate_pending_fee(self, student_id: str) -> int:
fees = self.fee_repo.find_by_student(student_id)
return sum(f.amount for f in fees if not f.paid)
def check(self, student_id: str) -> int:
"""Deprecated: use calculate_pending_fee instead."""
warnings.warn(
"check() is deprecated; use calculate_pending_fee()",
DeprecationWarning,
stacklevel=2,
)
return self.calculate_pending_fee(student_id)প্রতিটা পুরনো caller এখনও কাজ করে, কিন্তু test run এখন caller-এর file আর line সহ deprecation warning print করে (stacklevel=2 এটাই করে) — compile time-এর বদলে runtime-এ generate হওয়া তোমার migration to-do list।
Renaming কি সত্যিই ফল দেয়?
এটা শুধু প্রসাধনী মনে হয়, তাই result নিয়ে কথা বলা যাক। "Wrong-use" bug — নাম ভুল জিনিস suggest করায় caller ভুল উদ্দেশ্যে method ব্যবহার করেছে — track করা টিমগুলো সৎ rename-এর পরে এই সংখ্যা ধসে পড়তে দেখে। কারণটা সহজ: যে ভুল বোঝাপড়া bug তৈরি করেছিল সেটা call site-এ আর থাকতে পারে না।
বাকি প্রতি মাসে একটা? সাধারণত string-based caller যেগুলো compiler দেখতে পায় না — তাই উপরের warning box-এর plain-text search অভ্যাস তার মূল্য বজায় রাখে।
IDE Support
Rename হলো সবচেয়ে automated refactoring। Modern editor পুরো gradual dance নিরাপদে একটা keystroke-এ করে দেয়:
| Tool | Shortcut | কী করে |
|---|---|---|
| VS Code | F2 (Rename Symbol) | Symbol আর workspace জুড়ে সব reference rename করে, language server-এর code বোঝার উপর ভিত্তি করে — text matching না। |
| JetBrains IDEs (IntelliJ IDEA, Rider, WebStorm, PyCharm) | Shift+F6 (Rename) | Method আর প্রতিটা usage rename করে; comment, string, আর inheritor-এ related নাম update করার offer দেয়। |
| JetBrains IDEs | Ctrl+F6 (Cmd+F6 on macOS) — Change Signature | পূর্ণ "Change Function Declaration": rename আর এক dialog-এ parameter যোগ/বাদ/reorder। |
| Visual Studio | Ctrl+R, Ctrl+R (Rename) | Solution-wide rename preview সহ, options tick করলে comment ও string সহ। |
IDE scope আর type বোঝে, তাই একই নামের ভিন্ন method-এ হাত দেবে না, আর পুরো polymorphic family একসাথে rename করবে। এজন্যই IDE rename কঠোরভাবে নিরাপদ find-and-replace-এর চেয়ে।
তবুও সীমা মনে রাখো: কোনো IDE সেই নাম follow করতে পারে না যেটা string-এর ভেতরে আছে (reflection, route table, serialized payload, SQL, config file)। One-key rename-এর পরে পুরনো নামটা plain-text search করো। দুই মিনিটের search দুই দিনের debugging বাঁচায়।
সুবিধা ও ঝুঁকি
| সুবিধা | ঝুঁকি / খরচ |
|---|---|
| Call site নিজেই explain করে; reader-দের একটা call বুঝতে method body খুলতে হয় না। | Public API rename করলে external consumer ভাঙে — delegate + deprecate + পরে remove ব্যবহার করতে হবে। |
| সবচেয়ে সস্তা, কখনো stale না হওয়া documentation: নাম প্রতিটা call-এর সাথে ভ্রমণ করে আর compiler check করে। | String-based caller (reflection, serialization, ORM, RPC route) compiler ও IDE rename-এর কাছে অদৃশ্য। |
| Failed naming attempt লুকানো design সমস্যা প্রকাশ করে — দুটো কাজ করা method সৎভাবে নাম দেওয়া যায় না। | বারবার rename ("name churn") git history দূষিত করে আর teammate-দের বিভ্রান্ত করে; ভালো নামে স্থির হও, দোলাও না। |
ভালো searchability: grep "discount" আসলে discount logic খুঁজে পায়। | Polymorphic family-তে শুধু একটা member rename করলে overriding নীরবে ভাঙে — পুরো family rename করো। |
| IDE support দিয়ে প্রায় বিনামূল্যে (F2 / Shift+F6)। | একই commit-এ rename ও logic change মিশিয়ে failure diagnose করা কঠিন — rename বিশুদ্ধ রাখো। |
এটা কোন Smell সারায়?
| Smell | Rename Method কীভাবে সাহায্য করে |
|---|---|
| Mysterious Name | সরাসরি সমাধান: check(), data(), get2() replace করো এমন নামে যেটা intent বলে। |
| Comments | একটা call কী করে তা explain করা comment একবার নাম সেটা বললে অপ্রয়োজনীয় হয়ে যায়। |
| Long Method | পরোক্ষভাবে: কোনো সৎ নাম না মিললে method অনেক বেশি করছে — naming struggle একটা split trigger করে। |
| Speculative Generality | processData() মতো অস্পষ্ট "future-proof" নাম replace করো এমন নামে যেটা code আজকে কী করে সেটা বলে। |
দ্রুত Revision Box
+--------------------------------------------------------------+
| RENAME METHOD — CHEAT SHEET |
+--------------------------------------------------------------+
| Story : Repaint "General Store" board -> "Mobile Recharge" |
| Goal : Name alone explains what the method does |
| 2nd ed. : Part of "Change Function Declaration" (Fowler) |
| |
| SAFE STEPS (gradual rename): |
| 1. Check super/sub classes share the signature |
| 2. New method with good name; move body into it |
| 3. Old method DELEGATES to new one |
| 4. Test. Migrate callers one by one. Test again. |
| 5. Delete old method (or [Obsolete] it if public) |
| |
| IDE : VS Code F2 | JetBrains Shift+F6 | VS Ctrl+R,R |
| Trap : reflection / strings hold old name — text-search! |
| Rule : behaviour identical; name change ONLY |
+--------------------------------------------------------------+Practice Exercise
এখানে একটা ছোট library-management class-এ অসৎ নাম আছে। তোমার কাজ:
class Library {
// 1. What should this be called?
proc(memberId: string): number {
const loans = this.loanRepo.findOverdue(memberId);
return loans.reduce((sum, l) => sum + l.daysLate * 2, 0); // Rs.2 per day
}
// 2. And this one?
go(memberId: string, bookId: string): void {
const book = this.bookRepo.find(bookId);
book.borrowedBy = memberId;
book.dueDate = addDays(new Date(), 14);
this.bookRepo.save(book);
}
// 3. And this? (Careful — does ONE honest name even exist?)
check(memberId: string): boolean {
const fine = this.proc(memberId);
if (fine > 100) {
this.memberRepo.block(memberId); // side effect!
}
return fine <= 100;
}
}কাজগুলো:
- Gradual rename ব্যবহার করে
procআরgorename করো: নতুন method লেখো, পুরনোটা delegate করাও, তারপর চূড়ান্ত অবস্থা দেখাও। প্রতিটা আসলে কী করে? (১-এর hint: এটা overdue fine calculate করে। ২-এর hint: এটা একজন member-কে বই issue করে।) check-এর জন্য আগে একটা সৎ নাম লেখার চেষ্টা করো। দেখবে একটা "and" লাগে — checkFineAndMaybeBlockMember — যেটা red flag। এটাকে একটা pure question আর একটা আলাদা action-এ split করো। (এটাই পাশের refactoring, Separate Query from Modifier।)- ধরো
procpublic আর অন্য টিম ব্যবহার করে। দেখাও কীভাবে TypeScript-এ (JSDoc@deprecated) delete না করে deprecate করবে। - এই class-এ দুটো জায়গা তালিকা করো যেখানে IDE rename-এর পরেও পুরনো নামটার plain-text search এখনও দরকার হবে। (ভাবো: route string? serialized field name? test description?)
- কাগজে
procrename-এর জন্য চিত্র ৭ থেকে three-state diagram sketch করো, আর কাজ ৩-এর পরে তোমার codebase কোথায় থাকবে mark করো।
তোমার চূড়ান্ত caller code যদি সহজ বাক্যের মতো পড়া যায় — overdue fine calculate করো, বই issue করো, member ভালো অবস্থায় আছে কিনা দেখো — তাহলে তুমি সব সাইনবোর্ড সঠিকভাবে রঙ করেছ। জামাল চাচা গর্বিত হতেন, আর রুবেল অবশেষে সঠিক দোকান থেকে তার সাবান পেত।
সচরাচর জিজ্ঞাসা
- নাম বদলানো কি আসলেই একটা refactoring? এটা তো অনেক ছোট কাজ মনে হচ্ছে।
- হ্যাঁ, এটা সবচেয়ে দরকারি refactoring-গুলোর একটা। Refactoring মানে হলো behaviour না বদলে design উন্নত করা — আর একটা সত্যিকারের নাম সেই method-কে call করার প্রতিটা লাইনের design উন্নত করে। Martin Fowler নামকরণকে এত গুরুত্ব দেন যে Refactoring বইয়ের ২য় সংস্করণে renaming-কে Change Function Declaration নামের একটা core refactoring-এর অংশ করেছেন।
- method-এর ভালো নাম কেমন হয়?
- ভালো নাম বলে caller কী পাবে বা কী হবে, সহজ কথায় — সাধারণত verb phrase যেমন calculateMonthlyFee বা sendWelcomeEmail। Fowler-এর একটা সহজ কৌশল হলো: method-টা কী করে সেটা comment হিসেবে লেখো, তারপর সেই comment-টাকেই নাম বানাও। যদি একটা পরিষ্কার নাম খুঁজে না পাও, সম্ভবত method-টা দুটো কাজ করছে — আগে সেটা ভাগ করতে হবে।
- নাম বদলাতে শুধু find-and-replace ব্যবহার করলে কী সমস্যা?
- সাধারণ text find-and-replace বিপজ্জনক। এটা একই নামের অন্য method, string ও comment-এর ভেতরের শব্দ, এবং অসম্পর্কিত code-কেও পরিবর্তন করে ফেলতে পারে। IDE rename (VS Code-এ F2, JetBrains IDE-এ Shift+F6) code structure বোঝে, তাই শুধু সেই exact method-এর real caller-গুলোকেই rename করে। Public API-এর জন্য সবচেয়ে নিরাপদ পথ হলো gradual rename: নতুন method বানাও, পুরনোটা delegate করুক, caller-রা migrate করুক, তারপর পুরনোটা delete করো।
- Compiler যেসব caller দেখতে পায় না, যেমন reflection বা JSON mapping, সেগুলোর কী হবে?
- এটাই সবচেয়ে বড় ফাঁদ। Reflection, serialization, ORM mapping, RPC contract, আর string-based lookup — এগুলো method-এর নাম text হিসেবে ধরে রাখে, তাই নাম বদলালে compiler সতর্ক করবে না। যেকোনো rename-এর পরে পুরো project-এ পুরনো নামটা plain string হিসেবে search করো।
- অন্য টিম বা package ইতিমধ্যে ব্যবহার করছে এমন public method rename করা উচিত?
- এক ধাক্কায় না। একটা public নাম সরিয়ে দিলে যারা সেটার উপর নির্ভর করছে তাদের সবার ক্ষতি হবে। বরং নতুন ভালো-নামের method যোগ করো, পুরনোটা দিয়ে নতুনটাকে delegate করাও, এবং পুরনোটা deprecated mark করো (যেমন C#-এ [Obsolete])। Consumer-দের migrate করার সময় দাও, তারপর পরের major version-এ পুরনো নাম সরাও।
আরো দেখো
সম্পর্কিত পাঠ
Add Parameter: অর্ডার স্লিপে একটা নতুন কলাম
Add Parameter সহজ ভাষায় — কীভাবে method-কে নতুন একটা তথ্য দিতে হয় যেটা সে এখন চাইছে, কেন explicit parameter গ্লোবাল state লুকিয়ে রাখার চেয়ে ভালো, overload দিয়ে নিরাপদে কীভাবে করবে, আর কখন থামতে হবে যাতে parameter list বেশি বড় না হয়।
Remove Parameter: স্কুলের ফর্ম থেকে 'টেলিগ্রাম ঠিকানা' ঘরটা বাদ দাও
Remove Parameter সহজ ভাষায় — method আর যে parameter ব্যবহার করে না সেটা নিরাপদে কীভাবে মুছবে, মরা parameter গুলো পাঠককে কীভাবে বিভ্রান্ত করে আর প্রতিটা caller-কে বাড়তি কাজ করায়, আর মুছে ফেলার আগে কী কী চেক করতে হবে (interface, override, reflection)।
Separate Query from Modifier: জিজ্ঞেস করলেই বিল বাড়ে না তো?
Separate Query from Modifier সহজ ভাষায় — যে method একসাথে question করে আর state বদলায়, সেটাকে ভেঙে দুটো করো: একটা pure query আর একটা আলাদা command। Bertrand Meyer-এর Command-Query Separation (CQS) principle অনুযায়ী: question করলে answer বদলানো উচিত না।
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।