Consolidate Duplicate Conditional Fragments: মিষ্টির কাউন্টারটা বাইরে নিয়ে যাও
ক্যান্টিনের গল্প দিয়ে Consolidate Duplicate Conditional Fragments refactoring শেখো — TypeScript আর C# example, safety rules, আর সহজ step-by-step practice।
ক্যান্টিনের মিষ্টির কাউন্টারের গল্প
ধরো তোমার স্কুলের ক্যান্টিনে দুপুরে খাওয়ার সময় দুইটা লাইন আছে। সবাইকে একটা বেছে নিতে হবে।
ভেজ লাইন: রুটি কাউন্টার, তারপর ডাল কাউন্টার, তারপর পনির কাউন্টার, আর সবশেষে — একটা মিষ্টির কাউন্টার যেখানে রসগোল্লা দেওয়া হয়। সুমাইয়া রোজ এই লাইনে যায়।
নন-ভেজ লাইন: রুটি কাউন্টার, তারপর চিকেন কারি কাউন্টার, আর সবশেষে — একটা মিষ্টির কাউন্টার যেখানে রসগোল্লা দেওয়া হয়। তার বন্ধু তারিক ওই লাইনে যায়।
একটু থামো। আবার পড়ো। দুটো লাইনই মিষ্টির কাউন্টারে শেষ হচ্ছে। একই মিষ্টি। দুইটা কাউন্টার, দুইজন স্টাফ, দুইটা ট্রেতে রসগোল্লা — দশ মিটার দূরে দূরে একটাই কাজ করছে।
স্কুলের কর্তৃপক্ষ ব্যাপারটা খেয়াল করল। তারা দেখল দুটো আলাদা লাইনে দুইজন স্টাফ হুবহু একই রসগোল্লা দিয়ে যাচ্ছে। সমাধানটা বলার সাথে সাথেই বোঝা যায়: মিষ্টির ব্যাপারটা ভেজ বা নন-ভেজ choice-এর সাথে সম্পর্কিত না। যে লাইনেই যাক, সবাই রসগোল্লা পাবে। তাহলে মিষ্টির কাউন্টার লাইনের ভেতরে কেন?
সপ্তাহান্তে ক্যান্টিন নতুন করে সাজানো হলো। ভেজ লাইন: রুটি, ডাল, পনির। নন-ভেজ লাইন: রুটি, চিকেন। তারপর দুই লাইন মিলে যায়, আর সেখানে — মেলার পয়েন্টের পরে — দাঁড়িয়ে আছে একটাই মিষ্টির কাউন্টার। একটা ট্রে। একজন স্টাফ, যে এখন সবাইকে সার্ভ করছে। কারো খাবার প্লেটে কোনো পরিবর্তন হয়নি। শুধু layout বদলেছে — shared step টা split-এর বাইরে চলে গেছে।
আর একটা real bug-ও ঠিক হলো। গত মাসে ক্যান্টিন সোমবার মিষ্টি বদলে খিচুড়ি দেওয়া শুরু করল — কিন্তু শুধু ভেজ লাইনের কাউন্টার জানল। তিনটা সোমবার তারিক রসগোল্লা পেল আর সুমাইয়া খিচুড়ি পেল, কেউ বুঝতে পারল না কেন। একটাই কাউন্টার হওয়ার পর এরকম half-update আর সম্ভব না। জানানোর জন্য একটাই জায়গা আছে।
Code-এও ঠিক এটাই হয়। একটা if/else flow-কে দুই ভাগ করে, আর কোথাও না কোথাও একই line দুই branch-এই copy-paste হয়ে যায় — একটা log statement, একটা save call, একটা counter update। সেই line হলো দুই লাইনের ভেতরে দাঁড়িয়ে থাকা মিষ্টির কাউন্টার। এটাকে বাইরে নিয়ে আসার refactoring-টার নাম Consolidate Duplicate Conditional Fragments।
Consolidate Duplicate Conditional Fragments কী?
Consolidate Duplicate Conditional Fragments হলো একটা refactoring — যেখানে তুমি conditional-এর প্রতিটা branch-এ হুবহু একই code খুঁজে বের করো আর সেটাকে conditional-এর বাইরে নিয়ে যাও, যাতে একবার লেখা হয় আর একবার চলে।
placement-এর নিয়মটা খুব সহজ:
- প্রতিটা branch-এর শুরুতে duplicate থাকলে conditional-এর আগে নিয়ে যাও।
- প্রতিটা branch-এর শেষে duplicate থাকলে conditional-এর পরে নিয়ে যাও।
- Branch-এর মাঝখানে আটকে থাকলে আগে প্রতিটা branch-এর শুরু বা শেষে slide করো (যদি behavior না বদলায়), তারপর বাইরে নিয়ে যাও।
Martin Fowler-এর classic উদাহরণ হলো একটা pricing snippet: special deal হলে total একভাবে, normal হলে অন্যভাবে — আর দুটো branch-ই শেষে send() call করে। send() deal-এর উপর নির্ভর করে না একটুও — তাই সেটা if/else-এর নিচে চলে যায় আর একবারই থাকে।
মূল কথাটা হলো: conditional-এ শুধু সেটাই থাকা উচিত যেটা paths-এর মধ্যে আসলেই আলাদা। সব branch-এ যেটা একই, সেটা decision-এর অংশ না — সেটা শুধু ভুল জায়গায় দাঁড়িয়ে আছে, আর এই duplication হলো Duplicate Code smell-এর একটা রূপ, if-এর ভেতরে লুকিয়ে আছে।
একটু deeper যাই: compiler-রা ঠিক এই কাজটাই তোমার হয়ে করে, আর compiler literature-এ এর নির্দিষ্ট নাম আছে। shared computation উপরে তোলাকে বলে code hoisting; নিচে নামানোকে বলে code sinking। optimiser control flow graph (CFG)-এর উপর কাজ করে — প্রতিটা straight-line statement-এর গুচ্ছ একটা basic block, conditional একটা split তৈরি করে, আর branches যেখানে আবার মেলে সেটা হলো join block। একটা statement join block-এ sink হতে পারে শুধু তখনই যদি সেই block উভয় branch-কে post-dominate করে — মানে প্রতিটা path-কে সেখান দিয়েই যেতে হবে। এটাই আমাদের "no early exits" rule-এর formal version — একটা branch-এ return থাকলে post-dominance ভেঙে যায়, তাই move টা illegal।
একটা line মনে রাখো: প্রতিটা branch যদি কাজটা করে, তাহলে conditional-এর ভেতরে সেটা থাকা উচিত না। শুরুতে shared থাকলে if-এর আগে উঠবে, শেষে shared থাকলে if-এর পরে নামবে। Branch-এ শুধু সত্যিকারের পার্থক্য থাকবে।
কখন দরকার হয়?
এই signals দেখলেই বুঝবে:
- প্রতিটা branch-এর শেষ line একই। একটা
recordDispatch(order), একটাsaveToDb(), একটাconsole.log(...)—ifআরelseদুটোতেই বন্ধ হচ্ছে। এটাই সবচেয়ে common রূপ — trailing duplication বাড়ে কারণ প্রতিটা programmer নিজের branch "সম্পূর্ণভাবে" শেষ করে অন্য branch না দেখে। - প্রতিটা branch-এর প্রথম line একই। দুটো branch-ই একই setting পড়ে বা একই subtotal হিসাব করে শুরু হয়। সেই কাজটা decision-এর আগে।
- Branch দেখতে লম্বা কিন্তু মাত্র এক-দুই line আলাদা। চোখ একটু কুঁচকে দেখো conditional-টা। যদি branches ৮০ শতাংশ একই হয়, shared অংশ বের করলে আসল পার্থক্যটা দেখা যাবে — আর তখন ছোট হওয়া conditional টা ternary হয়ে যেতে পারে বা Decompose Conditional-এর যোগ্য হয়।
- এক branch-এ bug ঠিক হয়েছে, অন্যটায় হয়নি। তোমার bug tracker যদি দেখায় "priority orders-এ ঠিক হয়েছে, standard orders-এ এখনো ভাঙা" — এটাই duplicate fragments আলাদা হয়ে যাওয়া, ঠিক সোমবারের খিচুড়ির মতো।
switch-এর প্রতিটা case একইভাবে শেষ হচ্ছে। একই নিয়মswitch-এও প্রযোজ্য — সব case-এর shared statement switch-এর পরে চলে যাবে।
এই refactoring Long Method-ও কমায়: repeated lines সরালে method ছোট হয় আর আসল logic পরিষ্কার দেখা যায়।
কখন করবে না:
- কোনো branch আগে বেরিয়ে যায়। কোনো branch যদি trailing fragment-এর আগে
returnবাthrowকরে, সেই fragment আসলে সত্যিকারের common না — সরালে কখন চলবে সেটা বদলে যাবে। - Fragment গুলো শুধু দেখতে একই। এক branch-এ
total += fee(order)আর অন্যটায়total += fee(otherOrder)— এগুলো একই পোশাক পরা আলাদা statement। একসাথে করলে bug হবে। - Order গুরুত্বপূর্ণ। কোনো branch যদি fragment যে state পড়ে সেটা বদলায়, fragment উপরে তুললে কাজের sequence বদলে যাবে। সরানোর আগে verify করো।
দুইটা প্রশ্ন urgency ঠিক করে দেয়: copies কতটা identical, আর সেই code কত প্রায়ই বদলায়? প্রায়ই-বদলানো code-এ identical copies মানে half-update bug আসছে।
আগে আর পরে এক নজরে
TypeScript-এ একটা shipping function, classic trailing duplication সহ:
interface Order {
isPriority: boolean;
totalCost: number;
carrier: string;
}
// BEFORE: recordDispatch is copied into both branches
function ship(order: Order): void {
if (order.isPriority) {
order.totalCost += expressFee(order);
order.carrier = pickFastCarrier(order);
recordDispatch(order);
} else {
order.totalCost += standardFee(order);
order.carrier = pickCheapCarrier(order);
recordDispatch(order);
}
}recordDispatch(order) যে branch-ই নাও, চলবেই। এটাই মিষ্টির কাউন্টার। সেটাকে split-এর নিচে নিয়ে যাও:
// AFTER: the conditional holds only what truly differs
function ship(order: Order): void {
if (order.isPriority) {
order.totalCost += expressFee(order);
order.carrier = pickFastCarrier(order);
} else {
order.totalCost += standardFee(order);
order.carrier = pickCheapCarrier(order);
}
recordDispatch(order);
}এক line কমেছে, হ্যাঁ — কিন্তু আসল লাভ হলো meaning। conditional এখন একটাই প্রশ্নের উত্তর দেয়: priority আর standard shipping-এ কী আলাদা? Fee আর carrier। ব্যস। dispatch recording স্পষ্টতই universal, আর এটা কখনো এক branch-এ update হয়ে অন্যটায় ভুলে যাওয়া সম্ভব না।
আগের ship function-এ গুনে দেখো: branches-এর ভেতরে ছয়টা statement-এর মধ্যে দুটো copy। conditional body-র এক তৃতীয়াংশ আসলে কখনো decision-এর অংশ ছিলই না।
নিরাপদ ধাপে ধাপে করার পদ্ধতি
move টা সহজ দেখায়, কিন্তু সতর্ক programmer-রা একটা checklist follow করে, কারণ trap গুলো (early exits, hidden ordering) চুপচাপ থাকে। একটু জটিল example দিয়ে দেখাই — শুরুতে আর শেষে দুটোই duplication আছে:
// STARTING POINT: leading AND trailing duplication
function prepareReport(term: Term): Report {
let report: Report;
if (term.isFinal) {
const header = schoolHeader(); // duplicated first line
report = buildFinalReport(term, header);
stampDate(report); // duplicated last line
} else {
const header = schoolHeader(); // duplicated first line
report = buildMidTermReport(term, header);
stampDate(report); // duplicated last line
}
return report;
}ধাপ ১ — fragments চিহ্নিত করো। প্রতিটা branch পড়ো আর কোন lines identical, কোথায় আছে সেটা mark করো। এখানে: const header = schoolHeader() দুটো branch-এর শুরুতে, stampDate(report) শেষে। লিখে রাখো — দরকার হলে কাগজে। "duplicate" ভুল চেনাটাই বেশিরভাগ ভুলের কারণ।
ধাপ ২ — safety conditions চেক করো।
- leading fragment কি branch-এ তার আগে কম্পিউট হওয়া কিছুর উপর নির্ভর করে? না — এটাই প্রথম line, branch-এর ভেতরে এর আগে কিছু চলে না। উপরে তুলতে পারবে।
- trailing fragment-এর আগে কোনো branch কি আগেভাগে বেরিয়ে যায়? কোনো branch body-তে
return/throwনেই। নিচে নামাতে পারবে। - দুটো copy কি একই variable-এ একই meaning নিয়ে কাজ করছে? হ্যাঁ — একই
header, একইreport। সত্যিকারের identical।
ধাপ ৩ — leading fragment-কে conditional-এর আগে তুলে আনো। একটাই move:
// Step 3: INTERMEDIATE — leading fragment lifted, trailing still duplicated
function prepareReport(term: Term): Report {
const header = schoolHeader();
let report: Report;
if (term.isFinal) {
report = buildFinalReport(term, header);
stampDate(report);
} else {
report = buildMidTermReport(term, header);
stampDate(report);
}
return report;
}Test চালাও। সব green? তাহলে এগোও।
ধাপ ৪ — trailing fragment-কে conditional-এর পরে নামাও।
// Step 4: FINAL — each fragment written once, branches show only the difference
function prepareReport(term: Term): Report {
const header = schoolHeader();
let report: Report;
if (term.isFinal) {
report = buildFinalReport(term, header);
} else {
report = buildMidTermReport(term, header);
}
stampDate(report);
return report;
}আবার test চালাও।
ধাপ ৫ — ছোট হওয়া conditional পুনরায় দেখো। এখন প্রতিটা branch একটাই assignment, তাই পুরো if/else একটা ternary হতে পারে:
// Step 5: BONUS polish — the decision is now one readable line
function prepareReport(term: Term): Report {
const header = schoolHeader();
const report = term.isFinal
? buildFinalReport(term, header)
: buildMidTermReport(term, header);
stampDate(report);
return report;
}এই chain টা typical: shared lines সরালে branches ছোট হয়, ছোট branches আরেকটা simplification-এর সুযোগ দেয়। Refactoring-রা দলবেঁধে আসে।
প্রতিটা hoist-এর পরে test চালাও — আর early exits সবচেয়ে বেশি নজর রাখো। Classic ভুল: এক branch-এর মাঝখানে return আছে, তুমি trailing fragment নিচে নামালে, আর এখন এমন path-এ চলছে যেখানে আগে চলত না। Test চালালে এটা তৎক্ষণাৎ ধরা পড়ে যদি প্রতিটা ধাপে চালাও। পাঁচটা move একসাথে করে একবার test দিলে, failure বলবে না কোন move টা ভেঙেছে।
আরেকটু deeper যাই: "order matters" safety check আসলে data dependence-এর প্রশ্ন। দুটো statement জায়গা বদলাতে পারবে শুধু তখনই যদি কেউ কারো লেখা variable পড়ে না বা কেউ কারো পড়া variable লেখে না। মাঝের fragment-কে branch-এর edge-এ slide করার সময় তুমি assert করছ যে slide করা অংশে পাশের lines-এর সাথে কোনো data dependence নেই। যদি fragment report পড়ে আর ঠিক আগের line report লেখে, সেখানে dependence আছে — slide করা যাবে না, copies দেখতে যতই identical হোক।
পুরো checklist, প্রতিটা fragment-এর জন্য যে state গুলো পেরোতে হয়:
একটা বড় real-life example
ধরো পুরো ক্যান্টিনটাই code করছো। "before" version-এ দুই food line হুবহু একই — duplicate মিষ্টির কাউন্টার, duplicate tray step, duplicate plate count সহ:
interface Student {
name: string;
prefersVeg: boolean;
}
interface Plate {
items: string[];
}
// BEFORE: tray, dessert, and counting duplicated inside both lines
function serveLunch(student: Student, stats: { plates: number }): Plate {
let plate: Plate;
if (student.prefersVeg) {
plate = { items: [] };
plate.items.push("roti");
plate.items.push("dal");
plate.items.push("paneer");
plate.items.push("gulab jamun"); // dessert counter inside veg line
stats.plates += 1; // counting inside veg line
} else {
plate = { items: [] };
plate.items.push("roti");
plate.items.push("chicken curry");
plate.items.push("gulab jamun"); // dessert counter inside non-veg line
stats.plates += 1; // counting inside non-veg line
}
return plate;
}duplicates গুনে দেখো: খালি tray (plate = ...) দুটো branch-ই শুরু করছে, রুটি দুটোর দ্বিতীয় line, মিষ্টি আর plate count দুটোর শেষে। দুই লাইনের একমাত্র সত্যিকারের পার্থক্য হলো ডাল-পনির বনাম চিকেন কারি। চলো code-কে সেটাই বলাই:
// AFTER: lines split only where food actually differs
function serveLunch(student: Student, stats: { plates: number }): Plate {
const plate: Plate = { items: [] }; // tray issued once, before the split
plate.items.push("roti"); // roti counter serves everyone
if (student.prefersVeg) {
plate.items.push("dal");
plate.items.push("paneer");
} else {
plate.items.push("chicken curry");
}
plate.items.push("gulab jamun"); // ONE dessert counter, after the merge
stats.plates += 1; // counted once
return plate;
}"after" version উপর থেকে নিচে পড়ো আর নতুন ক্যান্টিনের গল্প শুনবে: সবাই tray নেয়, সবাই রুটি নেয়, শুধু main course-এর জন্য লাইন ভাগ হয়, লাইন আবার মেলে, সবাই মিষ্টি নেয়, প্রতিটা plate গোনা হয়। conditional ঠিক সেই decision-টাই represent করছে। সুমাইয়ার plate follow করো:
এখন সোমবারের খিচুড়ি বদলানো একটা line-এর edit, কাউকে মিস করার সম্ভাবনা নেই:
// Monday special: change ONE line, both lines get kheer — guaranteed
plate.items.push(isMonday() ? "kheer" : "gulab jamun");ক্যান্টিনের logic যদি আরো বাড়তে থাকে, shared steps স্বাভাবিকভাবেই নিজেদের ছোট service হয়ে যায়, আর hall-এর structure হয়ে যায় code-এর structure:
C# আর Python-এও একই refactoring
একটা ছোট example, classic pricing shape থেকে:
// BEFORE: Send() duplicated at the end of both branches
void Checkout(Order order)
{
if (order.HasSpecialDeal)
{
order.Total = order.Price * 0.95m;
Send(order);
}
else
{
order.Total = order.Price * 0.98m;
Send(order);
}
}
// AFTER: the conditional decides only the discount; sending is universal
void Checkout(Order order)
{
if (order.HasSpecialDeal)
order.Total = order.Price * 0.95m;
else
order.Total = order.Price * 0.98m;
Send(order);
}এখন প্রতিটা branch একটাই assignment, তাই C# ternary দিয়ে আরো সহজ করা যায়:
void Checkout(Order order)
{
order.Total = order.Price * (order.HasSpecialDeal ? 0.95m : 0.98m);
Send(order);
}আট line-এর branching থেকে দুটো সৎ line: discount factor হলো decision, send হলো constant। Python-এও একই গল্প:
# BEFORE: send_order copied into both branches
def checkout(order):
if order.has_special_deal:
order.total = order.price * 0.95
send_order(order)
else:
order.total = order.price * 0.98
send_order(order)
# AFTER: the branch decides only the factor; sending happens once
def checkout(order):
factor = 0.95 if order.has_special_deal else 0.98
order.total = order.price * factor
send_order(order)আরেকটু deeper যাই: এই refactoring হলো DRY principle-এর statement-level রূপ — Don't Repeat Yourself। DRY-কে অনেকে ভুল বোঝে "একই text দুবার লিখবে না" বলে। আসল মানে হলো প্রতিটা piece of knowledge-এর system-এ একটাই authoritative representation থাকবে। মিষ্টির step হলো একটা knowledge ("সবার plate-এ মিষ্টি যাবে"), দুই branch-এ লেখা মানে দুটো representation যেগুলো synchronised থাকবে কিনা luck-এর উপর নির্ভর করছে। সোমবারের kheer bug হলো textbook shotgun update gone wrong — একটা logical পরিবর্তনের জন্য multiple জায়গায় edit করতে হচ্ছে, যেকোনো একটা মিস হলে bug। Single representation এই পুরো failure mode টাকে অসম্ভব করে দেয়।
IDE support
এই refactoring মূলত সাবধানী cut-and-paste, কিন্তু modern tools পাহারা দেয়:
| Tool | কী help পাবে |
|---|---|
| IntelliJ IDEA / Rider / WebStorm | "common part can be extracted from if" inspection, Alt+Enter-এ one-click fix |
| Visual Studio + ReSharper | "Invert if", "merge", আর duplicate-code analysis branches-এর identical fragments highlight করে |
| SonarQube / SonarLint | conditional branches-এ identical implementation আর duplicated blocks detect করার rules আছে |
| VS Code | কোনো dedicated action নেই; selection comparison আর test suite-এর উপর নির্ভর করো |
IntelliJ-family inspection শুরু করার জন্য সত্যিই কাজের: সেই duplication ধরে যেটা তুমি নিজে লেখার কারণে আর দেখতে পাচ্ছ না। shared fragment যদি অনেক line-এর হয়, ভালো combo হলো আগে Extract Method করো (যাতে প্রতিটা branch-এ একটা named call হয়), তারপর সেই একটা call-কে বাইরে নিয়ে যাও।
সুবিধা আর ঝুঁকি
দুইটা কাউন্টার, দুইজন স্টাফ, দুইটা burner — একটাই কাজ করছে। Code audit-এও একই হিসাব। প্রতিটা duplicate fragment মানে change করার জায়গা দ্বিগুণ — আর "যত জায়গায় edit দরকার" আর "যত জায়গায় edit হয়েছে"-এর ফাঁকেই kheer bug-রা বাস করে।
| সুবিধা | কেন গুরুত্বপূর্ণ |
|---|---|
| Branches শুধু সত্যিকারের পার্থক্য দেখায় | Reader সেকেন্ডে আসল decision বুঝতে পারে |
| Shared behaviour-এর একটাই home | "এক branch-এ ঠিক, অন্যটায় ভাঙা" bug class টা উধাও হয় |
| Method ছোট হয় | প্রতিটা সরানো copy মানে reader-কে আর সেই line পড়তে হবে না |
| আরো cleanup unlock হয় | ছোট branches প্রায়ই ternary বা guard clause হয়ে যায় |
| ঝুঁকি | কীভাবে handle করবে |
|---|---|
| Branch-এ early exit আছে | Fragment-এর আগে return/throw/break থাকলে sinking unsafe — আগে প্রতিটা branch চেক করো |
| Hidden ordering dependency | Branch যদি fragment-এর পড়া state লেখে, hoist করলে behaviour বদলাবে — data trace করো |
| দেখতে একই কিন্তু আলাদা fragment | একই shape, আলাদা variable = duplicate না; একসাথে করলে bug |
| ভবিষ্যতে fragment আলাদা হওয়ার কথা | যদি lines আলাদা evolve করার কথা থাকে, আলাদা রাখাই সৎ |
কখন করবে না: যখন fragments আজকে coincidentally identical কিন্তু আলাদা responsibility represent করে যেগুলো আলাদাভাবে evolve করবে। Conditions consolidate করার মতো, fragments consolidate করা মানে বলছ "এটা একটা জিনিস" — সেটা সত্যি হলেই বলো।
কোন code smell ঠিক করে?
| Code smell | কীভাবে এই refactoring সাহায্য করে |
|---|---|
| Duplicate Code | Conditional branches-এর ভেতরে লুকানো copies সরাসরি মুছে দেয় |
| Long Method | Repeated lines মুছলে method ছোট হয়, view পরিষ্কার হয় |
Quick revision
+-----------------------------------------------------------------+
| CONSOLIDATE DUPLICATE CONDITIONAL FRAGMENTS — CHEAT SHEET |
+-----------------------------------------------------------------+
| Signal : the SAME line appears in EVERY branch of an if/switch |
| Rule : leading duplicate -> move BEFORE the conditional |
| trailing duplicate -> move AFTER the conditional |
| middle duplicate -> slide to an edge first |
| Safety : no early exits past the fragment |
| no branch-computed state the fragment depends on |
| fragments must be TRULY identical |
| Test : run the suite after every single hoist |
| Result : branches contain only what truly differs |
| Cures : Duplicate Code, Long Method |
+-----------------------------------------------------------------+Practice করো
একটা school app-এর notification function — branch-এর শুরু আর শেষে duplication আছে, আর একটা trap-ও আছে। ঠিক করো:
// Careful: one of these branches has a trap!
function notifyResult(student: Student, passed: boolean): void {
if (passed) {
const contact = lookupParentContact(student);
sendSms(contact, `${student.name} has passed. Congratulations!`);
logNotification(student);
} else {
const contact = lookupParentContact(student);
if (!contact.smsEnabled) {
return; // parents opted out of bad-news SMS
}
sendSms(contact, `${student.name} needs a re-test. Please meet the teacher.`);
logNotification(student);
}
}তোমার checklist:
- Leading fragment
lookupParentContact(student)দুটো branch-ই শুরু করছে।if-এর আগে তোলা কি safe? (চেক করো: কোনো branch-এ এর আগে কিছু চলছে কিনা?) - Trailing fragment
logNotification(student)দুটো branch-ই শেষ করছে — কিন্তু একটু থামো। এক branch-এ এর আগে earlyreturnআছে। নিচে নামানো কি safe? কী বদলে যাবে? - ভাবো: opt-out check টা কি এমনভাবে restructure করা যায় যাতে trailing fragment নিরাপদে বাইরে যেতে পারে? (Hint: contact lookup উপরে উঠলে early exit কোথায় যেতে পারে সেটা ভাবো।)
- College bonus: এই function-এর control flow graph আঁকো।
if/else-এর পরে join block mark করো আর post-dominance-এর ভাষায় explain করো কেন earlyreturnsink করতে দিচ্ছে না। - প্রতিটা move-এর পরে test চালাও — বিশেষ করে যেটায়
smsEnabledfalse।
early return ধরতে পারলে logNotification এর blind hoist ভুল হবে বুঝেছ — এই judgment-টাই এই refactoring শেখায়। এক কাউন্টার, merge-এর পরে, শুধু তখনই যখন প্রতিটা path সেখান দিয়ে যায়। Practice চালিয়ে যাও!
সচরাচর জিজ্ঞাসা
- Consolidate Duplicate Conditional Fragments মানে কী সহজ ভাষায়?
- if/else-এর প্রতিটা branch-এ যদি হুবহু একই line থাকে, তাহলে সেই line আসলে decision-এর অংশ না। সেটাকে বাইরে নিয়ে যাও — শুরুতে থাকলে if-এর আগে, শেষে থাকলে if-এর পরে। এতে code একবারই লেখা হবে, একবারই চলবে।
- Consolidate Conditional Expression আর এটার মধ্যে পার্থক্য কী?
- নামে মিল আছে কিন্তু সমস্যা আলাদা। Consolidate Conditional Expression একটাই result-এ যাওয়া কয়েকটা CONDITION মিলিয়ে দেয়। আর Consolidate Duplicate Conditional Fragments একটা conditional-এর branches থেকে duplicate ACTION code বের করে আনে। একটা প্রশ্নটা ঠিক করে, অন্যটা উত্তরগুলো ঠিক করে।
- if-এর পরে trailing fragment সরানো কখন unsafe?
- দুইটা মূল ক্ষেত্র। প্রথমত, কোনো branch যদি return, throw, বা break দিয়ে আগেই বেরিয়ে যেতে পারে, তাহলে সরানো line যেখানে আগে চলত না সেখানে চলবে বা উল্টো হবে। দ্বিতীয়ত, কোনো branch যদি fragment যে state-এর উপর নির্ভর করে সেটা পরিবর্তন করে, তাহলে সরালে কাজের order বদলে যায়। সরানোর আগে দুটোই চেক করো।
- duplicate code টা প্রতিটা branch-এর মাঝখানে আছে। কী করব?
- আগে সেটাকে প্রতিটা branch-এর শুরু বা শেষে slide করার চেষ্টা করো — কিন্তু শুধু তখনই যদি পাশের line গুলো পেরিয়ে swap করলে result না বদলায়। একবার প্রতিটা branch-এর একই edge-এ এলে, বাইরে তুলে নাও। fragment যদি অনেক line-এর হয়, আগে Extract Method করো যাতে একটা পুরো block-এর বদলে একটা named call সরাতে হয়।
- switch statement-এও কি এই refactoring কাজ করে?
- হ্যাঁ। switch-এর প্রতিটা case যদি একই statement দিয়ে শেষ হয়, সেই statement switch-এর নিচে সরানো যায়। একই safety rules প্রযোজ্য: কোনো case fragment-এর আগে বেরিয়ে যেতে পারবে না, আর fragment কোনো case-specific state-এর উপর position-sensitive ভাবে নির্ভর করতে পারবে না।
আরো দেখো
সম্পর্কিত পাঠ
Consolidate Conditional Expression: অনেক ছোট চেক, একটাই পরিষ্কার প্রশ্ন
স্কুল গেটের গল্প দিয়ে Consolidate Conditional Expression শেখো — TypeScript আর C# উদাহরণ, নিরাপদ ধাপ, আর side-effect-এর ফাঁদ যেটা না জানলেই নয়।
Decompose Conditional: জটিল if-কে সহজ নামে ভেঙে ফেলো
Decompose Conditional refactoring শেখো স্কুলের নোটিশের গল্প দিয়ে — সহজ TypeScript ও C# উদাহরণ, নিরাপদ ধাপ, আর IDE shortcut সহ।
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।
Remove Control Flag: পেয়ে গেলেই থেমে যাও
Remove Control Flag refactoring শেখো একজন দারোয়ানের গল্পের মাধ্যমে। TypeScript আর C# এর উদাহরণ দিয়ে বুঝবে break আর return কীভাবে control flag-এর জায়গা নেয়।