Decompose Conditional: জটিল if-কে সহজ নামে ভেঙে ফেলো
Decompose Conditional refactoring শেখো স্কুলের নোটিশের গল্প দিয়ে — সহজ TypeScript ও C# উদাহরণ, নিরাপদ ধাপ, আর IDE shortcut সহ।
স্কুলের নোটিশের গল্প
ধরো জুলাই মাসের এক সকাল। ভোর থেকে বৃষ্টি পড়ছে, রাস্তায় পানি জমেছে, আর সপ্তম শ্রেণির ছাত্রী ফাতেমা স্কুলের নোটিশ বোর্ডের সামনে ভেজা অবস্থায় দাঁড়িয়ে আছে। একটা নতুন নোটিশ লাগানো হয়েছে, কালির কোণ এরইমধ্যে নরম হয়ে যাচ্ছে। সে একবার পড়ল। তারপর আবার। তারপর রাহিম আর সুমাইয়াকে ডাকল — কারণ কেউই বুঝতে পারছে না।
নোটিশে লেখা আছে: "আবহাওয়া অধিদফতর যদি দিনের জন্য ভারী বৃষ্টি ঘোষণা করে, এবং তারিখটি বার্ষিক পরীক্ষার ছুটি শুরুর আগে হয়, এবং সপ্তাহটি একাডেমিক ক্যালেন্ডার অনুযায়ী unit test সপ্তাহ না হয়, এবং ছাত্রটি দশম বা দ্বাদশ শ্রেণির না হয়, তাহলে স্কুল সকাল ৮:০০ থেকে দুপুর ১২:৩০ পর্যন্ত চলবে, খেলার পিরিয়ড বাতিল থাকবে, এবং বাস ২ নম্বর গেট থেকে ছাড়বে।"
একটানা চারটা শর্ত AND দিয়ে জোড়া, তিনটা আলাদা ফলাফল — সব একটা লম্বা বাক্যে। নোটিশ বোর্ডের সামনে প্রতিটা বাচ্চা একই কষ্টের কাজ করছে: চারটা তথ্য মাথায় ধরো, আজকের সাথে একে একে মেলাও, তারপর জানতে পারবে এটার মানে কী।
সেদিন সন্ধ্যায় ফাতেমার বাবা জামাল সাহেব অভিভাবক group-এ ছবি হিসেবে একই নোটিশ পেলেন। তিনি চায়ের দোকানে বসে ফোনে পড়ছেন, তৃতীয় AND-এ এসে হারিয়ে যাচ্ছেন, আবার শুরু থেকে পড়ছেন, শেষে ক্লাস টিচারকে message করলেন: "ম্যাডাম, সহজ প্রশ্ন — কাল কি স্কুল অর্ধ দিন নাকি না?" এক ঘণ্টার মধ্যে আরো বিয়াল্লিশজন অভিভাবক একই প্রশ্ন করলেন। নাসরিন ম্যাডাম পুরো সন্ধ্যা একে একে উত্তর দিতে কাটিয়ে দিলেন — কারণ নিয়মটা এমনভাবে লেখা যে প্রতিটা পাঠককে নিজেই সব হিসাব করতে হয়।
এবার দেখো নতুন প্রধান শিক্ষিকা রাহেলা ম্যাডাম পরের সপ্তাহ কী করলেন। তিনি নোটিশটা নামিয়ে দুটো ছোট নোটিশ দিয়ে বদলালেন।
প্রথম নোটিশ: "বৃষ্টির দিনের সময়সূচিতে স্কুল ৮:০০ থেকে ১২:৩০ পর্যন্ত চলে, খেলা বাতিল থাকে, বাস ২ নম্বর গেট থেকে ছাড়ে।"
দ্বিতীয় নোটিশ: "বৃষ্টির দিনের সময়সূচি প্রযোজ্য হয় যখন: ভারী বৃষ্টি ঘোষণা হয়, বার্ষিক ছুটির আগে হয়, পরীক্ষার সপ্তাহ না হয়, আর তুমি দশম বা দ্বাদশ শ্রেণির না হও।"
একই নিয়ম। একই শর্ত। একই ফলাফল। কিন্তু এখন মাঝখানে একটা নাম আছে: বৃষ্টির দিনের সময়সূচি। বেশিরভাগ ছাত্র শুধু প্রথম নোটিশটা দরকার। স্কুল এখন একটা SMS পাঠাতে পারে — "কাল বৃষ্টির দিনের সময়সূচি" — আর প্রতিটা পরিবার তাৎক্ষণিক জানে কী করতে হবে। জামাল সাহেব চায়ের কাপে চুমুক দিতে দিতে দুই সেকেন্ডে পড়ে ফেলেন।
রাহেলা ম্যাডাম নিয়মটা এক কমাও বদলাননি। তিনি এটাকে decompose করেছেন। প্রশ্নটা ("কাল কি বৃষ্টির দিনের সময়সূচি?") থেকে উত্তরগুলো ("এমন দিনে কী হয়") আলাদা করেছেন, আর প্রশ্নটাকে একটা নাম দিয়েছেন যা পুরো স্কুল বলতে পারে।
কোডেও একই সমস্যা আছে। একটা জটিল if statement প্রশ্ন আর উত্তর একসাথে একটা ঘন block-এ মিশিয়ে দেয়। প্রতিটা পাঠককে শুধু কী সিদ্ধান্ত হচ্ছে তা বুঝতে পুরো জিনিসটা মাথায় চালাতে হয়। এটা ঠিক করার refactoring-টার নাম Decompose Conditional — আর এটা তুমি শিখবে এমন সবচেয়ে সহজ refactoring-গুলোর একটা।
Decompose Conditional কী?
Decompose Conditional হলো একটা refactoring যেখানে তুমি একটা জটিল conditional-কে নামযুক্ত method-এ ভেঙে ফেলো। একটা method condition-এর জন্য (প্রশ্ন হিসেবে নাম), একটা then-branch-এর জন্য (ফলাফলের নাম), একটা else-branch-এর জন্য (সেটাও ফলাফলের নাম)। if statement থাকে, কিন্তু সেটা একটাই পাঠযোগ্য লাইনে সংকুচিত হয়ে যায় — রাহেলা ম্যাডামের এক-লাইনের SMS-এর code সংস্করণ।
Martin Fowler-এর Refactoring বইয়ে এই technique টা Simplifying Conditional Expressions পরিবারের অন্তর্গত। Fowler-এর classic উদাহরণ seasonal pricing নিয়ে: একটা hotel charge যা গ্রীষ্মে এক ভাবে আর বছরের বাকি সময়ে অন্যভাবে হিসাব হয়। Refactoring-এর আগে পাঠক raw তারিখ তুলনা আর arithmetic দেখে। পরে দেখে "if summer, summer charge, else regular charge" — প্রায় ইংরেজির মতো পড়া যায়।
Decompose Conditional-এর ভেতরের engine হলো Extract Method। তুমি এটা তিনবার করো — condition-এ, then-অংশে, আর else-অংশে। এটাকে নিজস্ব refactoring করে তোলে তার উদ্দেশ্য: তুমি শুধু একটা লম্বা method ছোট করছো না, তুমি একটা নিচু-স্তরের test-কে একটা নামযুক্ত domain ধারণায় রূপান্তর করছো — ঠিক যেমন রাহেলা ম্যাডাম চারটা AND-এর শর্তকে "বৃষ্টির দিনের সময়সূচি" বানিয়েছেন।
ব্যাপারটা হলো এই। একটা conditional-এ তিনটা কাজ একসাথে আটকানো থাকে:
- Test — তুলনা আর operator দিয়ে তৈরি একটা boolean expression।
- Then-path — উত্তর হ্যাঁ হলে কী হয়।
- Else-path — উত্তর না হলে কী হয়।
তিনটা inline লেখা থাকলে অর্থ ডুবে যায় mechanics-এ। প্রতিটা অংশকে নাম দেওয়া হলো সবচেয়ে সস্তা abstraction যা তোমার ভাষা দিতে পারে। একটা method-এর নাম "এই token-গুলো কী compute করে" থেকে "এটার মানে কী" তে রূপান্তর করে।
কলেজ কর্নার: তুমি আসলে এখানে যা করছো তা হলো procedural abstraction — computer science-এর সবচেয়ে পুরনো ধারণাগুলোর একটা। একটা abstraction কীভাবে কিছু compute হয় তা একটা নামের পেছনে লুকিয়ে রাখে, যাতে পাঠক অর্থের স্তরে কাজ করতে পারে। Barbara Liskov-এর কাজ বলে একটা ভালো নাম একটা ছোট চুক্তি গঠন করে: caller-রা implementation-এর উপর না, নামের প্রতিশ্রুতির উপর নির্ভর করে। isRainyDaySchedule-এর মতো boolean-returning method হলো সবচেয়ে ছোট কার্যকর abstraction — একটা predicate, তোমার domain থেকে শুধু true আর false ধারণকারী set-এ একটা function। যখন তুমি conditional decompose করো, তুমি একটা anonymous predicate-কে expression soup থেকে বের করে program-এর vocabulary-তে নিবন্ধন করছো।
মনে রাখার একটা লাইন: প্রশ্নের নাম দাও, প্রতিটা উত্তরের নাম দাও, if টা যেন একটা বাক্যের মতো পড়া যায়। যদি if লাইনটা বন্ধুকে জোরে পড়ে শোনাতে পারো আর সে তাৎক্ষণিক বুঝতে পারে — তাহলে ঠিকমতো হয়েছে।
কখন এটা দরকার?
এই situation-গুলো খেয়াল করো:
- condition বোঝাতে একটা comment দরকার হচ্ছে। যদি কোনো
if-এর উপরে// check if monsoon discount appliesলিখতে নিজেকে দেখো, code টা একটা নামের জন্য চিৎকার করছে। Comment টাকেmonsoonDiscountApplies()নামের একটা method দিয়ে বদলাও। এটা Comments smell-ও সারিয়ে দেয় — code কী করে সেটা ব্যাখ্যা করা comment সাধারণত ছদ্মবেশী একটা method নাম। - condition-এ তিন বা তার বেশি অংশ আছে।
&&দিয়ে জোড়া দুটো clause হয়তো এখনো পড়া যায়। মিশ্র&&,||, আর!সহ চারটা clause হলো একটা ধাঁধা। Decompose করো। - branch-গুলো লম্বা। যখন then-block আর else-block প্রতিটা দশ লাইনের, পুরো conditional টা একটা দেওয়াল হয়ে দাঁড়ায়। এটা Long Method smell-এর classic লক্ষণ, আর conditional decompose করা প্রায়ই প্রথম কাটা যা এটাকে ছোট করে আনে।
- বারবার একই
ifপড়তে হচ্ছে মনে করার জন্য। তোমার নিজের বিভ্রান্তিটাই তথ্য! লেখক যদি দ্রুত আবার পড়তে না পারেন, কেউ পারবে না। - একটা নতুন branch আসছে। ধরো স্কুল আগামী মৌসুমে "অর্ধ বৃষ্টির দিন" নিয়ম যোগ করছে। একটা ঘন inline conditional-এ এটা যোগ করা মানে জটের মধ্যে অপারেশন। একটা decomposed conditional-এ মানে একটা নতুন নামযুক্ত method আর একটা নতুন পরিষ্কার branch।
আর কখন অপেক্ষা করা বা এড়িয়ে যাওয়া উচিত:
- conditional ইতিমধ্যেই সহজ।
if (marks >= 40)এর জন্যhasPassed()method দরকার নেই, যদি না সেই নিয়মটা অনেক জায়গায় ব্যবহৃত হয় বা pass mark বদলাতে পারে। - branch-গুলো অনেক local variable শেয়ার করে। Extract করলে লম্বা parameter list আসতে পারে। আগে parameter object বা Extract Class করার কথা ভাবো।
- একটা ভালো নাম ভাবতে পারছো না।
checkStuff()এর মতো অস্পষ্ট নাম inline code-এর চেয়ে খারাপ। আগে condition টা বোঝো, নামটা এমনিই আসবে।
সিদ্ধান্ত নেওয়ার সহজ উপায়: জিজ্ঞেস করো condition টা কতটা লম্বা, আর মানুষ কতটা ঘন ঘন এটা পড়ে। লম্বা condition যা সবাই প্রতিদিন পড়ে — নোটিশ বোর্ডের নোটিশের মতো — সেটা এখনই decompose করা উচিত। codebase-এর কোণে পড়ে থাকা ছোট condition যেখানে কেউ যায় না, সেটা চিরকাল অপেক্ষা করতে পারে।
একনজরে আগে আর পরে
স্কুলের নোটিশটা TypeScript-এ লিখে দেখাই। স্কুলের app-কে দিনের closing time হিসাব করতে হবে। এখানে "আগের" version, ঠিক সেই বিভ্রান্তিকর নোটিশের মতো লেখা:
interface DayInfo {
heavyRainDeclared: boolean;
date: Date;
diwaliVacationStart: Date;
isTestWeek: boolean;
classLevel: number;
}
// BEFORE: the question and the answers are all mashed together
function closingTime(day: DayInfo): string {
if (
day.heavyRainDeclared &&
day.date < day.diwaliVacationStart &&
!day.isTestWeek &&
day.classLevel !== 10 &&
day.classLevel !== 12
) {
return "12:30";
} else {
return "15:30";
}
}আর এখানে "পরের" version। একই behaviour, কিন্তু প্রশ্নটার এখন একটা নাম আছে:
// AFTER: the if reads like the principal's short notice
function closingTime(day: DayInfo): string {
return isRainyDaySchedule(day) ? "12:30" : "15:30";
}
function isRainyDaySchedule(day: DayInfo): boolean {
const beforeDiwaliVacation = day.date < day.diwaliVacationStart;
const isBoardClass = day.classLevel === 10 || day.classLevel === 12;
return (
day.heavyRainDeclared && beforeDiwaliVacation && !day.isTestWeek && !isBoardClass
);
}তিনটা ছোট লাভ দেখো। প্রথমত, closingTime এখন একটাই পাঠযোগ্য লাইন। দ্বিতীয়ত, ঝামেলার বিস্তারিত isRainyDaySchedule-এ চলে গেছে — কৌতূহলী পাঠক চাইলে ঢুকতে পারবে। তৃতীয়ত, extracted method-এর ভেতরে উপ-ধারণাগুলোকেও নাম দিয়েছি (beforeDiwaliVacation, isBoardClass) Extract Variable দিয়ে — decomposition প্রতিটা স্তরে কাজ করে।
আগের version-এ লাইনগুলো কোথায় থাকে সেটায় একটু থামো। মূল closingTime দেখো: মোটামুটি এক তৃতীয়াংশ condition logic, এক তৃতীয়াংশ then-branch, এক তৃতীয়াংশ else-branch — আর তিনটা তৃতীয়াংশই একসাথে পাঠকের দিকে চিৎকার করছে। Decomposition-এর পরে উপরের method শুধু বাক্যটা রাখে, প্রতিটা তৃতীয়াংশ নিজের নামযুক্ত বাড়িতে থাকে।
কলেজ কর্নার: উপরের condition টা একটা boolean expression, আর boolean algebra তোমাকে এটাকে আকার দেওয়ার বিনামূল্যে tools দেয়। isRainyDaySchedule-এর ভেতরে আমরা !isBoardClass লিখেছি, যেখানে isBoardClass হলো classLevel === 10 || classLevel === 12। De Morgan's law অনুযায়ী, NOT (A OR B) সমান (NOT A) AND (NOT B) — তাই এটা মূল classLevel !== 10 && classLevel !== 12-এর সমতুল্য। দুটো form-ই সঠিক, কিন্তু refactored টা স্পষ্ট কারণ ইতিবাচক ধারণাটা ("board class-এ আছে") নাম দিতে আর verify করতে সহজ। একটা practical rule: ইতিবাচকভাবে predicate নাম দাও, তারপর যেখানে দরকার সেখানে negate করো। !isBoardClass পড়তে isNotBoardClass-এর চেয়ে অনেক ভালো।
ধাপে ধাপে, নিরাপদ পদ্ধতিতে
Refactoring মানে rewriting না। আমরা ছোট ছোট ধাপে এগাই, প্রতিটা ধাপের পরে test চালাই। একটা সমৃদ্ধ উদাহরণ দিয়ে পুরো discipline দেখাই: seasonal নিয়ম সহ একটা school-fee calculator।
শুরুর code:
// STARTING POINT: correct, but the intent is buried
function busFee(month: number, distanceKm: number): number {
let fee: number;
if (month >= 6 && month <= 9) {
fee = distanceKm * 30 + 150; // monsoon: higher rate plus rain-cover charge
} else {
fee = distanceKm * 25;
}
return fee;
}ধাপ ১ — condition টাকে একটা question method-এ Extract করো। month >= 6 && month <= 9 select করো, Extract Method করো। প্রশ্ন হিসেবে নাম দাও:
// Step 1: INTERMEDIATE — only the condition has moved
function busFee(month: number, distanceKm: number): number {
let fee: number;
if (isMonsoon(month)) {
fee = distanceKm * 30 + 150;
} else {
fee = distanceKm * 25;
}
return fee;
}
function isMonsoon(month: number): boolean {
return month >= 6 && month <= 9;
}Test চালাও। সবুজ? ভালো।
ধাপ ২ — then-branch টা Extract করো। Monsoon formula select করো, extract করো, ফলাফলের নাম দাও:
// Step 2: INTERMEDIATE — then-branch is now named
function busFee(month: number, distanceKm: number): number {
let fee: number;
if (isMonsoon(month)) {
fee = monsoonFee(distanceKm);
} else {
fee = distanceKm * 25;
}
return fee;
}
function monsoonFee(distanceKm: number): number {
return distanceKm * 30 + 150; // higher rate plus rain-cover charge
}আবার test চালাও।
ধাপ ৩ — else-branch টা Extract করো। একই move, একই সতর্কতা:
// Step 3: INTERMEDIATE — both branches are named
function busFee(month: number, distanceKm: number): number {
let fee: number;
if (isMonsoon(month)) {
fee = monsoonFee(distanceKm);
} else {
fee = regularFee(distanceKm);
}
return fee;
}
function regularFee(distanceKm: number): number {
return distanceKm * 25;
}Test চালাও।
ধাপ ৪ — সম্ভব হলে flatten করো। দুটো branch এখন single expression, তাই পুরো conditional একটা ternary-তে collapse হতে পারে:
// Step 4: FINAL — reads like a sentence
function busFee(month: number, distanceKm: number): number {
return isMonsoon(month) ? monsoonFee(distanceKm) : regularFee(distanceKm);
}ধাপ ৫ — নামগুলো review করো। উপরের method জোরে পড়ো: "bus fee is monsoon fee in monsoon, else regular fee." যদি কোনো নাম যান্ত্রিক বা অস্পষ্ট শোনায়, এখনই rename করো — আজ rename করা সস্তা, পরের বছর ব্যয়বহুল।
শেষে না, প্রতিটা ধাপের পরেই test চালাও। প্রতিটা extraction ছোট, তাই test fail করলে জানবে ঠিক কোন দুই মিনিটের পরিবর্তনটা কারণ। পাঁচটা ধাপ একসাথে করে তারপর test করলে failure যেকোনোটায় লুকিয়ে থাকতে পারে, আর তুমি সন্ধ্যা কাটাবে খুঁজতে। ছোট ধাপ আর ঘন ঘন test হলো নিরাপদ refactoring-এর পুরো রহস্য।
পুরো যাত্রাটা একটা ছোট state machine যার মধ্যে cycle করো যতক্ষণ না conditional সম্পূর্ণ নামযুক্ত হয়:
একটা বড় বাস্তব উদাহরণ
এবার পুরো নোটিশটাই code করি, outcomes সহ। স্কুলের app-কে পুরো দিনের পরিকল্পনা তৈরি করতে হবে: closing time, games status, আর bus gate। এখানে জগাখিচুড়ি "আগের" version — একটা function সব কিছু inline করছে, যেভাবে এই ধরনের code আসলে বাড়তে থাকে:
interface DayPlan {
closing: string;
gamesOn: boolean;
busGate: number;
}
// BEFORE: the circular, translated word for word into a tangle
function dayPlan(day: DayInfo): DayPlan {
if (
day.heavyRainDeclared &&
day.date < day.diwaliVacationStart &&
!day.isTestWeek &&
day.classLevel !== 10 &&
day.classLevel !== 12
) {
return { closing: "12:30", gamesOn: false, busGate: 2 };
} else {
return { closing: "15:30", gamesOn: true, busGate: 1 };
}
}কাজ করে। কিন্তু শুধু "বৃষ্টির দিনগুলো তাড়াতাড়ি শেষ হয়" জানার জন্য প্রতিটা পাঠককে পাঁচটা শর্ত আর দুটো object literal-এর পুরো পড়ার খরচ দিতে হয়। Decompose Conditional করো, একটা করে নামযুক্ত টুকরো:
// AFTER: the principal's two short notices, in code
function dayPlan(day: DayInfo): DayPlan {
return isRainyDaySchedule(day) ? rainyDayPlan() : normalDayPlan();
}
// Notice 2: when does the Rainy Day Timetable apply?
function isRainyDaySchedule(day: DayInfo): boolean {
const beforeDiwaliVacation = day.date < day.diwaliVacationStart;
const isBoardClass = day.classLevel === 10 || day.classLevel === 12;
return (
day.heavyRainDeclared && beforeDiwaliVacation && !day.isTestWeek && !isBoardClass
);
}
// Notice 1: what happens on such a day?
function rainyDayPlan(): DayPlan {
return { closing: "12:30", gamesOn: false, busGate: 2 };
}
function normalDayPlan(): DayPlan {
return { closing: "15:30", gamesOn: true, busGate: 1 };
}দেখো কী লাভ পেলাম। dayPlan এখন একটা সূচিপত্র। "বৃষ্টির দিনে কী হয়?" জিজ্ঞেস করা কেউ rainyDayPlan পড়বে। "আজ কি নিয়মটা প্রযোজ্য?" জিজ্ঞেস করা অফিস স্টাফ isRainyDaySchedule পড়বে। কাউকে সবকিছু পড়তে বাধ্য করা হচ্ছে না। আর স্কুল যখন "অর্ধ বৃষ্টির দিন" নিয়ম যোগ করবে, সেটা আসবে একটা নতুন question method আর একটা নতুন plan method হিসেবে — বিদ্যমান logic-এ কোনো অপারেশন না।
উপরের method আর নামযুক্ত প্রশ্নের কথোপকথন এখন সুন্দরভাবে সহজ — একটা call, একটা হ্যাঁ-বা-না উত্তর, একটা decision point:
একটা bonus সুবিধা আছে: প্রতিটা টুকরো এখন স্বাধীনভাবে test করা যায়। দশটা কঠিন তারিখ দিয়ে isRainyDaySchedule test করতে পারো কখনো পুরো day plan না বানিয়ে, আর আবহাওয়া নকল না করে plan-গুলো test করতে পারো।
App বড় হলে এই নামযুক্ত টুকরোগুলো স্বাভাবিকভাবেই একটা class-এ জড়ো হয় — decomposition নিঃশব্দে তোমার জন্য design-এর খসড়া এঁকেছে:
কলেজ কর্নার: লক্ষ করো isRainyDaySchedule একটা pure function — এটা input পড়ে, তুলনা করে, return করে; বাইরের জগতে কিছু বদলায় না। Pure predicate-এর একটা বৈশিষ্ট্য আছে যাকে বলে referential transparency: একটা call-কে program না বদলে যেকোনো জায়গায় তার ফলাফল দিয়ে প্রতিস্থাপন করা যায়। এটাই তাদের extract করা নিরাপদ, reuse করা নিরাপদ, isolation-এ test করা নিরাপদ করে। যখনই conditional decompose করো, extracted প্রশ্নটা pure রাখার চেষ্টা করো। যদি condition টা গোপনে কাজ করে — logging, counting, saving — সেই কাজটা আগে আলাদা করো, নইলে নামযুক্ত প্রশ্নটা সে কী করে সে সম্পর্কে একটা ছোট মিথ্যা বলবে।
C# আর Python-এ একই refactoring
ধারণাটা প্রতিটা ভাষায় অভিন্ন। এখানে C#-এ Fowler-style seasonal pricing, একইভাবে decomposed:
// BEFORE
decimal CalculateCharge(DateTime date, int quantity, decimal unitPrice)
{
decimal total;
if (date < _summerStart || date > _summerEnd)
total = quantity * unitPrice
- Math.Max(0, quantity - _bulkThreshold) * unitPrice * 0.10m;
else
total = quantity * unitPrice + _summerSurcharge;
return total;
}
// AFTER
decimal CalculateCharge(DateTime date, int quantity, decimal unitPrice) =>
IsWinter(date)
? WinterCharge(quantity, unitPrice)
: SummerCharge(quantity, unitPrice);
bool IsWinter(DateTime date) => date < _summerStart || date > _summerEnd;
decimal WinterCharge(int quantity, decimal unitPrice)
{
var bulkUnits = Math.Max(0, quantity - _bulkThreshold);
return quantity * unitPrice - bulkUnits * unitPrice * 0.10m;
}
decimal SummerCharge(int quantity, decimal unitPrice) =>
quantity * unitPrice + _summerSurcharge;C#-এর expression-bodied members (=>) decomposed টুকরোগুলোকে বিশেষভাবে গোছানো করে তোলে। Magic number আর date arithmetic এখন ছোট, সুনামকৃত method-এর ভেতরে, আর উপরের method একটা লাইনে business rule বলে: winter-কে winter পদ্ধতিতে charge করা হয়।
Python-ও ঠিক স্বাভাবিক — ছোট নামযুক্ত function খুব Python-এর ধরন:
# BEFORE: the rule is buried in the comparison soup
def closing_time(day):
if (day.heavy_rain and day.date < day.diwali_start
and not day.is_test_week and day.class_level not in (10, 12)):
return "12:30"
return "15:30"
# AFTER: the question has a name; the if reads aloud
def is_rainy_day_schedule(day):
before_vacation = day.date < day.diwali_start
is_board_class = day.class_level in (10, 12)
return day.heavy_rain and before_vacation and not day.is_test_week and not is_board_class
def closing_time(day):
return "12:30" if is_rainy_day_schedule(day) else "15:30"IDE support
Decompose Conditional আসলে তিনটা Extract Method move, তাই তোমার editor বেশিরভাগ ভারী কাজ করে দেয়:
| IDE | কীভাবে condition বা branch extract করবে |
|---|---|
| Visual Studio | code select করো, Ctrl+R, Ctrl+M চাপো, বা Ctrl+. চাপো আর Extract method বেছে নাও |
| IntelliJ IDEA / Rider | code select করো আর Ctrl+Alt+M চাপো (Refactor → Extract → Method) |
| VS Code | code select করো, Ctrl+. চাপো আর Extract to function বেছে নাও (TypeScript/JavaScript built-in) |
| ReSharper | rename preview সহ Extract Method-এর জন্য Ctrl+R, M |
একটা practical tip: if (...) parentheses-এর ভেতরে শুধু boolean expression select করো আর Extract Method invoke করো — IDE boolean/bool return করা একটা function তৈরি করবে আর expression টাকে একটা call দিয়ে বদলে দেবে। তারপর প্রতিটা branch body select করো আর পুনরাবৃত্তি করো। IDE স্বয়ংক্রিয়ভাবে parameter আর return type handle করে, যা মানবিক ভুলের বেশিরভাগ সুযোগ দূর করে। তোমার একমাত্র আসল কাজ ভালো নাম বেছে নেওয়া — আর সেটা কোনো IDE করতে পারবে না।
সুবিধা আর ঝুঁকি
নামটা আসলে কতটা বাঁচায়? একটু ভাবো নাসরিন ম্যাডামের সন্ধ্যার কথা। পুরনো নোটিশে প্রতিটা অভিভাবককে নিয়ম আবার হিসাব করতে হতো। নামযুক্ত নিয়মে যাচাই করা এক দৃষ্টিতে হয়ে গেছে। Code একইভাবে আচরণ করে: "এটা কি board class ঠিকমতো handle করছে?" পরীক্ষা করা reviewer হয় পাঁচটা clause আবার বের করে, অথবা সরাসরি একটা ছোট method-এ যায় আর চারটা লেবেলযুক্ত লাইন পড়ে।
| সুবিধা | কেন গুরুত্বপূর্ণ |
|---|---|
if টা intent হিসেবে পড়া যায় | "If rainy day schedule" পাঁচটা AND-এর তুলনার চেয়ে ভালো |
| নাম comment বদলে দেয় | একটা method নাম কখনো comment-এর মতো পুরনো হয়ে ভুল হয় না |
| branch-গুলো test করা যায় | প্রতিটা ফলাফল আলাদাভাবে unit-test করা যায় |
| বিস্তারিত quarantine-এ থাকে | magic number-গুলো main flow-এ না, ছোট method-এর ভেতরে |
| ভবিষ্যত branch পরিষ্কারভাবে যুক্ত হয় | নতুন নিয়ম নতুন নামযুক্ত method, অতিরিক্ত clause না |
| ঝুঁকি | কীভাবে সামলাবে |
|---|---|
| সহজ condition-এর অতিরিক্ত extraction | if (x > 0) একা ছেড়ে দাও; শুধু তখনই extract করো যখন অর্থ লুকানো |
| অস্পষ্ট নাম | helper1() inline code-এর চেয়ে খারাপ; যতক্ষণ না জোরে পড়া ভালো শোনায় rename করো |
| লম্বা parameter list | branch-গুলো অনেক local শেয়ার করলে আগে parameter object করো |
| extraction-এর সময় behaviour পরিবর্তন | IDE refactoring ব্যবহার করো আর প্রতিটা ধাপের পরে test চালাও |
কখন না করা উচিত? যখন conditional ছোট, স্পষ্ট, আর শুধু এক জায়গায় ব্যবহৃত। Extraction তখন পাঠকের জন্য একটা লাফ যোগ করে অর্থ না যোগ করেই। Decompose Conditional উজ্জ্বল হয় জটিল conditional-এ — শব্দটা কারণেই recipe-তে আছে।
কলেজ কর্নার: একটা সাধারণ চিন্তা হলো performance — "তিনটা অতিরিক্ত method call কি program ধীর করবে না?" ব্যবহারিকভাবে, না। Just-in-time compiler-গুলো (JVM-এর HotSpot, .NET-এর RyuJIT, JavaScript-এর V8) ছোট, ঘন ঘন call করা method আক্রমণাত্মকভাবে inline করে: call compile time-এ অদৃশ্য হয়ে যায় আর generated machine code মূলত হাতে-জটানো version-এর মতো। Inlining heuristic-গুলো আসলে extracted predicate-এর মতো ছোট method-কে পছন্দ করে। তাই trade-off টা "readability বনাম speed" না — এটা "readability বনাম কিছুই না"। একমাত্র সৎ ব্যতিক্রম হলো profiler report-এ পরিমাপ করা hot loop — আর সেখানেও ক্ষতি অনুমান করার আগে decompose করার পরে পরিমাপ করো।
কোন smell-গুলো এটা সারিয়ে দেয়?
| Code smell | Decompose Conditional কীভাবে সাহায্য করে |
|---|---|
| Long Method | condition আর branch-গুলো method-এ বিভক্ত করা host method-কে নাটকীয়ভাবে ছোট করে |
| Comments | condition ব্যাখ্যা করা comment টা extracted method-এর নামে পরিণত হয় |
| Duplicate Code | isMonsoon-এর মতো নামযুক্ত predicate raw test আবার টাইপ করার বদলে reuse করা যায় |
এটা বড় পদক্ষেপের জন্যও ক্ষেত্র প্রস্তুত করে: branch-গুলো named method হয়ে গেলে Replace Conditional with Polymorphism-এর মতো pattern প্রয়োগ করা অনেক সহজ।
দ্রুত revision box
+--------------------------------------------------------------+
| DECOMPOSE CONDITIONAL — CHEAT SHEET |
+--------------------------------------------------------------+
| Problem : a complicated if/else hides what is being decided |
| Move : Extract Method x3 |
| 1. condition -> question name (isRainyDay) |
| 2. then-part -> outcome name (rainyDayPlan) |
| 3. else-part -> outcome name (normalDayPlan) |
| Test : run the suite after EVERY extraction |
| Result : the if reads like a sentence |
| Skip if : condition is already tiny and clear |
| Cures : Long Method, Comments |
+--------------------------------------------------------------+অনুশীলন
ধরো একটা বিজ্ঞান জাদুঘরের app-এ এই জগাখিচুড়ি ticket-price function আছে। তোমার কাজ: প্রতিটা extraction-এর পরে test চালিয়ে ধাপে ধাপে Decompose Conditional করো।
// Make this read like a sentence!
function ticketPrice(age: number, day: number, hasSchoolPass: boolean): number {
let price: number;
if ((day === 0 || day === 6) && !(age < 5) && !hasSchoolPass && age < 60) {
price = 200 + 50; // weekend rate plus crowd surcharge
} else {
price = age < 5 || age >= 60 ? 0 : hasSchoolPass ? 50 : 120;
}
return price;
}কিছু hint:
- বড় condition টাকে একটা question method-এ extract করো। এটা আসলে কী জিজ্ঞেস করছে?
paysWeekendRate(...)এর মতো কিছু — একটা নাম খোঁজো যা ভালো পড়া যায়। - else-branch-এর nested ternary আরো দুটো ধারণা লুকিয়ে রেখেছে:
isFreeEntry(age)আর একটা school-pass concession। Extract করো আর নাম দাও। 200 + 50কেweekendPrice()তে extract করো যাতে surcharge comment অপ্রয়োজনীয় হয়।ticketPriceকে এক বা দুটো পাঠযোগ্য লাইনে collapse করে শেষ করো।- কলেজ bonus:
!(age < 5) && age < 60তে De Morgan's law প্রয়োগ করো আর যাচাই করো তোমার সরলীকৃত form একটা ইতিবাচকভাবে নামযুক্ত predicate যেমনpaysAdultRate(age)এর সাথে মেলে।
যখন তোমার চূড়ান্ত ticketPrice একজন বন্ধুকে জোরে পড়ে শোনাতে পারবে আর সে এক টানে বুঝবে — তুমি আজকের কাজ শেষ করেছো। প্রতিবার রাহেলা ম্যাডামের কথা মনে করো: নিয়ম পরিবর্তন করো না, শুধু প্রশ্নটাকে একটা নাম দাও যা পুরো স্কুল বলতে পারে। Happy refactoring!
সচরাচর জিজ্ঞাসা
- Decompose Conditional আসলে কী করে?
- একটা জটিল if/else কে নামযুক্ত method-এ ভেঙে দেয়। condition টা হয় একটা method যার নাম একটা প্রশ্নের মতো — যেমন isRainyDaySchedule। then-branch আর else-branch প্রত্যেকটা আলাদা method হয়, নাম থেকেই বোঝা যায় ফলাফল কী। if নিজে থাকে, কিন্তু এখন সেটা পড়তে একটা সহজ বাক্যের মতো — আগের ধাঁধার মতো না।
- Decompose Conditional কি শুধু Extract Method?
- প্রায়! এটা Extract Method-এর উপরে বানানো একটা বিশেষ recipe। তিনবার Extract Method করো — একবার condition-এ, একবার then-অংশে, একবার else-অংশে। আসল দক্ষতা হলো নামকরণে: condition পাবে একটা প্রশ্নসূচক নাম, আর প্রতিটা branch পাবে ফলাফলের নাম।
- বেশি ছোট ছোট method যোগ করলে কি code ধীর হয়ে যাবে?
- সাধারণ program-এ না। আধুনিক compiler আর runtime ছোট method inline করতে খুব ভালো, তাই খরচ প্রায় শূন্য। ভবিষ্যতের প্রতিটা programmer যে পড়ার সময় বাঁচাবে, সেটা কয়েক nanosecond-এর চেয়ে অনেক বেশি দামি। শুধু proven hot path-এ চিন্তা করো — যেটা খুবই বিরল।
- extracted condition method-এর নাম কী রাখবো?
- হ্যাঁ বা না দিয়ে উত্তর দেওয়া যায় এমন প্রশ্নের মতো নাম দাও, যাতে if পড়তে স্বাভাবিক লাগে। ভালো pattern হলো isSomething, hasSomething, বা exceedsSomething — যেমন isRainyDaySchedule, hasExpired, বা exceedsLimit। যদি স্পষ্ট নাম না পাও, মানে হলো condition টা তুমি এখনো ভালো বোঝোনি — আগে বোঝো।
- কখন conditional decompose করা উচিত না?
- যখন conditional ইতিমধ্যেই ছোট আর পরিষ্কার, যেমন if (x > 0)। সেটা extract করলে পাঠকের জন্য শুধু একটা অতিরিক্ত লাফ তৈরি হয়, কোনো মানে যোগ হয় না। আর সাবধান থাকো যদি branch-গুলো অনেক local variable শেয়ার করে — তাহলে লম্বা parameter list পাঠাতে হবে। সেক্ষেত্রে আগে parameter object বা Extract Class করার কথা ভাবো।
আরো দেখো
সম্পর্কিত পাঠ
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।
Consolidate Conditional Expression: অনেক ছোট চেক, একটাই পরিষ্কার প্রশ্ন
স্কুল গেটের গল্প দিয়ে Consolidate Conditional Expression শেখো — TypeScript আর C# উদাহরণ, নিরাপদ ধাপ, আর side-effect-এর ফাঁদ যেটা না জানলেই নয়।
Consolidate Duplicate Conditional Fragments: মিষ্টির কাউন্টারটা বাইরে নিয়ে যাও
ক্যান্টিনের গল্প দিয়ে Consolidate Duplicate Conditional Fragments refactoring শেখো — TypeScript আর C# example, safety rules, আর সহজ step-by-step practice।
Remove Control Flag: পেয়ে গেলেই থেমে যাও
Remove Control Flag refactoring শেখো একজন দারোয়ানের গল্পের মাধ্যমে। TypeScript আর C# এর উদাহরণ দিয়ে বুঝবে break আর return কীভাবে control flag-এর জায়গা নেয়।