Extract Variable: ছোট ছোট নামওয়ালা ধাপে বড় হিসাব সমাধান করো
Extract Variable শেখো ধাপে ধাপে। একটা বিশাল, জটিল expression কে ছোট ছোট নামওয়ালা অংশে ভাগ করো — ঠিক যেভাবে গণিতের খাতায় কাজ দেখাও।
বড় গণিতের হিসাব আর রাফ পেপার ✏️
সোমবার সকালে ৮ম শ্রেণির ক্লাসে গণিত পিরিয়ড চলছে। স্যার বোর্ডে একটা ভয়ঙ্কর অঙ্ক লিখলেন: "একটা দোকানে একটা স্কুলব্যাগ ৮৫০ টাকায় বিক্রি হয় ১২% ছাড়ে, আর একটা পানির বোতল ২৪০ টাকায় ৫% ছাড়ে, তারপর মোট দামের উপর ১৮% ভ্যাট যোগ হয়। চূড়ান্ত বিল কত?"
দুজন ছাত্র দুভাবে সমাধান করল।
রহিম হিরো হতে চায়। সে প্রথমে শেষ করতে চায় আর সবার সামনে দেখাতে চায়। সে তার খাতায় একটা বিশাল লাইন লিখল: ৮৫০ - ৮৫০×১২/১০০ + ২৪০ - ২৪০×৫/১০০ + (৮৫০ - ৮৫০×১২/১০০ + ২৪০ - ২৪০×৫/১০০)×১৮/১০০ = ? ওই লাইনটা ভালো করে দেখো। একই ছাড়ের হিসাব দুইবার এসেছে, কারণ ভ্যাটের জন্য আবার সেই মোটটা দরকার। হিসাব করতে করতে রহিম গুলিয়ে ফেলল কোন ব্র্যাকেট কোথায় যাবে। সে দ্বিতীয়বার ছাড়ের হিসাবে ভুল করল — ১২-এর জায়গায় ১৫ লিখল — ভুল উত্তর পেল, এবং কোথায় ভুল হলো সেটাও খুঁজে পেল না, কারণ পুরো হিসাবটা একটা জটিল দড়ির গোলার মতো। স্যার লাল কলমে বৃত্ত দিলেন। রহিম নিজের লাইনের দিকে তাকিয়ে সত্যি বলতে আর পড়তেই পারল না।
করিম দুই বেঞ্চ দূরে বসে স্যার যা সবসময় বলেন তাই করল: রাফ পেপারে ছোট ছোট ধাপে কাজ দেখাও। রাফ পেপারে সে লিখল:
- ছাড়ের পর ব্যাগের দাম = ৮৫০ − ১০২ = ৭৪৮
- ছাড়ের পর বোতলের দাম = ২৪০ − ১২ = ২২৮
- ভ্যাটের আগে মোট = ৭৪৮ + ২২৮ = ৯৭৬
- ভ্যাট = ৯৭৬-এর ১৮% = ১৭৫.৬৮
- চূড়ান্ত বিল = ৯৭৬ + ১৭৫.৬৮ = ১১৫১.৬৮
প্রতিটা লাইনে একটা ছোট নাম আর একটা ছোট কাজ। কোনো লাইনে ভুল হলে মুহূর্তেই ধরা যায়। স্যার তার চিন্তার ধারা বুঝতে পারেন এবং প্রতিটা সঠিক ধাপের জন্য নম্বর দিতে পারেন, শেষ লাইনে ভুল হলেও। এমনকি পরের সপ্তাহের করিমও এটা পড়ে বুঝতে পারবে। আর লক্ষ্য করো: করিম কোনো হিসাব দুইবার করেনি। "ভ্যাটের আগে মোট" একবার লিখে ভ্যাটের লাইনেও আর চূড়ান্ত লাইনেও ব্যবহার করা হয়েছে। রহিমের দুইবার কপি করার ভুল করিমের পদ্ধতিতে সম্ভবই না।
কোডেও রহিম আর করিম আছে। কিছু function-এ থাকে একটাই বিশাল expression — ব্র্যাকেটের ভেতর ব্র্যাকেট, গণিতের ভেতর condition — যেটা কেউ পড়তে পারে না। Extract Variable refactoring রহিমের একলাইনের দানবকে করিমের পরিষ্কার ধাপে রূপান্তরিত করে। আর আমাদের সোনালি কথাটা মনে রাখো: refactoring মানে কোডের বাইরের আচরণ না বদলে ভেতরের উন্নতি করা। চূড়ান্ত বিল ১১৫১.৬৮ টাকাই থাকবে; শুধু পাঠযোগ্যতা বদলায়। করিমের রাফ পেপার মাথায় রাখো — নিচের প্রতিটা অংশ হলো সেই পেপারই, কোডে রূপান্তরিত।
Extract Variable কী?
সহজ সংজ্ঞা:
একটা জটিল expression-এর একটা অর্থপূর্ণ অংশ নাও। সেই অংশটাকে একটা নতুন local variable-এ রাখো। variable-টাকে এমন নাম দাও যেটা বলে value-টার মানে কী। বড় expression-এর ওই অংশটা variable দিয়ে বদলে দাও। বড় expression টা একটা বাক্যের মতো না পড়া পর্যন্ত এটা চালিয়ে যাও।
বড় লাইনটা কয়েকটা ছোট লাইন হয়ে যায়, আর প্রতিটা ছোট লাইনে একটা লেবেল থাকে — ঠিক করিমের রাফ কাজের মতো।
এক লাইনে মনে রাখার কৌশল: Extract Variable = একটা জটিল sub-expression-কে নাম দাও, গণিতের রাফ কাজের ধাপের মতো। কম্পিউটার একই উত্তর দেয়; মানুষ দশগুণ দ্রুত পড়তে পারে।
নামের ব্যাপারে একটা কথা। Martin Fowler-এর Refactoring ১ম সংস্করণে (১৯৯৯) এই কৌশলকে বলা হতো Introduce Explaining Variable — সুন্দর একটা নাম, কারণ variable-এর একমাত্র কাজ হলো ব্যাখ্যা করা। ২য় সংস্করণে (২০১৮) নাম বদলে হয় Extract Variable, Extract Function-এর পরিবারের সাথে মিল রাখতে। এর inverse-এরও নাম বদলেছে: Inline Temp (১ম সংস্করণ) হয়েছে Inline Variable (২য় সংস্করণ)। তাই কোনো বইয়ে "explaining variable" লেখা থাকলে, কোনো ওয়েবসাইটে "Extract Variable" বললে, আর IDE-তে "Introduce Variable" দেখালে — চিন্তা করো না, সবগুলো একই বন্ধুসুলভ refactoring।
Fowler extract করার আগে একটা তীক্ষ্ণ প্রশ্ন জিজ্ঞেস করতে বলেন: এই নামটা কি শুধু এই function-এর ভেতরে অর্থপূর্ণ, নাকি বড় context-এও? যদি মানেটা সম্পূর্ণ local হয়, তাহলে variable নিখুঁত। যদি অন্য function-গুলোরও এই ধারণাটা দরকার হয়, তাহলে সেটাকে function হিসেবে extract করাই ভালো — সেই পরবর্তী ধাপটা সামনে আসবে।
পুরো বিষয়টা, একটা revision map-এ:
কখন দরকার হয়? 🔍
এই লক্ষণগুলো দেখলে সাবধান। প্রতিটাই আলাদা পোশাকে রহিমের বিশাল লাইন।
- একটা expression অনেক কিছু বলার চেষ্টা করছে। গণিত, condition, আর method chain একটা লাইনে ঠেসে দেওয়া। কী সিদ্ধান্ত নেওয়া হচ্ছে সেটা বুঝতে মাথায় operator precedence সমাধান করতে হচ্ছে। এরকম ঘন লাইনগুলো Long Method smell-এর একটা বড় কারণ — long method মানে প্রায়ই উচ্চতায় না, প্রশস্ততায় লম্বা।
- একটা formula ব্যাখ্যা করতে comment লিখেছ।
// ছাড়ের পর দামমতো একটা comment একটা bare expression-এর উপরে থাকলে সেটা Comments smell ফিসফিস করছে: আমাকে বরং একটা নাম দাও।priceAfterDiscountনামের একটা variable কখনো পুরোনো হয় না; comment পুরোনো হয়ে যেতে পারে। - জটিল
ifcondition।if (platform.includes("Mac") && browser.includes("IE") && resize > 0)পড়তে চোখ কুঁচকাতে হয়। প্রতিটা অংশকে নামের boolean-এ বের করো আরif-টা একটা ইংরেজি বাক্যের মতো পড়া যাবে। (পুরো conditional-এর জন্য এটা করাটা হলো cousin refactoring Decompose Conditional।) - একটা calculation-এর মাঝখানটা debug করা যাচ্ছে না। Breakpoint আর watch window variable-এর সাথে লাগে। যখন সব কিছু একটা expression-এ থাকে, debugger-কে জিজ্ঞেস করতে পারো না "surcharge কত ছিল?" কারণ surcharge-এর কোনো নাম নেই। Extract করার পরে প্রতিটা ধাপ পরীক্ষা করা যায় — করিমের রাফ কাজের প্রতিটা লাইন যাচাই করা যায়।
- একই sub-expression একটা expression-এ দুইবার আছে। একবার extract করো, variable দুইবার ব্যবহার করো — লাইনের ভেতরের duplication-ও শেষ হয়ে যায়। এটাই রহিমের ফাঁদ ছিল: সে ছাড়ের হিসাব দুইবার কপি করল আর দুটো কপি আলাদা হয়ে গেল।
একটা সাধারণ দানব লাইনের ভেতরে কী লুকিয়ে থাকে? একটা কেটে দেখলে সাধারণত কয়েকটা আলাদা ব্যবসায়িক ধারণা একসাথে চাপা পড়ে থাকে:
প্রতিটা অংশ তার নিজের নামওয়ালা লাইন পাওয়ার যোগ্য। ছাড়ের হিসাব হয় bagPriceAfterDiscount। ভ্যাটের নিয়ম হয় taxableAmount আর TAX_MULTIPLIER। ডেলিভারির নিয়ম হয় qualifiesForFreeDelivery। বাল্ক অফার হয় bulkDiscount। চারটা অংশ, চারটা নামওয়ালা ধাপ, একটা পাঠযোগ্য return লাইন।
আরেকটা সুন্দর সুবিধাও আছে: Extract Variable হলো একটা stepping stone। নামওয়ালা variable-গুলো একটা block-এর input আর output দৃশ্যমান করে দেওয়ার পরে, প্রায়ই বুঝতে পারো পুরো block-টাই Extract Method-এর মাধ্যমে তার নিজের method হতে পারে। আগে ধাপগুলো নামকরণ করো, তারপর হয়তো ধাপগুলো promote করো।
কলেজের কোণ: ঘন expression কেন এত কষ্টকর? Cognitive science-এর পরিষ্কার উত্তর আছে: working memory। মানুষ একসাথে মোটামুটি চার থেকে সাতটা "chunk" তথ্য ধরে রাখতে পারে (Miller-এর 7±2, পরে Cowan আরো কমিয়ে চারটায় নামিয়ে এনেছেন)। (a - a*d/100 + (b - b*e/100)) * (1 + t/100) parse করতে operand, precedence, আর bracket depth একসাথে track করতে হয় — সহজেই ডজনখানেক mental item। একটা নামওয়ালা variable expression-এর পুরো একটা উপ-বৃক্ষকে একটাই chunk-এ পরিণত করে: totalBeforeTax। এই কারণেই আধুনিক static-analysis tool শুধু লাইন গণনা নয়, cognitive complexity (SonarSource-এর metric) আর expression nesting depth পরিমাপ করে — আর কেন style guide প্রতিটা condition-এ boolean operator সীমিত রাখে। Extract Variable হলো সেই metric কমানোর সরাসরি, যান্ত্রিক উপায়: প্রতিটা extraction পাঠকের mental parse tree থেকে এক স্তর nesting সরিয়ে দেয়।
একনজরে আগে এবং পরে
একটা দোকানের billing code, রহিম-স্টাইলে আর তারপর করিম-স্টাইলে।
// BEFORE: one expression trying to say everything
function finalBill(bag: Item, bottle: Item): number {
return (
(bag.price - (bag.price * bag.discountPct) / 100 +
(bottle.price - (bottle.price * bottle.discountPct) / 100)) *
(1 + 18 / 100)
);
}// AFTER: rough-paper steps with names
function finalBill(bag: Item, bottle: Item): number {
const bagPriceAfterDiscount =
bag.price - (bag.price * bag.discountPct) / 100;
const bottlePriceAfterDiscount =
bottle.price - (bottle.price * bottle.discountPct) / 100;
const totalBeforeTax = bagPriceAfterDiscount + bottlePriceAfterDiscount;
const TAX_RATE = 0.18;
return totalBeforeTax * (1 + TAX_RATE);
}পরের version-এ লাইন বেশি — আর এটা ঠিকই আছে! আমরা "সবচেয়ে ছোট কোড জেতে" খেলছি না। আমরা খেলছি "সবচেয়ে দ্রুত সৎভাবে বোঝা যায় সেটা জেতে।" প্রতিটা const হলো করিমের রাফ কাজের একটা লাইন।
এই refactoring-এর ছন্দ, এক লাইনের arrow-এ:
আর এইখানেই ছাত্রছাত্রীরা মুগ্ধ হয়ে যায়: extraction কোডটাকে ধাপে ধাপে যাচাইযোগ্য করে তোলে — ঠিক যেভাবে স্যার করিমের খাতা দেখেন। প্রতিটা নামওয়ালা লাইন পরের লাইনে যাওয়ার আগে নিজে থেকেই নিশ্চিত করা যায়:
"Meena"-কে তোমার function দিয়ে আর "Teacher"-কে তোমার debugger-এর watch window দিয়ে বদলে দাও, আর এই diagram হুবহু দেখাচ্ছে তুমি সারাজীবন billing bug কীভাবে সমাধান করবে।
ধাপে ধাপে, নিরাপদ উপায়ে 🪜
একটা extraction ধীরে ধীরে করা যাক, পেশাদারভাবে, প্রতিটা পর্যায়ে কোড দেখিয়ে। শুরুর বিন্দু:
function deliveryCharge(order: Order): number {
return order.weightKg * 12 +
(order.city === "metro" ? 0 : 40) +
(order.total > 999 ? 0 : 30);
}এই লাইনে তিনটা ধারণা লুকিয়ে আছে: একটা ওজন চার্জ, একটা বাইরের শহরের surcharge, আর একটা ছোট অর্ডারের ফি। একটা একটা করে নামকরণ করি।
ধাপ ১ — Check করো sub-expression-এ কোনো side effect নেই। order.weightKg * 12 মূল্যায়ন করা কিছু বদলাতে পারবে না — কোনো printing নেই, কোনো saving নেই, কোনো counter বাড়ানো নেই। শুধু pure calculation। (যদি side effect থাকত, তাহলে extract করলে কখন effect হয় সেটা বদলে যেত, আর তখন আমরা আচরণ বদলাতাম — আর refactoring থাকত না।) আমারটা pure। নিরাপদ।
ধাপ ২ — একটা নতুন immutable variable declare করো আর sub-expression-টা সেখানে assign করো। TypeScript-এ const ব্যবহার করো (Java-তে final, যেখানে পাওয়া যায় readonly)। Immutable, কারণ একটা explaining variable একটা তথ্য হওয়া উচিত, পরে বদলানো কিছু না। করিম রাফ কাজের কোনো লাইন কেটে নতুন মান লেখে না।
function deliveryCharge(order: Order): number {
const weightCharge = order.weightKg * 12; // new line
return order.weightKg * 12 + // original untouched so far
(order.city === "metro" ? 0 : 40) +
(order.total > 999 ? 0 : 30);
}ধাপ ৩ — value-টার মানে কী সেটার উপর ভিত্তি করে নাম দাও, কীভাবে হিসাব হচ্ছে তার উপর না। weightCharge ✔। weightTimesTwelve ✘ — ওই নামটা শুধু formula আবার বলছে, কিছু ব্যাখ্যা করছে না। নামের মান যাচাইয়ের একটা দ্রুত পরীক্ষা:
| প্রার্থী নাম | রায় | কেন |
|---|---|---|
weightCharge | ভালো | টাকাটা কীসের জন্য সেটা বলছে |
weightTimesTwelve | খারাপ | Formula আবার বলছে, কিছু ব্যাখ্যা করছে না |
wc | খারাপ | টাইপ কম করে, বোঝাপড়া কমায় |
temp1 | ভয়ঙ্কর | এমন নাম যেটা কিছুই নাম দেয় না |
ধাপ ৪ — বড় expression-এর সেই occurrence-টা variable দিয়ে বদলাও।
function deliveryCharge(order: Order): number {
const weightCharge = order.weightKg * 12;
return weightCharge +
(order.city === "metro" ? 0 : 40) +
(order.total > 999 ? 0 : 30);
}ধাপ ৫ — Compile করো আর test চালাও। প্রতিটা অর্ডারের চার্জ ঠিক আগের মতোই হতে হবে। সবুজ? চালিয়ে যাও।
ধাপ ৬ — যদি একই sub-expression একাধিকবার থাকে, সব কপি বদলাও। আমারটা একবার আছে, কিন্তু যখন একটা formula একটা expression-এর ভেতরে বারবার আসে, এই ধাপটা duplication-ও দূর করে — এক ঢিলে দুই পাখি। শুধু কিছু কপি বদলানো হলো চিরন্তন অসতর্ক ভুল; বাকি কপি আর variable তখন আলাদা হয়ে যায়, রহিম-স্টাইলে।
ধাপ ৭ — পরের জটিল অংশের জন্য আবার করো। আরো দুই রাউন্ড, প্রতিটার পরে test:
function deliveryCharge(order: Order): number {
const weightCharge = order.weightKg * 12;
const outstationSurcharge = order.city === "metro" ? 0 : 40;
const smallOrderFee = order.total > 999 ? 0 : 30;
return weightCharge + outstationSurcharge + smallOrderFee;
}ওই return লাইনটা জোরে পড়ো: "delivery charge হলো weight charge যোগ outstation surcharge যোগ small order fee।" এটা এখন একটা বাক্য। আর debugger-এ তিনটা মান আলাদাভাবে দেখা যাবে — যখন কোনো কাস্টমার ভুল চার্জের অভিযোগ করবে, তুমি মুহূর্তেই দেখতে পাবে কোন ধাপে ভুল হয়েছে।
প্রতিটা extraction-এর পরে test চালাও, শেষে না। Extract Variable দেখতে এতটাই সহজ মনে হয় যে কিছু ভাঙার সম্ভাবনা নেই — আর ঠিক এই মুহূর্তেই মানুষ অসতর্ক হয়। দুটো চিরন্তন ভুল হলো side effect আছে এমন expression extract করা, আর বারবার আসা expression-এর শুধু কিছু occurrence বদলানো। ছোট ধাপ আর test দুটোই তাৎক্ষণিকভাবে ধরে ফেলে।
একটা extraction-এর জীবন, state হিসেবে আঁকা। লক্ষ্য করো side-effect check-টাই শুরুতে দরোয়ান:
একটা বড় বাস্তব উদাহরণ: স্কুল শপের বিল 🏪
এখন গণিত পিরিয়ডের গল্পটা পুরোপুরি কোডে করা যাক। স্কুল শপের একটা billing function আছে রহিম-স্টাইলে লেখা। এতে ছাড়, বাল্ক অফার, ভ্যাট, আর ফ্রি ডেলিভারি — সব একটানে।
interface ShopOrder {
bagPrice: number;
bagDiscountPct: number;
bottlePrice: number;
bottleDiscountPct: number;
quantitySets: number;
isSchoolMember: boolean;
}
// BEFORE: Raju's one-line monster
function schoolKitBill(o: ShopOrder): number {
return (
((o.bagPrice - (o.bagPrice * o.bagDiscountPct) / 100 +
(o.bottlePrice - (o.bottlePrice * o.bottleDiscountPct) / 100)) *
o.quantitySets -
(o.quantitySets >= 5 ? 100 : 0)) *
1.18 +
((o.bagPrice - (o.bagPrice * o.bagDiscountPct) / 100 +
(o.bottlePrice - (o.bottlePrice * o.bottleDiscountPct) / 100)) *
o.quantitySets >
2000 || o.isSchoolMember
? 0
: 60)
);
}সৎভাবে বলো: ওটার দিকে কতক্ষণ তাকিয়ে থাকলে? সবচেয়ে খারাপ অংশটা লক্ষ্য করো — ছাড়-সহ set-এর দাম দুইবার হিসাব হচ্ছে (একবার বিলের জন্য, একবার ফ্রি-ডেলিভারি চেকের জন্য), আর দুটো কপি ভবিষ্যতের edit-এ চুপচাপ আলাদা হয়ে যেতে পারে। এটাই রহিম তার bracket ভুল হুবহু কপি করছে।
ডেটা আর চারপাশের function দেখতে বাইরে থেকে সহজ — জঞ্জালটা পুরোটাই expression-এর ভেতরে:
এখন করিম খেলি। একটা একটা নামওয়ালা ধাপ extract করি, প্রতিটার পরে test।
রাউন্ড ১ — ছাড়ের পর দাম নামকরণ:
const bagPriceAfterDiscount =
o.bagPrice - (o.bagPrice * o.bagDiscountPct) / 100;
const bottlePriceAfterDiscount =
o.bottlePrice - (o.bottlePrice * o.bottleDiscountPct) / 100;রাউন্ড ২ — set-এর দাম নামকরণ এবং বারবার আসা expression-এর দুটো কপিই বদলানো:
const pricePerSet = bagPriceAfterDiscount + bottlePriceAfterDiscount;
const orderValue = pricePerSet * o.quantitySets;রাউন্ড ৩ — অফার, ভ্যাট, আর ডেলিভারির সিদ্ধান্ত নামকরণ। চূড়ান্ত ফলাফল:
// AFTER: Meena's rough work, as code
function schoolKitBill(o: ShopOrder): number {
const bagPriceAfterDiscount =
o.bagPrice - (o.bagPrice * o.bagDiscountPct) / 100;
const bottlePriceAfterDiscount =
o.bottlePrice - (o.bottlePrice * o.bottleDiscountPct) / 100;
const pricePerSet = bagPriceAfterDiscount + bottlePriceAfterDiscount;
const orderValue = pricePerSet * o.quantitySets;
const bulkDiscount = o.quantitySets >= 5 ? 100 : 0;
const taxableAmount = orderValue - bulkDiscount;
const TAX_MULTIPLIER = 1.18;
const billWithTax = taxableAmount * TAX_MULTIPLIER;
const qualifiesForFreeDelivery = orderValue > 2000 || o.isSchoolMember;
const deliveryFee = qualifiesForFreeDelivery ? 0 : 60;
return billWithTax + deliveryFee;
}স্যার দুটো খাতা তুলনা করার মতো দুটো version তুলনা করো। duplicate computation চলে গেছে — orderValue একবার হিসাব হয়ে দুইবার ব্যবহার হচ্ছে। ফ্রি-ডেলিভারির নিয়ম, যেটা bracket-এ চাপা পড়েছিল, এখন পড়া যাচ্ছে qualifiesForFreeDelivery = orderValue > 2000 || o.isSchoolMember হিসেবে — একটা আসল স্কুল নীতি যেটা দোকানদারকে পড়ে শোনানো যায়। আর যদি কোনো অভিভাবক বিল নিয়ে প্রশ্ন করেন, যেকোনো লাইনে breakpoint দিয়ে taxableAmount বা deliveryFee সরাসরি দেখা যাবে। সাশ্রয় হওয়া সময় কল্পনার না:
আরেকটা পর্যবেক্ষণ, তোমার বাড়তে থাকা refactor-sense-এর জন্য: ব্যাগ আর বোতলের ছাড়ের লাইনগুলো একটু একইরকম দেখাচ্ছে। নামওয়ালা variable-গুলো একটা shared pattern প্রকাশ করেছে — একটা priceAfterDiscount(price, pct) helper থাকার দাবি রাখে। এই explaining variable-কে reusable function-এ promote করাটাই হলো Extract Variable থেকে Extract Method আর Replace Temp with Query-এ যাওয়ার stepping stone।
Python আর C#-এ একই Refactoring
একই ওষুধ সব ভাষায় কাজ করে। Python version, সংক্ষিপ্ত আর মিষ্টি:
# BEFORE
def shipping_cost(order):
return (
order.weight_kg * 1.5
+ (5.0 if order.region == "remote" else 0.0)
+ (0.0 if order.subtotal > 100 else order.subtotal * 0.02)
)
# AFTER
def shipping_cost(order):
base_rate = order.weight_kg * 1.5
remote_surcharge = 5.0 if order.region == "remote" else 0.0
handling_fee = 0.0 if order.subtotal > 100 else order.subtotal * 0.02
return base_rate + remote_surcharge + handling_feeআগে শুধু অনুমান করা যেত weight_kg * 1.5 মানে কী। পরে কোড নিজেই বলছে: একটা base rate। return লাইনটা তিনটা সৎ নামের যোগফল হয়ে গেল।
C# developers-ও একই কাজ করেন local variable দিয়ে — আর একটা জটিল if হয়ে যায় ইংরেজি বাক্য:
// BEFORE: squint and pray
if ((order.Total - order.Discount) * 1.18 > customer.CreditLimit
&& !customer.IsBlocked && order.Items.Count > 0)
{
RejectOrder(order);
}
// AFTER: a policy you can read aloud
decimal payableAmount = (order.Total - order.Discount) * 1.18m;
bool exceedsCreditLimit = payableAmount > customer.CreditLimit;
bool isActiveCustomer = !customer.IsBlocked;
bool hasItems = order.Items.Count > 0;
if (exceedsCreditLimit && isActiveCustomer && hasItems)
{
RejectOrder(order);
}পরের condition জোরে পড়ো: "বিল credit limit ছাড়িয়ে গেলে, আর customer active থাকলে, আর অর্ডারে item থাকলে — reject করো।" তিনটা নামওয়ালা boolean bracket-এর ঝোলাকে একটা ব্যবসায়িক নিয়মে রূপান্তরিত করল।
কলেজের কোণ: এই extra temporary variable কি program-কে ধীর করে? প্রায় কখনো না — আর compiler-ই কারণ। আধুনিক compiler কোডকে অভ্যন্তরীণভাবে SSA form (static single assignment)-এ রূপান্তরিত করে, যেখানে প্রতিটা মধ্যবর্তী মান এমনিতেই নিজের virtual নাম পায়; তোমার const temporary-গুলো শুধু optimiser যা করতে যাচ্ছিল তার সাথে মিলে যায়। Register allocation তারপর সেই মানগুলো CPU register-এ map করে, তাই একটা স্বল্পস্থায়ী local সাধারণত memory-ই স্পর্শ করে না। Compiler এমনকি উল্টো কৌশলও স্বয়ংক্রিয়ভাবে করে — common subexpression elimination (CSE) বারবার আসা expression খুঁজে বের করে (যেমন রহিমের দ্বিগুণ ছাড়ের হিসাব) এবং একবার হিসাব করে। তাই machine-ও করিম আর ইরেজার উভয়ই খেলে: সব কিছু অভ্যন্তরীণভাবে নামকরণ করে আর duplicate সরিয়ে দেয়। Compiled বা JIT-compiled কোডে একটা explaining variable-এর performance খরচ কার্যত শূন্য; পাঠযোগ্যতার লাভ তোমার কাছেই থাকে। Dynamic language-এর জন্য একটা সত্যিকার সতর্কতা: একটা hot loop-এ লাইনে লাইনে interpreted হলে, লক্ষ লক্ষ redundant allocation গুরুত্বপূর্ণ হতে পারে — কিন্তু সবসময়ের মতো, optimize করার আগে পরিমাপ করো।
IDE Support ⚙️
Editor-গুলো এই refactoring খুব ভালোভাবে automate করে — এমনকি expression-এর সব duplicate occurrence খুঁজে বের করে প্রতিটা বদলানোর প্রস্তাব দেয়।
| IDE | Variable extract করার পদ্ধতি | Shortcut |
|---|---|---|
| IntelliJ IDEA / Rider / অন্যান্য JetBrains IDE | Expression select করো → Refactor → Extract/Introduce → Variable | Ctrl+Alt+V (Mac-এ Cmd+Option+V); constant: Ctrl+Alt+C |
| VS Code | Expression select করো → Refactor → "Extract to constant" | Ctrl+Shift+R (Refactor menu) বা Ctrl+. (Quick Fix) |
| Visual Studio | Expression select করো → Quick Actions → "Introduce local" | Ctrl+. তারপর Introduce local বেছে নাও |
| ReSharper in Visual Studio | Expression select করো → Refactor This → Introduce Variable | Ctrl+Shift+R তারপর Introduce Variable বেছে নাও |
JetBrains IDE একই expression-এর অন্য সব occurrence highlight করে জিজ্ঞেস করবে "Replace all 3 occurrences?" — হ্যাঁ বলো, আর একটা keystroke-এ duplication উধাও। উল্টো operation, Inline Variable, একই menu-তে আছে (JetBrains: variable-এ Ctrl+Alt+N), যেদিন কোনো নাম আর মূল্য যোগ না করে সেদিনের জন্য।
Extract করব নাকি রেখে দেব? সিদ্ধান্ত নেওয়া 🤔
প্রতিটা sub-expression নাম পাওয়ার যোগ্য না। total + tax এমনিতেই একটা বাক্য; আগে এটাকে grandTotal নামকরণ করা শুধু আসবাব। দুটো প্রশ্ন প্রায় প্রতিটা ক্ষেত্রে সমাধান করে দেয়: অংশটা পড়তে কতটা কঠিন? আর কতবার আসে?
উপরে-ডানে হলো jackpot: পড়তে কঠিন এবং বারবার আসে — extract করলে এক ঘায়ে confusion আর duplication দুটোই দূর হয়, ঠিক orderValue-এর মতো। নিচে-ডানে তাও লাভ হয়: একবার-মাত্র-আসা কঠিন expression-ও শুধু ব্যাখ্যার শক্তির জন্য নাম পাওয়ার যোগ্য। উপরে-বামে dedupe tool হিসেবে নাম পায়। নিচে-বামে — স্পষ্ট আর একবার-মাত্র — রেখে দাও, আর মনে রাখো inverse refactoring ঠিক এই জন্যই আছে, যেসব নাম আর কাজে আসছে না তাদের জন্য।
সুবিধা এবং ঝুঁকি ⚖️
| বিষয় | |
|---|---|
| ✅ | প্রতিটা sub-expression একটা নাম পায় — কোড নিজেই তার অর্থ ব্যাখ্যা করে। |
| ✅ | মধ্যবর্তী মানগুলো debugger-এ breakpoint আর watch-এর মাধ্যমে দৃশ্যমান হয়। |
| ✅ | একটা জটিল if condition নামওয়ালা boolean-এর পাঠযোগ্য বাক্য হয়ে যায়। |
| ✅ | একটা লাইনের ভেতরে duplicate sub-expression একটা variable-এ collapse হয়। |
| ✅ | Extract Method আর Replace Temp with Query-র দিকে যাওয়ার স্বাভাবিক প্রথম ধাপ। |
| ⚠️ | নামটা local — শুধু এই function-এর ভেতরেই ব্যাখ্যা করে। একই ধারণা অন্য জায়গাতেও দরকার হলে Replace Temp with Query পছন্দ করো যাতে নাম আর logic শেয়ার হয়। |
| ⚠️ | কখনো side effect আছে এমন expression extract করবে না; মূল্যায়নের ক্রম আর সংখ্যা বদলাতে পারে, আর সেটা আচরণ বদলায়। |
| ⚠️ | অতিরিক্ত extraction-এ method temp1 আর result2 মতো অলস নামে ভরে যায় — লাইন যোগ হয়, মানে না। শুধু যেখানে নাম সত্যিকার অর্থে শেখায় সেখানে extract করো। |
কখন ব্যবহার করবে না: expression এমনিতেই স্পষ্ট, ছোট বাক্য হলে (total + tax), variable যোগ করা শুধু আসবাব। আর এই দোল আবার এলো, Extract আর Inline Method-এর মতো একই ভারসাম্য: Extract Variable-এর সরাসরি inverse হলো Inline Temp (২য় সংস্করণের নাম: Inline Variable)। যখন কোনো variable-এর নাম তার expression-এর চেয়ে বেশি কিছু বলে না, সেটা ভেতরে ভাঁজ করে দাও। যেসব নাম শেখায় সেগুলো যোগ করো; যেসব নাম শুধু প্রতিধ্বনি করে সেগুলো সরাও। দোলনাটা সেখানেই বিশ্রাম নেয় যেখানে পড়া সবচেয়ে সহজ। করিম ঠিক যতটুকু প্রয়োজন ততটুকু রাফ কাজের লাইন লেখে — কখনো একটা কম না, কখনো পাঁচটা বেশি না।
কোন Smell-গুলো সারায়?
| Smell | Extract Variable কীভাবে সাহায্য করে |
|---|---|
| Long Method | ঘন, চওড়া লাইনগুলো untangle করে যেগুলো method-কে ক্লান্তিকর করে তোলে; প্রায়ই Extract Method-এর জন্য প্রস্তুত chunk প্রকাশ করে। |
| Comments | একটা formula ব্যাখ্যাকারী comment variable-এর নাম হয়ে যায়; comment মুছে ফেলা হয়। |
| Duplicate Code (expression-এর ভেতরে) | একটা calculation-এর ভেতরে বারবার আসা sub-expression একবার হিসাব হয়ে নামের মাধ্যমে reuse হয়। |
| Complex conditional logic | নামওয়ালা boolean bracket-এর ঝোলা condition-কে পাঠযোগ্য নীতিতে রূপান্তরিত করে; দেখো Decompose Conditional। |
দ্রুত Revision Box
+--------------------------------------------------------------+
| EXTRACT VARIABLE — QUICK REVISION |
+--------------------------------------------------------------+
| WHAT : Give a confusing sub-expression its own well-named |
| local variable. Like rough-work steps in maths. |
| NAMES : 1st ed: Introduce Explaining Variable |
| 2nd ed: Extract Variable |
| WHEN : Dense expressions, formula comments, tangled ifs, |
| undebuggable middles, repeated sub-expressions. |
| STEPS : 1. Check NO side effects |
| 2. Declare const with a MEANING-based name |
| 3. Replace every occurrence |
| 4. TEST after each extraction |
| 5. Repeat until it reads like a sentence |
| INVERSE: Inline Temp / Inline Variable |
| RULE : Name the WHY of the value, not the HOW. |
+--------------------------------------------------------------+অনুশীলন 🏏
তোমার পালা, করিম-স্টাইলে! এই function একটা সিনেমার টিকিটের দাম একটানে ঠিক করে। return লাইনটা বাক্যের মতো না পড়া পর্যন্ত variable extract করো। const ব্যবহার করো, মানে অনুযায়ী নামকরণ করো, আর প্রতিটা ধাপের পরে কল্পনায় test চালাও।
function ticketPrice(show: Show, viewer: Viewer): number {
return (
(show.is3D ? 250 : 180) +
(show.day === "Sat" || show.day === "Sun" ? 50 : 0) -
(viewer.age < 12 || viewer.age >= 60 ? 40 : 0) +
(show.seatRow <= 3 ? -30 : 0)
);
}Hint: এখানে চারটা ধারণা লুকিয়ে আছে — একটা base price, একটা weekend surcharge, একটা বয়স ছাড়, আর একটা সামনের সারির discount। লক্ষ্য রাখো চূড়ান্ত লাইনটা যেন return basePrice + weekendSurcharge - ageConcession + frontRowAdjustment; মতো হয়। তারপর স্যারের মতো নিজেকে grade করো: প্রতিটা লাইন কি আলাদাভাবে check করা যায়? কোনো formula কি দুইবার আছে? কোনো debugger কি প্রতিটা ধাপ watch করতে পারবে? Bonus প্রশ্ন: তোমার নতুন variable-গুলোর কোনটাকে shared function-এ promote করতে চাইবে যদি app-এর অন্য অংশেও সেটা দরকার হয়? (ফিসফিস করে বলি: সেটাই Replace Temp with Query তোমার জন্য অপেক্ষা করছে।) অনুশীলন চালিয়ে যাও — ছোট ছোট ধাপ স্পষ্টভাবে নামকরণ করা programming-এর সবচেয়ে মূল্যবান অভ্যাসগুলোর একটা, আর এটার খরচ শুধু একটু সৎ চিন্তার মুহূর্ত।
সচরাচর জিজ্ঞাসা
- Extract Variable refactoring সহজ ভাষায় কী?
- Extract Variable মানে হলো একটা বড়, জটিল expression-এর একটা অংশ নিয়ে সেটাকে একটা ভালো নামের local variable-এ রাখা। শেষের লাইনটা তখন জটিল সংখ্যার জঞ্জালের বদলে পরিষ্কার নামের একটা বাক্যের মতো পড়া যায়।
- Fowler-এর বইয়ের প্রথম সংস্করণে Extract Variable-কে কী বলা হতো?
- ১ম সংস্করণে (১৯৯৯) এটাকে বলা হতো Introduce Explaining Variable — সুন্দর একটা নাম, কারণ variable-এর একমাত্র কাজ হলো ব্যাখ্যা করা। ২য় সংস্করণে (২০১৮) এটার নাম বদলে হয় Extract Variable। এর inverse হলো Inline Variable, যেটা প্রথম সংস্করণে Inline Temp নামে পরিচিত ছিল।
- বের করা variable-এর নাম কীভাবে দেবো?
- নামটা দাও value-টা কী মানে সেটার উপর ভিত্তি করে, কীভাবে হিসাব হচ্ছে সেটার উপর না। base_fare একটা ভালো নাম; weight_times_rate একটা খারাপ নাম, কারণ এটা formula-টাই আবার বলে, উদ্দেশ্য ব্যাখ্যা করে না।
- বের করা expression-এ কোনো side effect থাকলে কেন সমস্যা?
- Extract করলে expression কখন এবং কতবার মূল্যায়ন হবে সেটা বদলে যায়। যদি মূল্যায়ন করলে program-এর অবস্থা বদলায় — যেমন message পাঠানো বা counter বাড়ানো — তাহলে সেটা সরালে আচরণ বদলে যেতে পারে, এবং তখন এটা আর নিরাপদ refactoring থাকে না।
- Extract Variable-এর চেয়ে Extract Method কখন ভালো?
- একটা variable-এর নাম শুধু তার নিজের function-এর ভেতরেই কাজে আসে। যদি একই expression অন্য জায়গাতেও দরকার হয়, তাহলে সেটাকে method বা query-তে extract করো — তাহলে নাম আর logic সব জায়গায় ব্যবহার করা যাবে।
আরো দেখো
সম্পর্কিত পাঠ
Inline Temp: একবারই ব্যবহার করা রাফ নোটটা ছুঁড়ে ফেলো
Inline Temp রিফ্যাক্টরিং শেখো একটা মজার রাফ পেপারের গল্প দিয়ে — TypeScript আর C# উদাহরণ, নিরাপদ ধাপ, IDE shortcut, আর কখন variable inline করা উচিত না সেটাসহ।
Replace Temp with Query: তাজা জিজ্ঞেস করো, বাসি চিরকুটে ভরসা করো না
ক্যান্টিনের সিঙ্গারার গল্প দিয়ে Replace Temp with Query বোঝো — TypeScript আর C# উদাহরণ, নিরাপদ ধাপ, আর একটাই সত্যের উৎস।
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।
Decompose Conditional: জটিল if-কে সহজ নামে ভেঙে ফেলো
Decompose Conditional refactoring শেখো স্কুলের নোটিশের গল্প দিয়ে — সহজ TypeScript ও C# উদাহরণ, নিরাপদ ধাপ, আর IDE shortcut সহ।