Consolidate Conditional Expression: অনেক ছোট চেক, একটাই পরিষ্কার প্রশ্ন
স্কুল গেটের গল্প দিয়ে Consolidate Conditional Expression শেখো — TypeScript আর C# উদাহরণ, নিরাপদ ধাপ, আর side-effect-এর ফাঁদ যেটা না জানলেই নয়।
তিনটা গেট চেকের গল্প
ধরো আজিমপুর সরকারি হাইস্কুলে সকালের অ্যাসেম্বলি, ঠিক সাতটা পঁয়তাল্লিশ। সপ্তম শ্রেণির ছাত্র রাহিম বাস থেকে নেমে মেইন গেটের লাইনে দাঁড়ায়। সামনে দেখে লাইন প্রায় বাস-বে পর্যন্ত চলে গেছে। কারণ গেটে তিনজন শিক্ষক দাঁড়িয়ে একের পর এক — যেন একটা ছোট রাস্তায় তিনটা টোল বুথ।
প্রথম শিক্ষক জনাব হাসান চেক করেন: "ইউনিফর্ম ঠিক নেই? বাড়ি যাও।"
রাহিম পাস করে। একটু এগিয়ে যায়। দ্বিতীয় শিক্ষক ম্যাডাম রহিমা চেক করেন: "আইডি কার্ড নেই? বাড়ি যাও।"
রাহিম পকেট চাপড়ে কার্ড বের করে দেখায়, আবার পাস। তৃতীয় শিক্ষক জনাব করিম চেক করেন: "কালো জুতো নেই? বাড়ি যাও।"
রাহিম নিজের চকচকে কালো জুতোর দিকে তাকায় আর শেষমেশ ঢুকতে পারে — বাস থেকে নামার এগারো মিনিট পরে। তার পেছনে বন্ধু সুমাইয়া তৃতীয় বুথে এসে আটকে যায়, প্রথম দুটো পার করার পর। এগারো মিনিট লাইনে দাঁড়িয়ে শেষ ধাপে ফেরত যেতে হলো তাকে।
একটু ভাবো — তিনটা আলাদা চেক। তিনজন আলাদা শিক্ষক। কিন্তু প্রতিটা চেকই একই ফলাফলে শেষ হচ্ছে: বাড়ি যাও। তিনটা আলাদা শাস্তি না, তিনটা আলাদা নিয়ম না। আসলে মাত্র একটাই সিদ্ধান্ত নেওয়া হচ্ছে — "এই ছাত্রের পোশাক কি ঠিকমতো নেই?" — কিন্তু সেটা তিনটা আলাদা টুপি পরে এসেছে।
নতুন সহকারী প্রধান শিক্ষক ম্যাডাম নাসরিন দুই সকাল stopwatch নিয়ে এই অবস্থা দেখলেন, তারপর একটাই পদক্ষেপে ঠিক করে ফেললেন। গেটের জন্য একটা laminated কার্ড বানালেন:
"যে ছাত্রের পোশাক ঠিকমতো নেই — অর্থাৎ: ইউনিফর্ম নেই, অথবা আইডি কার্ড নেই, অথবা কালো জুতো নেই — তাকে বাড়ি পাঠানো হবে।"
এখন একজন শিক্ষক গেটে দাঁড়িয়ে একটাই প্রশ্ন করেন: "এই ছাত্রের পোশাক কি ঠিকমতো নেই?" তিনটা চেক মিলিয়ে যায়নি। সেগুলো একটা নামযুক্ত ধারণার সংজ্ঞার ভেতরে চলে গেছে। গেটের logic এক লাইন হয়ে গেল, আর স্কুল অবশেষে সেটাই বলল যেটা শুরু থেকেই সত্য ছিল: এই তিনটা চেক আসলে একটাই নিয়ম। পরদিন সকালে রাহিম তিন মিনিটে ঢুকে পড়ে।
এখানে আরেকটা লুকানো সুবিধা আছে। গত বছর স্কুল "বাড়ি যাও" বদলে করেছিল "অফিসে যাও আর slip নাও"। কী হয়েছিল জানো? দুজন শিক্ষক আপডেট পেয়েছিলেন, একজন পাননি। তাই জুতো সমস্যায় বাড়ি যেতে হচ্ছিল আর ইউনিফর্ম সমস্যায় slip পাওয়া যাচ্ছিল। অভিভাবকরা অভিযোগ করলেন, কেউ বোঝাতে পারছিলেন না কেন। একটাই consolidated নিয়ম থাকলে ফলাফল এক জায়গায় থাকে — এই ধরনের আধা-আপডেটের সমস্যা হওয়াই সম্ভব না।
কোডে এই একই পদক্ষেপকে বলে Consolidate Conditional Expression।
Consolidate Conditional Expression কী?
সহজ কথায়: তুমি একই ফলাফল দেওয়া কয়েকটা conditional-কে একটাই মিলিত condition-এ একত্রিত করো, তারপর সেই condition-টাকে একটা নাম দাও — সেটাকে আলাদা method-এ বের করে এনে।
Martin Fowler-এর Refactoring বইতে এই technique Simplifying Conditional Expressions অধ্যায়ে আছে। তার বিখ্যাত উদাহরণে একটা disability payment হিসাব করা হয়। তিনটা আলাদা guard — কম seniority, অনেক মাস disabled, part-time worker — প্রতিটাই zero return করে। রিফ্যাক্টরিং-এর পরে তিনটা guard মিলে একটাই method call হয় যার নাম shared meaning বলে দেয়।
রিফ্যাক্টরিং-টার দুটো ভাগ আছে, দুটোই গুরুত্বপূর্ণ:
- একত্রিত করো। boolean operator দিয়ে condition-গুলো জোড়া লাগাও। Fowler-এর সূত্র: একই ফলাফলের আলাদা if-এর সমান্তরাল সিঁড়ি
||দিয়ে জোড়া লাগে (যেকোনো একটা কারণ যথেষ্ট), আর nested if-গুলো&&দিয়ে (সব condition একসাথে সত্য হতে হবে)। - নাম দাও। মিলিত expression-এ Extract Method apply করো আর concept অনুযায়ী নাম দাও, mechanics অনুযায়ী না —
isImproperlyDressed,checkUniformAndIdAndShoesনা।
নামকরণ কেন রিফ্যাক্টরিং-এর অর্ধেক? কারণ শুধু মিলিত expression মানে ছোট কোড। নামটাই ভবিষ্যতের পাঠককে বলে "এই চেকগুলো একটাই ধারণা"। আর একবার isImproperlyDressed তৈরি হলে detention report, parent SMS system, আর gate logic সবাই একই প্রশ্ন করতে পারে — বারবার তিনটা comparison টাইপ না করে।
কলেজ কর্নার: OR-বনাম-AND নিয়মটা শুধু কথার কথা না — এটা boolean algebra। guard-এর সমান্তরাল সিঁড়ি encode করে "যদি A সত্য হয়, বা B সত্য হয়, বা C সত্য হয় তাহলে বাড়ি পাঠাও" — যেটা হুবহু A OR B OR C। Nested if encode করে "ভেতরের কোড চালাও শুধু যখন A আর B আর C" — conjunction A AND B AND C। দুটো form একে অপরের dual, De Morgan-এর সূত্র দিয়ে সংযুক্ত: NOT (A OR B OR C) সমান (NOT A) AND (NOT B) AND (NOT C)। সেজন্যই "পোশাক ঠিক নেই" (লঙ্ঘনের OR) আর "পোশাক ঠিক আছে" (শর্তের AND) একই নিয়ম দুটো বিপরীত দিক থেকে দেখা। যখন consolidate করো, তুমি বেছে নিতে পারো কোনটা call site-এ ভালো পড়ায় — De Morgan গ্যারান্টি দেয় যে flip করা সবসময় সম্ভব।
একটাই লাইন মনে রাখো: যদি অনেক চেক একটাই ফলাফলে পৌঁছায়, সেগুলো একটাই প্রশ্ন — একত্রিত করো, তারপর প্রশ্নটার নাম দাও। একত্রিত করলে লাইন কমে; নাম দিলে বোঝাবুঝি বাড়ে।
কখন এটা দরকার?
Consolidate Conditional Expression দরকার যখন দেখবে:
- একই value return করা guard-এর সিঁড়ি। পাশাপাশি তিনটা if, প্রতিটা
0,null, বাfalsereturn করছে। এটাই textbook signal। বারবার একই ফলাফল হলো Duplicate Code-এর ক্ষুদ্র রূপ — আর সব duplication-এর মতোই, এটা "একটা আপডেট করলাম, অন্যটা ভুলে গেলাম" bug-এর আমন্ত্রণ। - একটা action ঘিরে nested if। একটাই statement guard করছে তিনটা nested test।
&&দিয়ে একটা condition-এ মেলাও, তারপর নাম দাও। - একই চেকের সেট কয়েকটা method-এ। যদি gate logic, report module, আর SMS module প্রতিটাই আলাদাভাবে uniform, ID, আর জুতো পরীক্ষা করে, তাহলে একটা নামযুক্ত predicate-এ consolidate করলে এক ধাক্কায় triplication দূর হয়।
- বেশিরভাগ guard চেকে ভরা লম্বা method। Consolidation দ্রুত Long Method ছোট করার উপায়: পাঁচ লাইন guard হয়ে যায় এক লাইন।
- চতুর্থ কারণ যোগ করতে যাচ্ছো। তিনটা ছড়িয়ে থাকা if-এ "বেল্ট নেই" যোগ মানে চতুর্থ ছড়িয়ে থাকা if।
isImproperlyDressed-এ যোগ মানে একটাই method-এর ভেতরে একটা extra clause — আর প্রতিটা caller বিনামূল্যে আপডেট পাবে।
আর কখন থামা উচিত?
- চেকগুলো আলাদা সিদ্ধান্ত, কাকতালীয়ভাবে একই ফলাফল দিচ্ছে। যদি "ফি বাকি" আর "মেডিকেল ছুটি" দুটোই এখন exam entry আটকায়, কিন্তু স্কুল পরের term-এ এগুলো আলাদাভাবে handle করতে পারে — তাহলে একসাথে গুলিয়ে ফেললে লুকিয়ে যায় যে এগুলো আলাদা নিয়ম। অর্থ consolidate করো, কাকতালীয় মিল না।
- একটা চেকে side effect আছে। কোনো condition যদি
logAttempt()ডাকে বা counter বদলায়, মিলিত expression-এ short-circuit evaluation সেটা skip করতে পারে। আগে side effect ঠিক করো। - মিলিত chain বিশাল হয়ে যাবে। একটা expression-এ দশটা OR করা clause নিজেই readability-র সমস্যা। কয়েকটা নামযুক্ত sub-predicate থেকে তৈরি করো — এখানে Decompose Conditional সাহায্য করে।
বিচারের দুটো মাপকাঠি: চেকগুলো অর্থের দিক থেকে কতটা সম্পর্কিত, আর ফলাফল কতটা এক। দুটোই উচ্চ হলে তবেই merge করো।
আগে-পরে এক নজরে
TypeScript-এ একটা fee-concession calculator। তিনটা আলাদা gate, একটাই shared ফলাফল:
interface Student {
attendancePercent: number;
feesPending: boolean;
disciplineCases: number;
annualMarks: number;
}
// BEFORE: three ifs, one repeated result — the single decision is invisible
function concessionAmount(s: Student): number {
if (s.attendancePercent < 75) {
return 0;
}
if (s.feesPending) {
return 0;
}
if (s.disciplineCases > 0) {
return 0;
}
return Math.round(s.annualMarks * 10);
}আর "পরে" — || দিয়ে একত্রিত করো, তারপর নাম দাও:
// AFTER: one question, asked once, answered in one place
function concessionAmount(s: Student): number {
if (isNotEligibleForConcession(s)) {
return 0;
}
return Math.round(s.annualMarks * 10);
}
function isNotEligibleForConcession(s: Student): boolean {
return s.attendancePercent < 75 || s.feesPending || s.disciplineCases > 0;
}function-টা এখন দুটো beat-এ তার গল্প বলে: "eligible না হলে কিছু না; নাহলে marks-এর দশগুণ।" আর eligibility নিয়মের ঠিক একটাই ঘর। স্কুল যদি "0" বদলে "ন্যূনতম ১০০ টাকা" করে, এখন তিনটার বদলে একটাই return 0 আপডেট করতে হবে।
আগের version মেপে দেখলে একটা চমকের ব্যাপার পাবে: বেশিরভাগ লাইনই guard plumbing, আর মাত্র একটা লাইন আসল কাজ করে।
নিরাপদ উপায়ে ধাপে ধাপে
চলো পূর্ণ শৃঙ্খলার সাথে concession উদাহরণটা refactor করি। একবারে একটাই ছোট ধাপ।
ধাপ ০ — পূর্বশর্ত নিশ্চিত করো। কিছু করার আগে দুটো প্রশ্ন। প্রথম: সব conditional কি সত্যিই একই ফলাফল দেয়? এখানে হ্যাঁ — সবই 0 return করে। দ্বিতীয়: কোনো চেকে side effect আছে? প্রতিটা পড়ো। attendancePercent < 75 — pure comparison। feesPending — pure read। disciplineCases > 0 — pure। এগিয়ে যাওয়া নিরাপদ।
Side effect এই refactoring-এর ফাঁদ। ধরো দ্বিতীয় চেকটা হতো if (recordFeeReminder(s)) return 0; — একটা function যেটা true return করে AND একটা SMS পাঠায়। || দিয়ে consolidation করার পরে, short-circuit evaluation মানে attendance চেকই যদি উত্তর দিয়ে দেয় তাহলে SMS আর যাবে না। কোড compile হবে, tests পাস হবে, আর behaviour চুপচাপ বদলে গেছে। নিয়ম: আগে query-কে modifier থেকে আলাদা করো, তারপর শুধু pure check-ই consolidate করো। আর সবসময়ের মতো — প্রতিটা ধাপের পরে tests চালাও।
কলেজ কর্নার: সেই warning নির্ভর করে short-circuit evaluation-এর উপর — C-family language, Java, C#, JavaScript, আর Python-এ এটা একটা semantic গ্যারান্টি। A || B-তে, A সত্য হলে runtime কখনো B evaluate করে না। A && B-তে, A মিথ্যা হলে কখনো B evaluate করে না। এটা compiler-এর optional optimization না — এটা language definition-এর অংশ। program legitimately এটার উপর নির্ভর করে, যেমন s !== null && s.marks > 40, যেখানে প্রথম clause shield না হলে দ্বিতীয় clause crash করত। উল্টো দিক হলো হুবহু আমাদের ফাঁদ: B-এর ভেতরে যেকোনো effect A-র value-এর উপর নির্ভরশীল হয়ে পড়ে যখনই জোড়া লাগাও। সেজন্যই short-circuit chain-এ clause reorder করা তখনই নিরাপদ যখন প্রতিটা clause pure।
ধাপ ১ — প্রথম দুটো conditional OR দিয়ে merge করো। সবচেয়ে ছোট সম্ভব পদক্ষেপ:
// Step 1: INTERMEDIATE — two of three checks merged
function concessionAmount(s: Student): number {
if (s.attendancePercent < 75 || s.feesPending) {
return 0;
}
if (s.disciplineCases > 0) {
return 0;
}
return Math.round(s.annualMarks * 10);
}Tests চালাও। সবুজ।
ধাপ ২ — তৃতীয় conditional merge করো।
// Step 2: INTERMEDIATE — one combined condition, still inline
function concessionAmount(s: Student): number {
if (s.attendancePercent < 75 || s.feesPending || s.disciplineCases > 0) {
return 0;
}
return Math.round(s.annualMarks * 10);
}আবার tests চালাও। সবুজ। দেখো আমরা ইতিমধ্যে দুটো duplicate return 0 সরিয়ে ফেলেছি।
ধাপ ৩ — মিলিত condition extract করো আর নাম দাও। পুরো boolean expression select করো আর Extract Method apply করো:
// Step 3: FINAL — the question has a name and a home
function concessionAmount(s: Student): number {
if (isNotEligibleForConcession(s)) {
return 0;
}
return Math.round(s.annualMarks * 10);
}
function isNotEligibleForConcession(s: Student): boolean {
return s.attendancePercent < 75 || s.feesPending || s.disciplineCases > 0;
}আরেকবার tests চালাও।
ধাপ ৪ — ভালো ঘর খোঁজো। জিজ্ঞেস করো: এই predicate কি কোনো domain object-এ থাকা উচিত? যদি Student একটা class হয়, student.isEligibleForConcession() অন্য module-কেও সাহায্য করতে পারে। সেটা সরানো একটা আলাদা ছোট refactoring (Move Method) — নিজের ধাপে, নিজের test run সহ করো।
AND variant। যখন if-গুলো sequential-এর বদলে nested, একই recipe && ব্যবহার করে। দেখো:
// BEFORE: nested ifs guarding one action
if (s.attendancePercent >= 75) {
if (!s.feesPending) {
if (s.disciplineCases === 0) {
grantConcession(s);
}
}
}
// AFTER: all conditions must hold — joined with AND, then named
if (isEligibleForConcession(s)) {
grantConcession(s);
}একটা elegant জিনিস লক্ষ করো: isEligibleForConcession হুবহু NOT isNotEligibleForConcession, আর De Morgan-এর সূত্র একটার body যান্ত্রিকভাবে অন্যটায় রূপান্তর করে। attendancePercent >= 75 && !feesPending && disciplineCases === 0 হলো attendancePercent < 75 || feesPending || disciplineCases > 0-এর clause-by-clause negation। যেটা call করার জায়গায় ভালো পড়ায় সেটা বেছে নাও। একটাকে অন্যটার negation হিসেবে define করো যাতে logic শুধু একবারই থাকে।
পুনরাবৃত্তিযোগ্য recipe, state হিসেবে:
একটা বড় বাস্তব উদাহরণ
এবার স্কুলের গেট ঠিকমতো code করা যাক। "আগে" version-টা হুবহু যেভাবে এই ধরনের কোড বাস্তব project-এ বাড়ে — প্রতি term-এ একটা করে চেক যোগ হয়েছে, তিনজন আলাদা programmer, প্রত্যেকে আগেরটার pattern copy করেছে:
interface GateStudent {
hasProperUniform: boolean;
hasIdCard: boolean;
shoeColor: string;
name: string;
}
type GateResult = { allowed: boolean; message: string };
// BEFORE: three teachers, three toll booths, one repeated outcome
function checkAtGate(s: GateStudent): GateResult {
if (!s.hasProperUniform) {
return { allowed: false, message: `${s.name}: report to the office.` };
}
if (!s.hasIdCard) {
return { allowed: false, message: `${s.name}: report to the office.` };
}
if (s.shoeColor !== "black") {
return { allowed: false, message: `${s.name}: report to the office.` };
}
return { allowed: true, message: `${s.name}: welcome!` };
}ঠিক করার আগে বিপদটা খুঁজে বের করো: সেই rejection message তিনবার লেখা। যেদিন কেউ "report to the office" বদলে "class teacher-এর কাছে যাও" করবে দুটো জায়গায়, স্কুলের gate inconsistent হয়ে যাবে — হুবহু আমাদের গল্পের half-update bug।
Consolidate করো, তারপর নাম দাও:
// AFTER: one named question, one outcome, zero duplication
function checkAtGate(s: GateStudent): GateResult {
if (isImproperlyDressed(s)) {
return { allowed: false, message: `${s.name}: report to the office.` };
}
return { allowed: true, message: `${s.name}: welcome!` };
}
function isImproperlyDressed(s: GateStudent): boolean {
return !s.hasProperUniform || !s.hasIdCard || s.shoeColor !== "black";
}দুটো function, প্রতিটার একটাই পরিষ্কার কাজ। checkAtGate ঠিক করে কী হবে; isImproperlyDressed dress code সংজ্ঞায়িত করে। গেট এখন ম্যাডাম নাসরিনের laminated কার্ডের মতো কাজ করে — একটা প্রশ্ন করা, একটা উত্তর পাওয়া:
পরের বছর স্কুল "বেল্ট নেই" যোগ করলে, শুধু definition-এ হাত দেবে:
// Next year's change: ONE line, in ONE place
function isImproperlyDressed(s: GateStudent & { hasBelt: boolean }): boolean {
return !s.hasProperUniform || !s.hasIdCard || s.shoeColor !== "black" || !s.hasBelt;
}আর predicate-টা এখন একটা নামযুক্ত, callable জিনিস বলে monthly discipline report সেটা reuse করতে পারে — students.filter(isImproperlyDressed) — তিনটা চেক বারবার টাইপ না করে। বড় system-এ predicate-টা প্রায়ই নিজেই একটা ছোট dress-code policy হয়ে যায়, উত্তর দরকার এমন প্রতিটা module সেবা করে:
সেই class-এ sub-predicate hasUniformIssue আর hasFootwearIssue লক্ষ করো। OR chain চার-পাঁচটার বেশি clause হয়ে গেলে, সম্পর্কিত clause-গুলো নামযুক্ত sub-question-এ গোষ্ঠীবদ্ধ করো আর সেগুলো OR করো। তখন top predicate-টা পড়ে মনে হয় table of contents — ঠিক Decompose Conditional-এর কৌশল, consolidated নিয়মের ভেতরে apply করা।
C# আর Python-এ একই refactoring
C#-তে disability-payment-এর classic shape:
// BEFORE: three guards, one shared result
decimal DisabilityAmount(Employee e)
{
if (e.Seniority < 2) return 0m;
if (e.MonthsDisabled > 12) return 0m;
if (e.IsPartTime) return 0m;
return e.BaseAmount * e.Seniority * 0.05m;
}
// AFTER: combined with OR, extracted, and named for the concept
decimal DisabilityAmount(Employee e)
{
if (IsNotEligibleForDisability(e)) return 0m;
return e.BaseAmount * e.Seniority * 0.05m;
}
bool IsNotEligibleForDisability(Employee e) =>
e.Seniority < 2 || e.MonthsDisabled > 12 || e.IsPartTime;C#-তে extract করা predicate প্রায়ই Employee class-এ property হিসেবে থাকতে চায় — e.IsEligibleForDisability — যেখানে HR screen, payroll, আর report সবাই share করতে পারে। এটাই আমাদের walkthrough-এর "ভালো ঘর খোঁজো" ধাপ। এখানেই consolidation চুপচাপ তিনটা ছড়িয়ে থাকা comparison-কে একটা সত্যিকারের domain concept-এ রূপান্তরিত করে।
Python-এর any আর all built-in consolidated form-কে বিশেষভাবে expressive করে — এগুলো হলো একটা নামযুক্ত কারণের list-এর উপর OR আর AND:
# AFTER, the Python way: the reasons become a readable list
def is_improperly_dressed(student):
violations = [
not student.has_proper_uniform,
not student.has_id_card,
student.shoe_color != "black",
]
return any(violations)
def is_properly_dressed(student):
return not is_improperly_dressed(student) # De Morgan, applied once, lives onceকলেজ কর্নার: any আর all সরাসরি predicate logic-এর সাথে সংযুক্ত। any(violations) হলো existential quantifier — "অন্তত একটা লঙ্ঘন আছে" — আর all(requirements) হলো universal quantifier — "প্রতিটা শর্ত পূরণ হয়েছে"। De Morgan-এর সূত্র quantifier-এও কাজ করে: not (কোনো লঙ্ঘন আছে) সমান (প্রতিটা item লঙ্ঘনমুক্ত)। যখন is_properly_dressed-কে is_improperly_dressed-এর negation হিসেবে define করো, তুমি সেই সূত্র একবার, এক জায়গায় apply করছো — তিনটা clause হাতে নেগেট না করে। হাতে flip করাতেই off-by-one logic bug ঢুকে পড়ে (> বনাম >=); একটা predicate-কে নিয়ম-মালিক বানালে পুরো bug class-টাই দূর হয়।
IDE সাপোর্ট
একটা "Consolidate Conditional" button নেই, কিন্তু refactoring-এর দুটো ভাগ IDE feature-এ সুন্দরভাবে map হয়:
| কাজ | Visual Studio / Rider | IntelliJ IDEA | VS Code |
|---|---|---|---|
| Sequential if merge | Ctrl+. → "Merge consecutive if statements" (available হলে) | Alt+Enter → "Merge sequential ifs" intention | Manual edit, tests দিয়ে নির্দেশিত |
| Nested if merge | Ctrl+. quick actions | Alt+Enter → "Merge nested ifs" | Manual edit |
| Condition extract ও নামকরণ | Ctrl+R, Ctrl+M (Extract Method) | Ctrl+Alt+M (Extract Method) | Ctrl+. → "Extract to function" |
IntelliJ-family IDE (Rider আর WebStorm সহ) এখানে বিশেষভাবে সহায়ক। কোনো if-এ cursor রেখে Alt+Enter চাপলে nested বা sequential if merge করার intention দেখা যায়, যেগুলো typo ছাড়াই boolean algebra করে দেয়। Merge করার পরে একটা Extract Method shortcut কাজ শেষ করে। Smart tooling থাকলেও অভ্যাস রাখো: merge ধাপের পরে আর extract ধাপের পরে tests চালাও।
সুবিধা আর ঝুঁকি
ম্যাডাম নাসরিন যে stopwatch বহন করতেন সেটার কথা মনে করো। গেটে পরিমাপযোগ্য জয় ছিল লাইনের সময়। কোডে পরিমাপযোগ্য জয় হলো একজন পাঠক কত দ্রুত উত্তর দিতে পারে "কোন কোন অবস্থায় এই method zero return করে?" তিনটা ছড়িয়ে থাকা guard থাকলে পাঠককে সেগুলো সংগ্রহ করে সম্পর্ক বের করতে হয়। একটা নামযুক্ত predicate থাকলে উত্তর এক click দূরে।
| সুবিধা | কেন গুরুত্বপূর্ণ |
|---|---|
| একটাই সিদ্ধান্ত দৃশ্যমান করে | পাঠক "একটাই প্রশ্ন" দেখে, তিনটা চেক সম্পর্কিত কিনা অনুমান করতে হয় না |
| Duplicate ফলাফল দূর করে | Shared outcome এক জায়গায় থাকে; half-update bug অদৃশ্য হয় |
| Reusable predicate তৈরি করে | অন্য module isImproperlyDressed ডাকতে পারে, চেক আবার টাইপ না করে |
| Method ছোট করে | পাঁচ লাইন guard হয়ে যায় এক পড়ার যোগ্য লাইন |
| আরো refactoring সহজ করে | একটাই নামযুক্ত condition invert, simplify, বা move করা সহজ |
| ঝুঁকি | কীভাবে সামলাবে |
|---|---|
| Short-circuiting-এ side effect বাদ পড়া | একত্রিত করার আগে প্রতিটা চেক pure কিনা নিশ্চিত করো; আগে query-কে modifier থেকে আলাদা করো |
| অসম্পর্কিত নিয়ম জুড়ে দেওয়া | শুধু সত্যিকারের একটাই concept-এর চেকই consolidate করো; কাকতালীয় মিল পরে ভিন্ন হবে |
| বিশাল boolean chain | কয়েকটা নামযুক্ত sub-predicate থেকে predicate তৈরি করো, একটা বিশাল expression না |
| ভুল operator | Sequential same-result if-এ OR দরকার; nested if-এ AND দরকার — merge করার পরে test করো |
কখন করবে না: যদি দুটো চেক শুধু কাকতালীয়ভাবে একই ফলাফল share করে, বা side effect সহ evaluation order গুরুত্বপূর্ণ হয়, সেগুলো আলাদা রাখো। স্বাধীনতা সম্পর্কে স্পষ্টতাও এক ধরনের স্পষ্টতা।
কোন smell-গুলো দূর করে?
| Code smell | Consolidate Conditional Expression কীভাবে সাহায্য করে |
|---|---|
| Duplicate Code | Module জুড়ে বারবার আসা result লাইন আর চেক pattern সরিয়ে দেয় |
| Long Method | Guard-এর সিঁড়ি একটা পড়ার যোগ্য লাইনে পরিণত করে |
| Comments | Predicate-এর নাম চেকগুলো কী মানে বোঝাতো সেই comment-এর জায়গা নেয় |
দ্রুত revision বক্স
+----------------------------------------------------------------+
| CONSOLIDATE CONDITIONAL EXPRESSION — CHEAT SHEET |
+----------------------------------------------------------------+
| Signal : several ifs, SAME result each time |
| Step 0 : checks must be pure (no side effects!) |
| Combine : flat series -> join with OR (any reason is enough) |
| nested ifs -> join with AND (all must hold) |
| Name : Extract Method -> isImproperlyDressed, isNotEligible |
| De Morgan: NOT(A OR B) = (NOT A) AND (NOT B) — flip freely |
| Test : after the merge AND after the extract |
| Skip if : checks are independent rules that just look alike |
| Cures : Duplicate Code, Long Method |
+----------------------------------------------------------------+অনুশীলন করে দেখো
ধরো একটা library app ঠিক করতে হবে — কোনো ছাত্র নতুন বই নিতে পারবে কিনা। Consolidate Conditional Expression দিয়ে পরিষ্কার করো:
// Consolidate me!
function canBorrow(m: Member): boolean {
if (m.overdueBooks > 0) {
return false;
}
if (m.finesPending > 0) {
return false;
}
if (!m.cardValid) {
return false;
}
if (m.booksOnLoan >= 3) {
return false;
}
return true;
}তোমার checklist:
- নিশ্চিত করো চারটা চেকই pure আর সবই একই ফলাফলে পৌঁছায়। (হ্যাঁ, পৌঁছায়।)
||দিয়ে দুটো করে merge করো, প্রতিটা merge-এর পরে tests চালাও।- মিলিত condition-টাকে একটা ভালো নামের predicate-এ extract করো — হয়তো
hasBorrowingBlock(m)বাisNotInGoodStanding(m)। যে নামটা call site-এ সবচেয়ে ভালো পড়ায় সেটা বেছে নাও। - Bonus:
canBorrowকি এখন কোনোifছাড়াই একটামাত্রreturnstatement হতে পারে? - কলেজ bonus: De Morgan-এর সূত্র clause-by-clause তোমার OR predicate-এ apply করে
isInGoodStanding(m)লেখো। প্রতিটা flipped comparison সাবধানে পরীক্ষা করো —>= 3নেগেট হয়< 3,<= 3না। - Super bonus: যদি
Memberএকটা class হয়, predicate-টা আসলে কোথায় থাকা উচিত?
তোমার চূড়ান্ত version যদি ম্যাডাম নাসরিনের এক-কার্ড নিয়মের মতো পড়ায় — একটা প্রশ্ন, একটা উত্তর, সংজ্ঞার একটাই ঘর — তাহলে আজকের পাঠ তুমি আয়ত্ত করে ফেলেছো। চমৎকার!
সচরাচর জিজ্ঞাসা
- Consolidate Conditional Expression জিনিসটা এক লাইনে বলো?
- যখন কয়েকটা আলাদা চেক সবই একই ফলাফলে শেষ হয়, তখন সেগুলো AND বা OR দিয়ে একটা condition-এ জোড়া লাগাও। তারপর সেই মিলিত condition-টাকে একটা অর্থপূর্ণ নামের method-এ বের করে আনো। অনেক চেক হয়ে যায় একটাই পরিষ্কার প্রশ্ন।
- কখন OR দিয়ে জোড়া লাগাবো, কখন AND দিয়ে?
- OR ব্যবহার করো যখন একের পর এক আলাদা if-statement থাকে আর সবগুলো একই ফলাফলে পৌঁছায় — যেকোনো একটা কারণ যথেষ্ট, যেমন ড্রেস কোড লঙ্ঘনের যেকোনো একটা কারণেই স্কুল থেকে ফেরত পাঠানো হয়। AND ব্যবহার করো যখন if-গুলো একটার ভেতরে আরেকটা nested — সব condition একসাথে সত্য হলে তবেই ভেতরের কোড চলবে।
- এই refactoring-এ side-effect-এর বিপদটা কী?
- কোনো একটা চেক যদি এমন কোনো function ডাকে যেটা কিছু বদলে দেয় — রেকর্ড save করে, counter বাড়ায়, বা SMS পাঠায় — তাহলে short-circuit operator দিয়ে চেকগুলো জোড়া লাগালে আগের condition-ই উত্তর দিলে সেই call আর হবে না। behaviour চুপচাপ বদলে যাবে। একত্রিত করার আগে সবসময় নিশ্চিত হও যে চেকগুলো pure, মানে শুধু পড়ে আর তুলনা করে।
- একই value return করা সব চেক কি সবসময় একত্রিত করা উচিত?
- না। একত্রিত করো শুধু তখনই যখন চেকগুলো সত্যিই একটাই সিদ্ধান্তের অংশ। দুটো চেক আজকে একই ফলাফল দিলেও যদি সেগুলো আলাদা business rule প্রতিনিধিত্ব করে এবং ভবিষ্যতে আলাদা হতে পারে, তাহলে আলাদা রাখো। Consolidation মানে অর্থের বিবৃতি, শুধু জায়গা বাঁচানো না।
- মিলিত condition হয়ে গেছে OR-এর বিশাল chain। এখন কী করবো?
- কয়েকটা মাঝারি নামযুক্ত predicate-এ ভাগ করো আর সেগুলো মেলাও। যেমন, isImproperlyDressed নিজেই হতে পারে hasUniformIssue আর hasFootwearIssue মিলিয়ে। sub-group নাম দিলে logic পড়া সহজ থাকে। Decompose Conditional আর Consolidate Conditional Expression একসাথে দারুণ কাজ করে।
আরো দেখো
- Consolidate Conditional Expression — Refactoring Guru article
- Consolidate Conditional Expression — Fowler's Refactoring Catalog article
- Refactoring (2nd Edition) by Martin Fowler book
- Simplifying Conditional Expressions — Refactoring Guru article
- Consolidate Conditional Expression — SourceMaking article
সম্পর্কিত পাঠ
Decompose Conditional: জটিল if-কে সহজ নামে ভেঙে ফেলো
Decompose Conditional refactoring শেখো স্কুলের নোটিশের গল্প দিয়ে — সহজ TypeScript ও C# উদাহরণ, নিরাপদ ধাপ, আর IDE shortcut সহ।
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।
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-এর জায়গা নেয়।