Remove Assignments to Parameters: ধার করা খাতায় কখনো লিখবে না
Remove Assignments to Parameters refactoring শিখো একটা ধার করা খাতার গল্পের মাধ্যমে — TypeScript আর C# উদাহরণ সহ, সহজ ধাপে ধাপে।
ধার করা খাতার গল্প
ধরো সুমাইয়া ক্লাস এইটে পড়ে। জ্বরের কারণে দুইদিন school যেতে পারেনি। তার বেস্ট ফ্রেন্ড তারিক, যে পাশে বসে, শুক্রবার তার science খাতা ধার দিল। "বাসায় নিয়ে যা," তারিক বলল, "notes copy করো, সোমবার ফেরত দিও। আর সুমাইয়া — আমার আব্বু এই খাতা দেখেন, তাই একটু সাবধানে রাখিস।"
সেদিন রাতে সুমাইয়া পড়ার টেবিলে বসল, পাশে এক কাপ চা। এখন ভাবো দুইটা সম্পূর্ণ আলাদা উপায়ে সে এই খাতা ব্যবহার করতে পারে।
প্রথম উপায় — সৎ উপায়। সুমাইয়া তারিকের খাতা বাম দিকে খুলে রাখল। নিজের খাতা ডান দিকে। তার পাতা পড়ে নিজের খাতায় লিখল। তারিকের notes-এ একটা ছোট ভুল পেল — সে লিখেছে শব্দ আলোর চেয়ে দ্রুত চলে, হাস্যকর — সুমাইয়া সেটা নিজের খাতায় ঠিক করল, তারিকেরটায় না। হয়তো margin-এ একটু পেন্সিলে দাগ দিল সোমবার জিজ্ঞেস করবে বলে। সোমবার তারিকের খাতা ফেরত দিল হুবহু যেভাবে পেয়েছিল। আর এই পদ্ধতির একটা চুপচাপ সুবিধা আছে: যদি কখনো সন্দেহ হয় — "circuit diagram টা ঠিকমতো copy করলাম তো?" — তারিকের original খুলে check করা যাবে। Original সবসময় আছে, অক্ষত।
দ্বিতীয় উপায় — অলস আর বিপজ্জনক উপায়। সুমাইয়া ক্লান্ত, নিজের খাতা ব্যাগের কোথাও আছে, আর তারিকের খাতা সামনেই খোলা। তাই সে সরাসরি ওই খাতায় লিখতে শুরু করল। তারিকের লাইন কাটল, নিজের কথা দুই বাক্যের মাঝে গুঁজে দিল, তার diagram-এর উপর "উন্নত" সংস্করণ আঁকল। দশ পাতার মধ্যে খাতাটা এক গোলমেলে খিচুড়ি হয়ে গেল। এটা আর তারিকের notes না, পুরোপুরি সুমাইয়ারটাও না। আর সবচেয়ে খারাপ ব্যাপার: original চলে গেছে। তিন পাতায় যদি ভুল copy হয়, compare করার কিছুই নেই। সোমবার তারিকের মুখ পড়ে গেল, আর তারিকের আব্বুর মুখ আরও বেশি।
সহজ ঘরের নিয়ম যেটা সব দাদি-নানিরা জানেন: ধার পাওয়া জিনিসে কখনো লিখবে না। নিজের কাজ নিজের খাতায় করো।
এই ছবিটা মাথায় রাখো, কারণ code-এর method-গুলো প্রতিদিন ধার করা খাতা পায়। এগুলোকে বলে parameters। Parameter হলো caller যে value method-কে দেয়, ঠিক যেমন তারিক সুমাইয়াকে খাতা দিয়েছিল। তোমার method যখন সেই parameter-এ নতুন value বসাতে শুরু করে — price = price * 0.9 — তখন সে ধার করা খাতায় লিখছে। Original input নষ্ট হয়ে গেল, আর পরে কেউ method পড়লে বুঝতে পারবে না caller আসলে কী পাঠিয়েছিল। Fix টা ঠিক ওই ঘরের নিয়মের মতো: value-টা নিজের local variable-এ copy করো, সব কাজ সেখানে করো। এই fix-এর একটা নাম আছে: Remove Assignments to Parameters।
Remove Assignments to Parameters আসলে কী?
Remove Assignments to Parameters হলো Composing Methods family-র একটা ছোট refactoring। Recipe টা সহজ:
- তুমি এমন একটা method খুঁজে পাচ্ছো যেটা তার নিজের কোনো parameter-এ body-র মাঝে নতুন value বসাচ্ছে।
- তুমি একটা local variable বানাও, parameter-এর value সেখানে copy করো, আর একটা পরিষ্কার নাম দাও।
- এরপর থেকে সব changing আর computing ওই local variable-এ হয়। Parameter-এ আর কোনো লেখালেখি নেই।
Parameter সবসময় যা থাকার কথা তাই থাকে: caller যা দিয়েছে তার একটা বিশ্বস্ত, read-only রেকর্ড। Local variable হয়ে যায় তোমার নিজের খাতা, যেখানে তুমি যা খুশি লিখতে পারো।
এটা এত গুরুত্বপূর্ণ কেন? কারণ ভালো code প্রতিটা name-কে একটাই stable meaning দেয়। Method শুরু হলে price মানে "caller যে price পাঠিয়েছে।" ৭ নম্বর line যদি বলে price = price - 25, তাহলে ৮ নম্বর line থেকে price চুপচাপ অন্য কিছু মানে নিয়ে নিল — "ছাড় দেওয়া price।" একই নাম, দুই মানে, আর switch টা মাঝখানে নিঃশব্দে হয়ে গেল। Method-এর নিচে একজন reader জানে না কোন মানেটা এখন চলছে — সব বুঝতে উপর থেকে প্রতিটা line মাথায় রিপ্লে করতে হবে। ভবিষ্যতের প্রতিটা reader-এর কাছ থেকে এই tax নেওয়া হচ্ছে শুধু একটা variable declaration বাঁচানোর জন্য।
একটা line মনে রাখো: parameter হলো ধার করা খাতা — যত খুশি পড়ো, কিন্তু এতে কখনো লিখবে না। কিছু compute করতে হলে আগে নিজের local variable-এ value copy করো।
আরেকটা ফাঁদ আছে। TypeScript, C#, Java, Python-এর মতো language-এ object "reference value" দিয়ে pass হয়। দুটো সম্পূর্ণ আলাদা কাজ তখন ভীষণ একরকম দেখায়:
order = new Order()— এটা parameter-কে rebind করে। এটা local। Caller কিছুই জানে না।order.cancel()— এটা object-কে mutate করে। Caller অবশ্যই জানে, কারণ এটা caller-এরই object।
তোমার team-এর অভ্যাস যদি হয় "আমরা parameter-এ কখনো assign করি না," তাহলে যে line caller-কে affect করে সেটা অবশ্যই order.cancel()-এর মতো mutation হবে — যেটা review-এ ধরা অনেক সহজ। Parameter assignment সরিয়ে দিলে "caller এটা দেখবে নাকি দেখবে না?" এই গোটা ক্যাটাগরির confusion শেষ।
College corner: এই rebind-বনাম-mutate confusion আসলে parameter passing model-এর প্রশ্ন, যেটা classic exam topic। Strict pass-by-value-এ method value-এর copy পায়, তাই parameter reassign করলে caller কখনো জানবে না। True pass-by-reference-এ (C++ reference, C# ref) parameter মানেই caller-এর variable, তাই reassignment-ও বাইরে দেখা যায়। Java, Python, JavaScript, আর সাধারণ C# parameter ব্যবহার করে তৃতীয় একটা model যেটাকে অনেকে call by sharing বলে: method একটা reference-এর copy পায়। Copy-কে rebind করলে (order = new Order()) শুধু copy বদলায়, caller নিরাপদ; কিন্তু সেই reference দিয়ে mutating method call করলে (order.cancel()) caller-এর actual object-ই বদলে যায়। এই পার্থক্য ঠিকমতো বলতে পারলে বুঝবে কেন Remove Assignments to Parameters এই language-গুলোতে behavior কখনো বদলায় না — এটা শুধু সেই কাজের নাম পরিবর্তন করে যেটা আগে থেকেই local ছিল। এটাই ব্যাখ্যা করে কেন C# ref আর out parameter-এ এই refactoring apply হয় না: সেখানে model সত্যিকারের pass-by-reference, আর parameter-এ লেখাটাই declared contract।
Fowler-এর বই সম্পর্কে একটা কথা। Refactoring-এর প্রথম edition-এ Martin Fowler Remove Assignments to Parameters-কে আলাদা technique হিসেবে list করেছিলেন। দ্বিতীয় edition-এ (২০১৮) তিনি এটাকে Split Variable নামের একটা broader refactoring-এ মিলিয়ে দিয়েছেন। ধারণা একই: যখন একটা নাম দুটো আলাদা meaning বহন করতে বাধ্য হয়, সেটাকে দুটো নামে ভাগ করো। Parameter যেটা working variable হিসেবে reuse হয় সেটাই সবচেয়ে সাধারণ আর ক্ষতিকর case। তাই নতুন বই বা refactoring.com-এ এই exact title না পেলে Split Variable খুঁজো — আমাদের খাতার নিয়ম সেখানে সুখে আছে।
কখন দরকার হয়?
Method body-তে param = something দেখলেই এই refactoring মাথায় আসবে। কিন্তু ব্যথাটা সবচেয়ে বেশি কোন কোন ক্ষেত্রে?
- Long method-এ। Long Method-এ reassigned parameter হলো বিষ। ১২ নম্বর line-এ reassignment হলো, parameter আবার পড়া হলো ৭৮ নম্বর line-এ। ৭৮ নম্বর line পড়ছে যে, সে মনে রাখেনি ৬০ লাইন আগে input overwrite হয়ে গেছে। Long method-টা পরে Extract Method দিয়ে ভাঙতে চাইলে, আগে parameter reassignment ঠিক করলে extraction অনেক সহজ হয়, কারণ প্রতিটা টুকরো একটাই clear meaning-এর value পাবে।
- Debugging session-এ। Method-এর শেষে breakpoint দিয়ে
priceinspect করছো। এটা কি caller-এর price নাকি computed price? Reassigned parameter থাকলে বুঝতে পারবে না, আর original চলে গেছে। আলাদা local থাকলে debugger-এ দুটো value পাশাপাশি দেখা যায় — তারিকের original বাম দিকে, সুমাইয়ার copy ডান দিকে। - অনেক branch-ওয়ালা vague name method-এ। যখন কয়েকটা
ifblock একই parameter বারবার adjust করে, parameter হয়ে যায় running scratchpad। প্রতিটা branch চুপচাপ এর meaning বদলে দেয়।finalPriceবাadjustedScore-এর মতো নামে একটা local-এ ভাগ করলেই method কী বানাচ্ছে সেটা document হয়ে যায়। - Code review confusion-এ। Reviewer যদি বারবার জিজ্ঞেস করে "এটা কি caller যা পাঠিয়েছে সেটা বদলে দিচ্ছে?" — এটাই তোমার sign। Parameter assignment সরিয়ে দিলে উত্তর সবসময় "না" হয়ে যায়।
- Stricter rules-এর জন্য prepare করতে। কিছু team lint rule বা compiler keyword চালু করে (Java-তে
final) যেটা parameter assignment পুরোপুরি নিষিদ্ধ করে। Codebase জুড়ে এই refactoring করাটাই সেই rules চালু করার পথ।
একটা সৎ সতর্কতা: এই refactoring reassignment নিয়ে, mutation নিয়ে না। আসল সমস্যা যদি হয় method caller-এর object বদলে ফেলছে — pass করা list-এ item যোগ করছে, pass করা customer-এর field edit করছে — সেটা আলাদা সমস্যা, আলাদা fix। দুটো ধারণা আলাদা রাখো।
Rebind আর mutate-এর পার্থক্য crystal clear করতে এই table দেখো:
| Code | কী করে | Caller দেখে? | এই refactoring-এর target? |
|---|---|---|---|
order = new Order() | Parameter name-কে নতুন object-এ rebind করে | না — সম্পূর্ণ local | হ্যাঁ — local variable দিয়ে replace করো |
price = price * 0.9 | Parameter-কে নতুন number-এ rebind করে | না — সম্পূর্ণ local | হ্যাঁ — classic scribble |
order.cancel() | Caller-এর object mutate করে | হ্যাঁ — caller-এর object বদলে গেছে | না — আলাদা ব্যাপার |
items.push(gift) | Caller-এর array mutate করে | হ্যাঁ — caller-এর array বড় হয়েছে | না — আলাদাভাবে review করো |
result = 42 (C# out param) | Declared output লেখে | হ্যাঁ — এটাই পুরো contract | না — design-এ মাফ করা |
Before আর After এক নজরে
TypeScript-এ একটা ছোট কিন্তু typical example। একটা function online order-এর delivery charge হিসাব করছে। দেখো charge parameter-এ কীভাবে scribble হচ্ছে:
// BEFORE: the parameter is overwritten — the borrowed notebook is ruined
function deliveryCharge(charge: number, distanceKm: number, isFestival: boolean): number {
if (distanceKm > 20) {
charge = charge + 40; // scribble 1
}
if (isFestival) {
charge = charge * 1.5; // scribble 2
}
if (charge > 200) {
charge = 200; // scribble 3 — cap the charge
}
return charge; // what does "charge" even mean here?
}return-এ এসে charge নামটা তিন মুহূর্তে তিনটা আলাদা জিনিস মানে করেছে। Caller-এর original base charge গেছে। এখন refactored version:
// AFTER: the parameter stays untouched; the local does the work
function deliveryCharge(charge: number, distanceKm: number, isFestival: boolean): number {
let finalCharge = charge; // copy into our own notebook
if (distanceKm > 20) {
finalCharge = finalCharge + 40;
}
if (isFestival) {
finalCharge = finalCharge * 1.5;
}
if (finalCharge > 200) {
finalCharge = 200;
}
return finalCharge; // clearly the computed result
}Behavior-এ কিছুই বদলায়নি। কিন্তু এখন charge মানে প্রতিটা line-এ "caller যে base charge পাঠিয়েছে," আর finalCharge মানে "আমরা যে উত্তর বানাচ্ছি।" দুটো নাম, দুটো মানে, শূন্য confusion। Bug report এলে — "festival charge টা ভুল দেখাচ্ছে" — তুমি charge আর finalCharge দুটোই log করতে পারবে, input আর output পাশাপাশি দেখা যাবে।
Call-টাকে একটা conversation হিসেবে ভাবলেও বোঝা সহজ। Caller একটা value ধার দেয়; method সেটা copy করে, copy-তে কাজ করে, উত্তর ফেরত দেয় — ধার দেওয়া value অক্ষত থাকে:
ধাপে ধাপে, নিরাপদ পথে
এটা পুরো catalog-এর সবচেয়ে gentle refactoring-গুলোর একটা, তবুও আমরা ছোট ছোট verified step-এ করি — ভালো refactoring discipline-এর দাবি এটাই।
কিছু ছোঁয়ার আগে tests run করো আর দেখো সেগুলো green কিনা। Refactoring মানে structure বদলানো behavior না বদলিয়ে, আর "behavior unchanged"-এর একমাত্র সৎ প্রমাণ হলো প্রতিটা ছোট step-এর আগে আর পরে test suite pass করা। Test নেই? আগে এই method-এর জন্য দুয়েকটা quick test লিখো — এই refactoring-এর জন্য তিন-চারটা input/output check-ই যথেষ্ট safety net।
চলো delivery charge example টা এক এক ধাপে করি।
ধাপ ১ — Parameter-এ প্রথম assignment খুঁজে বের করো। Body scan করো। প্রথম scribble হলো charge = charge + 40। এর আগের সব line caller-এর true value দেখে; এর পরে নাও দেখতে পারে।
ধাপ ২ — Parameter থেকে initialize করা একটা local variable declare করো। একদম উপরে রাখো, আর নামটা দাও result-এর জন্য, input-এর জন্য না:
// INTERMEDIATE: local declared, but nothing uses it yet — still compiles, tests still green
function deliveryCharge(charge: number, distanceKm: number, isFestival: boolean): number {
let finalCharge = charge; // new line — harmless so far
if (distanceKm > 20) {
charge = charge + 40;
}
if (isFestival) {
charge = charge * 1.5;
}
if (charge > 200) {
charge = 200;
}
return charge;
}Tests run করো। Green, অবশ্যই — এমন একটা variable যোগ করলাম যেটা কেউ পড়ছে না। এটাই point: প্রতিটা step এত ছোট যে চুপচাপ কিছু ভাঙতে পারে না।
ধাপ ৩ — Parameter-এ প্রতিটা assignment একে একে local-এ বদলাও। প্রথম scribble বদলাও, test run করো। দ্বিতীয়টা বদলাও, test run করো। Read-ও replace করো যাতে chain consistent থাকে:
// INTERMEDIATE: first branch converted, others still pending
function deliveryCharge(charge: number, distanceKm: number, isFestival: boolean): number {
let finalCharge = charge;
if (distanceKm > 20) {
finalCharge = finalCharge + 40; // converted
}
if (isFestival) {
finalCharge = finalCharge * 1.5; // converted
}
if (charge > 200) { // OOPS — still reads the old name!
charge = 200;
}
return charge;
}থামো আর তৃতীয় branch টা দেখো। এটাই classic ভুল: write convert করলাম কিন্তু একটা read এখনো পুরনো parameter point করছে। এখন যদি charge = 150, distanceKm = 25, isFestival = true দিয়ে test run করো, cap check করবে 150 দিয়ে, 285 দিয়ে না — আর একটা ভালো test সেটা সাথে সাথে ধরে ফেলবে। ঠিক এইজন্যই প্রতিটা step-এর মাঝে test চালিয়ে রাখি।
ধাপ ৪ — Conversion শেষ করো যাতে parameter আর কখনো write না হয়, আর copy point-এর পরে read-ও না হয় (local-এর মাধ্যমে ছাড়া):
// FINAL
function deliveryCharge(charge: number, distanceKm: number, isFestival: boolean): number {
let finalCharge = charge;
if (distanceKm > 20) {
finalCharge = finalCharge + 40;
}
if (isFestival) {
finalCharge = finalCharge * 1.5;
}
if (finalCharge > 200) {
finalCharge = 200;
}
return finalCharge;
}Run the whole suite. Green।
ধাপ ৫ — দরজা বন্ধ করো যাতে scribble ফিরে না আসে। Language বা tooling support করলে rule automatic করো। Java-তে parameter-এ final mark করো। JavaScript আর TypeScript project-এ ESLint rule no-param-reassign enable করো। এখন ভবিষ্যতের কোনো teammate charge = ... লিখলে code review শুরু হওয়ার আগেই tools চিৎকার করবে।
এই কাজের মাঝে method কয়েকটা clear state-এর মধ্যে দিয়ে যায়। State গুলোর নাম জানলে মাঝপথে interrupt হলেও বুঝতে পারবে ঠিক কোথায় আছো:
College corner: "এক নাম, এক মানে" কেন এত সঠিক মনে হয়, এর পেছনে compiler theory-র একটা গভীর কারণ আছে। Optimizing compiler আমাদের code আভ্যন্তরীণভাবে SSA form — Static Single Assignment — হিসেবে rewrite করে, যেখানে প্রতিটা variable ঠিক একবার assign হয়, আর source-এর প্রতিটা reassignment নতুন versioned নাম পায় (charge1, charge2, charge3)। Compiler এই খরচ দেয় কারণ নাম কখনো meaning না বদলালে value নিয়ে reasoning অনেক সহজ হয়। Remove Assignments to Parameters apply করতে গিয়ে তুমি আসলে optimizer-এর বদলে human reader-এর জন্য সেই same transformation-এর মানবিক সংস্করণ করছো। এই instinct-ই functional language যেমন Haskell-কে চালায়, যেখানে binding by default immutable, আর mainstream language-এ final/const/readonly keyword-কে চালায়। Immutability fashion না; এটা code reasoning করার সবচেয়ে সস্তা পরিচিত পদ্ধতি।
বাস্তব জীবনের একটু বড় উদাহরণ
চলো তারিকের খাতার গল্পে ফিরে যাই আর পুরো ব্যাপারটা code-এ লিখি। ধরো একটা ছোট study app যেটা সুমাইয়া ব্যবহার করে। একজন বন্ধু তার notes share করেছে, আর app সেখান থেকে তার personal study notes বানাচ্ছে: নিজের heading যোগ করছে, spelling ঠিক করছে, extra points append করছে। এটা এমন কেউ লিখেছে যে ধার করা খাতায় লেখে:
interface NotePage {
topic: string;
lines: string[];
}
// BEFORE: 'sharedPages' is borrowed, but the function reassigns it freely
function buildMyNotes(sharedPages: NotePage[], myExtraPoints: string[], maxPages: number): NotePage[] {
// "Trim" the borrowed notes — the original array reference is overwritten
sharedPages = sharedPages.slice(0, maxPages);
// "Improve" them — overwritten again with a transformed copy
sharedPages = sharedPages.map((page) => ({
topic: page.topic.toUpperCase(),
lines: page.lines.map((line) => line.trim()),
}));
// Append my own points to the last page
if (sharedPages.length > 0 && myExtraPoints.length > 0) {
const last = sharedPages[sharedPages.length - 1];
sharedPages[sharedPages.length - 1] = {
topic: last.topic,
lines: [...last.lines, ...myExtraPoints],
};
}
return sharedPages; // is this the friend's notes or mine? The name lies.
}এই function-টায় চোখ বোলাও। sharedPages নামটা শুরু হয় "বন্ধু যে pages share করেছে" হিসেবে। দুই line পরে মানে "trimmed copy।" আরও তিন line পরে, "trimmed আর cleaned copy।" শেষে মানে "আমার finished personal notes" — কিন্তু বন্ধুর নাম গায়ে এঁটে। তুলনা log করতে হলে — "বন্ধু কী পাঠিয়েছিল বনাম আমি কী বানালাম" — পারবে না, কারণ original reference প্রথম line-েই ছুঁড়ে ফেলা হয়েছে।
এখন সৎ version। ধার করা notes ধারেই থাকে; কাজ হয় clearly named local-এ:
// AFTER: the parameter keeps its meaning; 'myNotes' is our own notebook
function buildMyNotes(sharedPages: NotePage[], myExtraPoints: string[], maxPages: number): NotePage[] {
let myNotes = sharedPages.slice(0, maxPages);
myNotes = myNotes.map((page) => ({
topic: page.topic.toUpperCase(),
lines: page.lines.map((line) => line.trim()),
}));
if (myNotes.length > 0 && myExtraPoints.length > 0) {
const last = myNotes[myNotes.length - 1];
myNotes[myNotes.length - 1] = {
topic: last.topic,
lines: [...last.lines, ...myExtraPoints],
};
}
return myNotes; // honest name: this is my work, built from the shared pages
}আবার গল্প হিসেবে পড়ো। sharedPages হলো তারিকের খাতা — একবার refer করা হলো, আর কখনো ছোঁয়া হলো না। myNotes হলো সুমাইয়ার খাতা — প্রতিটা পরিবর্তন সেখানে। Bug report এলে "app বন্ধুর শেষ page হারিয়ে ফেলছে," তুমি এখন sharedPages.length আর myNotes.length পাশাপাশি print করতে পারবে। Original বেঁচে আছে।
আরেকটা subtle জিনিস লক্ষ করো। আগের version দেখাচ্ছিল যেন বন্ধুর data modify হচ্ছে, কারণ বন্ধুর নামে বারবার assign হচ্ছিল। আসলে slice আর map নতুন array return করে, তাই caller নিরাপদই ছিল — কিন্তু reader-কে সেটা জানতে হতো আর verify করতে হতো। পরের version-এ reader-কে কিছু verify করতে হয় না। Parameter কখনো =-এর বাম দিকে নেই, তাই প্রশ্নটাই ওঠে না। ভালো refactoring শুধু code ঠিক করে না; প্রশ্নের পুরো ক্যাটাগরি মুছে দেয়।
এখানে after-design টা একটা ছোট class diagram হিসেবে — ধার করা input ঢোকে, builder নিজের copy-তে কাজ করে, দুটো কখনো একই জিনিস না:
C#-এ একই refactoring
C#-এও একই রোগ আর চিকিৎসা দেখা যায়। এটা একটা school fee calculator যেটা parameter-এ scribble করছে:
// BEFORE
public decimal MonthlyFee(decimal baseFee, Student student)
{
if (student.HasSiblingInSchool)
baseFee = baseFee * 0.85m; // sibling discount — parameter overwritten
if (student.IsScholarshipHolder)
baseFee = baseFee - 500m; // scholarship — overwritten again
if (baseFee < 0m)
baseFee = 0m; // never charge negative fees
return baseFee; // 'baseFee' no longer means the base fee
}baseFee নামটা method-এর বেশিরভাগ জায়গায় মিথ্যা বলছে। Refactored version:
// AFTER
public decimal MonthlyFee(decimal baseFee, Student student)
{
var payableFee = baseFee; // our own notebook
if (student.HasSiblingInSchool)
payableFee = payableFee * 0.85m;
if (student.IsScholarshipHolder)
payableFee = payableFee - 500m;
if (payableFee < 0m)
payableFee = 0m;
return payableFee;
}C#-এর জন্য দুটো বিষয় এক মিনিট মনোযোগ দেওয়ার মতো:
- C#-এ parameter-এর জন্য
finalkeyword নেই। Java-তেint gamma(final int inputVal)লিখলে compiler reassignment forbid করে। C#-এ ordinary parameter-এর জন্য direct equivalent নেই, তাই discipline enforce হয় team convention আর code analyzer দিয়ে, compiler দিয়ে না। outআরrefparameter মাফ। Method signature যখন বলেvoid TryParse(string text, out int result), তখনresult-এ assign করা smell না — এটাই পুরো contract। Caller explicitly method-কে বলেছে value লিখে দিতে। এই refactoring target করে সাধারণ input parameter যেগুলো চুপচাপ scratch variable হয়ে যায়, যেগুলোর declared কাজ output বহন করা সেগুলো না।
Python-এও নিয়ম একই অভ্যাস: function price পেলে আর computed version দরকার হলে final_price = price লিখে সেটায় কাজ করো। Python-এ constant parameter নেই, তাই convention-ই সব weight বহন করে:
# BEFORE: the parameter is hijacked as a scratch variable
def ticket_total(price, age, is_weekend):
if age < 12:
price = price * 0.5
if is_weekend:
price = price + 50
return round(price)
# AFTER: the parameter keeps its meaning end to end
def ticket_total(price, age, is_weekend):
payable = price
if age < 12:
payable = payable * 0.5
if is_weekend:
payable = payable + 50
return round(payable)এক extra line কি সত্যিই worth it?
কিছু student সঠিক প্রশ্নটা করে: "ভাই, একটাই extra line। সত্যিই কি worth it?" সৎ উত্তর হলো: parameter কতবার reassign হচ্ছে আর method কত লম্বা সেটার উপর নির্ভর করে। দুই line-এর method-এ একবার reassignment হলে mild case; ৬০ line-এর method-এ পাঁচটা branch-এ একই parameter reassign হলে emergency। নিচের quadrant দিয়ে যেকোনো method-কে place করতে পারবে:
আর payoff টা debugging-এ সবচেয়ে স্পষ্ট। Original input parameter-এ বেঁচে থাকলে ভুল value trace করতে দুটো clearly named variable পড়লেই হয়। না থাকলে, পুরো method মাথায় replay করতে হয় ধরে নিতে যে caller কী পাঠিয়েছিল:
এগুলো laboratory-precise সংখ্যা না — এগুলো প্রতিদিনের অভিজ্ঞতা যে "festival charge টা ভুল দেখাচ্ছে" bug বিকেল ৬টায় chase করেছে তার। Pattern টা real: input বাঁচিয়ে রাখলে search অর্ধেক বা তারও কম সময়ে শেষ হয়, কারণ এই ধরনের investigation-এর অর্ধেক সময় যায় শুধু "caller আসলে কী পাঠিয়েছিল?" প্রশ্নের উত্তর খুঁজতে।
IDE support
বেশিরভাগ IDE-তে "Remove Assignments to Parameters" বোতাম নেই, কারণ পরিবর্তনটা মূলত একটা নতুন line লেখা আর কয়েকটা নাম বদলানো। কিন্তু refactoring-এর আশেপাশের tools শক্তিশালী, আর রাখার জন্য এগুলো apply করার চেয়েও বেশি গুরুত্বপূর্ণ:
- IntelliJ IDEA / Android Studio (Java, Kotlin): Assignment to method parameter নামে built-in inspection আছে যেটা project জুড়ে প্রতিটা offending line highlight করে। Extract Variable refactoring দিয়ে fix করা যায়, আর quick-fix parameter-এ
finaldeclare করতে পারে। Inspection severity "Warning"-এ নিয়ে গেলে সবসময় editor gutter-এ smell দেখা যায়। - ESLint (JavaScript / TypeScript):
no-param-reassignrule parameter-এ যেকোনো assignment flag করে।props: trueoption দিলে parameter property mutation-ও flag করে, যেটা আরও strict notebook rule। অনেক popular style guide এই rule চালু করেই ship করে। - Visual Studio / Rider / ReSharper (C#): Introduce Variable (Visual Studio-তে Ctrl+R, V) ব্যবহার করো parameter expression থেকে local বানাতে, তারপর rename refactoring বাকি uses update করবে। Roslyn analyzer পাওয়া যায় যেটা parameter reassignment flag করে।
- সব IDE-তে: humble Rename refactoring হলো ধাপ ৩-এর সবচেয়ে ভালো বন্ধু — local বানানোর পরে rename দিলে guarantee থাকে copy point-এর পরে প্রতিটা read নতুন নাম ব্যবহার করছে, তাই আগে দেখা intermediate step-এর মতো stale read ভুলে পড়ার সুযোগ নেই।
বড় idea: একবার হাতে refactoring করো, তারপর linter বা inspection দিয়ে নিশ্চিত করো কেউ যেন আর কখনো এটা apply করতে না হয়।
সুবিধা আর সীমাবদ্ধতা
| সুবিধা | ঝুঁকি আর সীমা |
|---|---|
| প্রতিটা parameter প্রথম line থেকে শেষ line পর্যন্ত একটাই stable meaning রাখে — "caller যা দিয়েছে" | একটা extra variable declaration যোগ হয়; তিন line-এর method-এ এটা ceremony মনে হতে পারে |
| Original input logging, comparison আর debugging-এর জন্য বেঁচে থাকে | নতুন local-এর নাম ভালো হতে হবে; temp বা p2-এর মতো lazy নাম পুরো effort নষ্ট করে |
| Reference parameter rebind করা (caller অদৃশ্য) আর object mutate করার (caller দৃশ্যমান) মধ্যে confusion দূর হয় | Object mutation fix করে না — method caller-এর object edit করলে সেই সমস্যা থাকে, আলাদা সমাধান লাগবে |
| পরে Extract Method করা সহজ হয়, কারণ প্রতিটা value-এর একটাই clear role | C# out/ref parameter-এ apply হয় না, কারণ সেখানে assignment-ই declared contract |
final (Java) বা no-param-reassign (ESLint) দিয়ে permanently lock করা যায়, অভ্যাসকে guarantee-তে পরিণত করে | বিরল performance-critical numeric kernel-এ team কখনো কখনো ইচ্ছাকৃতভাবে parameter reuse করে — করলে loudly document করো |
ঝুঁকির column সত্যিই বেশ mild — এটা সবচেয়ে নিরাপদ refactoring-গুলোর একটা। Steps follow করলে method-এর behavior বদলানোর সুযোগ নেই, কারণ তুমি শুধু নতুন নাম introduce করছো এমন কাজের জন্য যেটা আগে থেকেই local ছিল। Real-world চ্যালেঞ্জ হলো consistency: scribblers-এ ভরা codebase-এ একটা পরিষ্কার method একটু সাহায্য করে; পুরো project জুড়ে lint rule অনেক বেশি সাহায্য করে।
কোন code smell ঠিক করে?
| Smell | এই refactoring কীভাবে সাহায্য করে |
|---|---|
| Long Method | লম্বা body-তে reassigned parameter হলো noise-এ লুকানো meaning-switch। Parameter + named local-এ ভাগ করলে long method পড়া যায় আর Extract Method-এর জন্য ready হয়, কারণ প্রতিটা extracted piece single, clear meaning-এর value পাবে |
| Comments | // price is now the discounted price-এর মতো comment শুধু এমন নামের জন্য ক্ষমা চাওয়া যেটার meaning বদলে গেছে। discountedPrice-এর মতো properly named local comment-টাকে unnecessary করে দেয় |
| Duplicate Code | Parameter-এ original input বেঁচে থাকলে, "যেমন pass হয়েছিল তেমন value" দরকার এমন helper logic সরাসরি সেটা পড়তে পারে — প্রতিটা caller-কে original re-derive বা re-pass না করে, যেটা ছোট ছোট duplication তৈরি করে |
এটা headline act-এর চেয়ে supporting refactoring বেশি: এটা খুব কমই এককভাবে পুরো smell fix করে, কিন্তু মাঠ পরিষ্কার করে দেয় যাতে বড় refactoring — Extract Method, Replace Method with Method Object, Substitute Algorithm — নিরাপদে apply করা যায়।
Quick revision box
+------------------------------------------------------------------+
| REMOVE ASSIGNMENTS TO PARAMETERS — REVISION CARD |
+------------------------------------------------------------------+
| Story : Arjun's borrowed notebook — read it, never write |
| in it; do your work in YOUR notebook (a local). |
| Smell : param = something inside the method body. |
| Fix : let result = param; then change only 'result'. |
| Why : one name = one meaning; original input survives; |
| no "did the caller see that?" confusion. |
| Steps : green tests -> add local copy -> convert one |
| assignment at a time -> tests after each -> lock |
| with final / no-param-reassign. |
| Excused : C# out/ref params — writing them IS the contract. |
| Naming : Fowler 2nd ed. folds this into "Split Variable". |
| Not for : object mutation — that is a different problem. |
+------------------------------------------------------------------+Practice করো
এবার নিজের খাতায় লেখার পালা। একটা movie ticket app-এর এই TypeScript function নাও আর নিজে refactor করো:
function ticketPrice(price: number, age: number, isWeekend: boolean, couponPercent: number): number {
if (age < 12) {
price = price * 0.5; // child discount
} else if (age >= 60) {
price = price * 0.7; // senior discount
}
if (isWeekend) {
price = price + 50; // weekend surcharge
}
if (couponPercent > 0) {
price = price - (price * couponPercent) / 100;
}
if (price < 0) {
price = 0;
}
return Math.round(price);
}তোমার কাজ:
- আগে চার-পাঁচটা quick test লিখো। কমপক্ষে cover করো: weekend-এ একটা শিশু, coupon সহ একজন senior, কোনো extra ছাড়া একজন adult, আর এত বড় coupon যেটা price শূন্যের নিচে নিয়ে যায়। Run করো — green।
- Remove Assignments to Parameters ছোট step-এ apply করো: ভালো নামে একটা local introduce করো (
payablePriceনাকিfinalPrice— নামটা নিয়ে একটু ভাবো), একটা একটা করে assignment convert করো, প্রতিটা conversion-এর পরে test run করো। - ইচ্ছাকৃতভাবে intermediate step-এর ভুলটা করো — write convert করো কিন্তু একটা
priceread-ও রেখে দাও — আর দেখো কোন test সেটা ধরে ফেলে। Test একটা real slip ধরতে দেখলে safety net-এ বিশ্বাস আসে সবচেয়ে ভালো। - Refactoring শেষ করো, সব test run করো, তারপর ESLint rule
no-param-reassignproject-এ যোগ করো যাতে scribble আর কখনো ফিরতে না পারে। - Bonus চিন্তার প্রশ্ন:
priceparameter আর তোমার বানানো local দুটোই এখন আছে। "Customer-কে X টাকা charge করা হয়েছে" বলা log line-এ কোনটা print করবে? "এই show-এর base price ছিল X" বলা log line-এ কোনটা? দুটো উত্তর সাথে সাথে মাথায় এলে, পুরো lesson বুঝে গেছো। - College corner challenge: এই viva question-এর এক paragraph উত্তর লেখো — "Java pass-by-value, তবুও একটা method আমার পাঠানো list empty করে দিতে পারে। ব্যাখ্যা করো কীভাবে দুটো statement একসাথে সত্য।" তোমার উত্তরে যদি reference-এর copy, rebinding, আর mutation শব্দগুলো সঠিকভাবে থাকে, তাহলে এই question-এর যেকোনো interview version-এর জন্য তুমি ready।
খাতা ফেরত দাও যেভাবে পেয়েছিলে। তোমার ভবিষ্যতের teammates — আর ভবিষ্যতের তুমি — কৃতজ্ঞ থাকবে।
সচরাচর জিজ্ঞাসা
- Parameter-এ নতুন value assign করা কি error?
- না, বেশিরভাগ language-এ এটা allowed আর code চলেও যায়। এটা readability-র সমস্যা, compiler error না। Assignment করার পরে parameter আর caller-এর পাঠানো value রাখে না — reader confused হয়ে যায়। আমরা refactor করি যাতে প্রতিটা name-এর একটাই মানে থাকে।
- এই refactoring কি caller কী দেখে তা বদলে দেয়?
- না। TypeScript, C#, Java, Python-এর মতো language-এ method-এর ভেতরে parameter reassign করা সেই method-এর মধ্যেই সীমাবদ্ধ। এই refactoring শুধু method body-র ভেতরে নাম পরিবর্তন করে, তাই behavior একদম একই থাকে। তোমার tests আগেও pass করবে, পরেও করবে।
- Parameter reassign করা আর object mutate করার মধ্যে পার্থক্য কী?
- Reassign মানে parameter name-কে নতুন value-তে point করানো, যেমন order = newOrder — caller এটা দেখে না। Mutate মানে object-টাকেই বদলে ফেলা, যেমন order.cancel() — caller এটা ঠিকই দেখে। এই refactoring শুধু reassignment সরায়; mutation আলাদা ব্যাপার।
- Fowler-এর দ্বিতীয় edition-এ এই refactoring-এর কী হলো?
- Refactoring-এর ২য় edition-এ Martin Fowler এটাকে Split Variable নামের একটা বড় technique-এর মধ্যে ঢুকিয়ে দিয়েছেন। ধারণাটা একই: যখন একটা name দুটো meaning বহন করতে বাধ্য হয়, তখন সেটাকে দুটো নামে ভাগ করো। Parameter-কে scratch variable হিসেবে ব্যবহার করাটাই সবচেয়ে সাধারণ আর সবচেয়ে ক্ষতিকর case।
- C#-এর out আর ref parameter-এ কি এটা apply হয়?
- না। out আর ref-এর ক্ষেত্রে parameter-এ value assign করাটাই contract-এর মূল কাজ — caller expect করে যে method একটা value লিখে দেবে। এই refactoring শুধু সাধারণ input parameter-কে target করে, যেগুলো চুপচাপ overwrite হয়ে নিজের আসল meaning হারিয়ে ফেলে।
আরো দেখো
সম্পর্কিত পাঠ
Split Temporary Variable: একটা বালতি দুই কাজ করতে পারে না
দুই বালতির গল্প দিয়ে Split Temporary Variable শেখো — TypeScript ও C# উদাহরণ আর নিরাপদ ধাপ সহ। প্রতিটা variable-কে একটাই কাজ আর একটাই সৎ নাম দাও।
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।
Replace Method with Method Object: বড় রান্নার জন্য আলাদা স্টেশন বানাও
Replace Method with Method Object শেখো বিয়ের রান্নাঘরের গল্প দিয়ে — TypeScript ও C# উদাহরণ আর নিরাপদ ধাপ-ধাপ পদ্ধতি দিয়ে, একদম শুরু থেকে।
Substitute Algorithm: স্কুলে যাওয়ার নতুন সোজা রাস্তা
Substitute Algorithm রিফ্যাক্টরিং শেখো সাইকেলের রুটের গল্প দিয়ে — TypeScript আর Python উদাহরণ সহ, আর টেস্ট-ফার্স্ট নিরাপত্তার নিয়ম যেটা সব শিক্ষার্থীর জানা দরকার।