এই পর্বে যা শিখবেন
if/else if/elseকীভাবে পথ বেছে নেয়, আর কেন সবসময় বন্ধনী{ }দেওয়া উচিত- শর্তে truthiness —
if (count),if (arr),if (str)কোথায় ঠকায়, আর ঠিক যাচাই কীভাবে লিখবেন - else-if-এর ক্রম, guard clause, early return আর ternary — কখন কোনটি
switch-এর===তুলনা,break, fall-through,default,switch (true)আর case-এর ভেতরের scope- switch-এর বদলে object বা
Mapদিয়ে lookup table, আর ইনপুট যাচাই করে তারপর সিদ্ধান্ত
১ · প্রোগ্রাম যখন পথ বেছে নেয় — if, else if, else
এ পর্যন্ত আমাদের প্রোগ্রাম চলেছে সোজা লাইনে — ওপর থেকে নিচে, প্রতিটি statement একবার করে। কিন্তু বাস্তব প্রোগ্রাম প্রতিনিয়ত সিদ্ধান্ত নেয়। অনলাইন দোকান দেখে ঠিকানা ঢাকার ভেতরে না বাইরে, তারপর ডেলিভারি চার্জ ঠিক করে। ফলাফলের ওয়েবসাইট নম্বর দেখে গ্রেড বসায়। লগইন পাতা দেখে পাসওয়ার্ড মিলল কি না।
প্রতিটি সিদ্ধান্তের গঠন একই: একটি প্রশ্ন, যার উত্তর হ্যাঁ বা না; উত্তর অনুযায়ী একটি পথ। JavaScript-এ এর সবচেয়ে মৌলিক রূপ if…else:
const city = "ঢাকা";
let charge;
if (city === "ঢাকা") {
charge = 60; // ঢাকার ভেতরে (উদাহরণের মান)
} else {
charge = 120; // ঢাকার বাইরে
}বন্ধনীর ( ) ভেতরে থাকে শর্ত — একটি expression, যার মান হিসাব করা হয়। মান সত্য হলে প্রথম block { … } চলে, না হলে else-এর block. দুটি কখনো একসাথে চলে না, আর একটি অবশ্যই চলে। এখানে তুলনায় === ব্যবহার করেছি — পর্ব ০৬-এ দেখেছেন কেন == এড়িয়ে চলা ভালো।
দুইয়ের বেশি পথ হলে else if দিয়ে শর্তের সারি বানানো হয়। ধরুন সিনেমা হলের টিকিটের দাম বয়স অনুযায়ী (উদাহরণের মান): ৩ বছরের কম হলে বিনামূল্যে, ১২-এর কম হলে অর্ধেক, ৬০ বা বেশি হলে ছাড়, বাকিদের পুরো দাম।
function ticketPrice(age) {
if (age < 3) {
return 0;
} else if (age < 12) {
return 150;
} else if (age >= 60) {
return 200;
} else {
return 300;
}
}Engine শর্তগুলো ওপর থেকে একটি একটি করে দেখে। যেটি প্রথম সত্য হয়, তার block চলে — বাকি শর্ত আর দেখাই হয় না। কোনোটি সত্য না হলে শেষের else. শেষে else রাখা বাধ্যতামূলক নয়, কিন্তু না রাখলে “কোনোটিই মিলল না” অবস্থায় কিছুই ঘটে না — সেটা ইচ্ছাকৃত কি না, ভেবে দেখুন। অংশ ৩-এ দেখবেন, এই “প্রথম সত্যটিই জেতে” নিয়ম কীভাবে বাগের জন্ম দেয়।
বন্ধনী ছাড়া if — এক লাইনের ফাঁদ
Block-এ যদি মাত্র একটি statement থাকে, JavaScript বন্ধনী ছাড়াও মেনে নেয়: if (ok) save();. সমস্যা হয় পরে, যখন কেউ নিচে আরেকটি লাইন যোগ করে:
if (isAdmin)
showPanel();
deleteAllUsers(); // দেখতে if-এর ভেতরে, আসলে সবার জন্য চলে!ইনডেন্টেশন JavaScript-এর কাছে অর্থহীন (Python-এর মতো নয়)। বন্ধনী না থাকলে if-এর অধীনে থাকে শুধু ঠিক পরের একটি statement. তৃতীয় লাইনটি দেখতে ভেতরে, কিন্তু চলে সবসময়। ২০১৪ সালে Apple-এর একটি বড় নিরাপত্তা-ত্রুটি ঠিক এই ধরনের এক লাইনের কারণে হয়েছিল — পঞ্চম ভাবনার প্রশ্নে বিস্তারিত। তাই অভ্যাস করুন: সবসময় বন্ধনী. ESLint-এর curly নিয়ম আর Prettier-এর মতো ফরম্যাটার এই ভুল চোখের সামনে এনে দেয়।
ঢাকার মোড়ে ট্রাফিক পুলিশ ভাবুন। প্রতিটি গাড়ি এলে তিনি একটি প্রশ্ন করেন — “এটা কি অ্যাম্বুলেন্স?” হ্যাঁ হলে সাথে সাথে ছেড়ে দেন, আর কোনো প্রশ্ন নয়। না হলে পরের প্রশ্ন — “এটা কি বাস?” তারপর “রিকশা?” — একটি উত্তর হ্যাঁ হলেই সিদ্ধান্ত শেষ। else-if সারি ঠিক এভাবে চলে। আর প্রশ্নের ক্রম ভুল হলে (আগে “এটা কি চার চাকার গাড়ি?” জিজ্ঞেস করলে) অ্যাম্বুলেন্সও সাধারণ গাড়ির লাইনে দাঁড়িয়ে যাবে।
২ · শর্তে যেকোনো মান — truthiness-এর সুবিধা আর ফাঁদ
Java বা C#-এ শর্তে অবশ্যই boolean দিতে হয় — if (count) লিখলে compile-ই হবে না। JavaScript এ ব্যাপারে উদার: শর্তে যেকোনো মান দেওয়া যায়। Engine মানটিকে নিজে থেকে boolean-এ বদলে নেয় (এটাও একধরনের coercion). পর্ব ০৮-এ পুরো তালিকা দেখেছেন; এখানে শুধু মনে করিয়ে দিই — ঠিক এই কয়েকটি মান falsy, অর্থাৎ শর্তে মিথ্যা হিসেবে গণ্য:
false · 0 · -0 · 0n · "" · null · undefined · NaNবাকি সব —
"0", "false", [], {} সহ — truthyএর সুবিধা স্পষ্ট: if (user) লিখেই বোঝা যায় user আছে কি না। কিন্তু বিপদও ঠিক এখানে — প্রশ্ন একটা লিখছেন, engine উত্তর দিচ্ছে আরেকটার। if (qty) লিখে আপনি হয়তো জানতে চাইছেন “পরিমাণের তথ্য আছে কি?”, অথচ engine জিজ্ঞেস করছে “qty কি falsy মানগুলোর একটি নয়?” — আর 0 একটি বৈধ পরিমাণ হয়েও falsy. নিচের ডায়াগ্রামে একই function-এ চারটি ভিন্ন মান পাঠিয়ে দেখুন:
1function stockText(qty) {2 if (qty) {3 return "স্টকে আছে: " + qty;4 }5 return "স্টকের তথ্য নেই";6}
| qty | |
| Boolean(qty) | |
| ফল ঠিক? |
- qty = 0 — বৈধ মান, ভুল পথ। দোকানে পণ্যটি শেষ, তাই stock
0. কিন্তু0falsy, তাই শর্ত মিথ্যা, আর লেখা এল “স্টকের তথ্য নেই”। ক্রেতা ভাববেন সিস্টেমে গোলমাল; আসলে উচিত ছিল “স্টক শেষ” দেখানো। - qty = "0" — string, তাই truthy. একই মান form বা API থেকে string হয়ে এলে (
"0") সেটি ফাঁকা নয়, তাই truthy. শর্ত সত্য — “স্টকে আছে: 0”. দুই ধরনের ইনপুটে একই তথ্য, দুই রকম আচরণ। - qty = [] — ফাঁকা array-ও truthy. প্রতিটি object (array-ও object) truthy, ভেতরে কিছু থাকুক বা না থাকুক। তাই
if (items)দিয়ে “তালিকায় কিছু আছে কি না” কখনো যাচাই হয় না — লিখতে হয়items.length > 0. - qty = null — এখানে falsy-ই ঠিক। তথ্যই নেই, তাই “স্টকের তথ্য নেই” সঠিক। Truthiness এখানে কাজ করেছে — কিন্তু কাকতালীয়ভাবে, কারণ এই শর্ত
0আরnull-কে আলাদা করতে পারে না। - সমাধান — প্রতিটি প্রশ্ন আলাদা করে লেখা। আগে “তথ্য আছে কি?” (
qty == null— এই একটি জায়গায়==ইচ্ছাকৃত, এটি null ও undefined দুটোই ধরে), তারপরNumber(qty)করে “বৈধ সংখ্যা কি?” (Number.isInteger), তারপর “শূন্য কি?” (n === 0). কোড একটু লম্বা, কিন্তু প্রতিটি লাইন ঠিক যা জিজ্ঞেস করতে চান তা-ই জিজ্ঞেস করে।
| যা লিখতে ইচ্ছা করে | কোথায় ঠকায় | যা আসলে জানতে চান |
|---|---|---|
if (count) | 0 বৈধ হলেও মিথ্যা | if (count !== undefined) বা count > 0 |
if (arr) | ফাঁকা array-ও সত্য | if (arr.length > 0) |
if (name) | " " (শুধু space) সত্য | if (name.trim() !== "") |
if (price) | NaN মিথ্যা, কিন্তু “ফ্রি পণ্য” 0-ও মিথ্যা | Number.isFinite(price) |
if (obj) | ফাঁকা {}-ও সত্য | Object.keys(obj).length > 0 |
Truthiness ব্যবহার করুন তখনই, যখন সব falsy মানের জন্য একই আচরণ সত্যিই চান — যেমন if (user), যেখানে user হয় একটি object, নয়তো null. মানটি সংখ্যা বা string হতে পারলে স্পষ্ট তুলনা লিখুন। কোনো মান না থাকলে ডিফল্ট বসাতে ||-এর বদলে ?? ব্যবহার করুন — qty ?? 1 শুধু null/undefined-এ 1 বসায়, 0-কে রেখে দেয় (পর্ব ০৮)।
সিদ্ধান্তের আগে ইনপুট যাচাই
Form-এর ঘর বা URL থেকে আসা মান সবসময় string. Number("৫০") (বাংলা অঙ্ক) বা Number("abc") দেয় NaN, আর NaN-এর সাথে যেকোনো তুলনা (>=, <, এমনকি ===) মিথ্যা। ফলে একটি গ্রেড-সারিতে NaN ঢুকলে সব শর্ত পেরিয়ে সোজা শেষের else-এ পৌঁছায় — অবৈধ ইনপুট চুপচাপ “F” হয়ে যায়। তাই সিদ্ধান্তের শুরুতে আগে যাচাই: Number.isNaN(m), পরিসীমা (০–১০০) — তারপর গ্রেড। (NaN নিয়ে বিস্তারিত পর্ব ১০-এ।)
৩ · else-if সারির ক্রম — প্রথম সত্যটিই জেতে
বোর্ড পরীক্ষার ধাঁচে একটি গ্রেড-স্কেল ধরুন (উদাহরণ হিসেবে — প্রতিষ্ঠানভেদে স্কেল আলাদা হতে পারে): ৮০–১০০ A+, ৭০–৭৯ A, ৬০–৬৯ A-, ৫০–৫৯ B, ৪০–৪৯ C, ৩৩–৩৯ D, ৩৩-এর নিচে F. প্রতিটি শর্ত আলাদাভাবে ঠিক — m >= 80 মানে A+, m >= 33 মানে পাস। কিন্তু সারিতে এদের ক্রম ভুল হলে কী হয়, দেখুন:
1function gradeWrong(m) {2 if (m >= 33) return "D";3 else if (m >= 80) return "A+";4 else return "F";5}6function grade(m) {7 if (m >= 80) return "A+";8 else if (m >= 70) return "A";9 // … A-, B, C, D একইভাবে10 else return "F";11}
| কল | |
| শর্ত | |
| ফল |
- ৯৫ নম্বর, ভুল ক্রমের function.
gradeWrong(95)ডাকা হলো। প্রথম শর্তে আছে সবচেয়ে “ঢিলা” প্রশ্ন — পাস করেছে কি? - প্রথম শর্তই সত্য। ৯৫ অবশ্যই ৩৩-এর বেশি। শর্ত সত্য — আর else-if সারিতে প্রথম সত্য শর্তই সিদ্ধান্ত নেয়।
- ফল "D" — অথচ পাওয়ার কথা A+. লাইন ৩-এর
m >= 80কখনো দেখাই হলো না। আসলে এই function-এ লাইন ৩ কোনো ইনপুটেই আর সত্য হতে পারে না — ৮০-র বেশি যেকোনো সংখ্যা আগেই লাইন ২-এ আটকে যায়। একে বলে মৃত শাখা (dead branch). কোনো error নেই, কোনো সতর্কবার্তা নেই — শুধু ভুল ফল। - সঠিক ক্রম: কড়া শর্ত আগে।
grade(95)-এ প্রথমেই সবচেয়ে উঁচু সীমা — ৯৫ ≥ ৮০ সত্য, ফল A+. নিয়ম: পরিসীমার সারিতে বড় থেকে ছোট (বা ছোট থেকে বড়) — এক দিকেই যান। - ৭২ নম্বর — প্রথম শর্ত মিথ্যা। ৭২ ≥ ৮০ মিথ্যা, তাই engine পরের
else if-এ যায়। খেয়াল করুন, এখানে আরm < 80লেখার দরকার নেই — এই লাইনে পৌঁছানো মানেই আগের শর্ত মিথ্যা ছিল। - ৭২ ≥ ৭০ — সত্য, ফল A. বাকি শর্ত আর দেখা হয় না। ক্রমই এখানে “৭০ থেকে ৭৯” পরিসীমাটা তৈরি করেছে — উপরের শর্তটি ঊর্ধ্বসীমা দিয়েছে, এই শর্তটি নিম্নসীমা।
এবার পুরো গ্রেড calculator চালিয়ে দেখুন — শুরুতেই ইনপুট যাচাই, তারপর সঠিক ক্রমে শর্ত। শেষে truthiness-এর চারটি মানও ছাপা হয়েছে:
// ভুল ক্রম: ঢিলা শর্ত আগে
function gradeWrong(m) {
if (m >= 33) return "D";
else if (m >= 80) return "A+";
else return "F";
}
// সঠিক: আগে যাচাই, তারপর বড় থেকে ছোট
function grade(m) {
if (Number.isNaN(m) || m < 0 || m > 100) return "অবৈধ নম্বর";
if (m >= 80) return "A+";
if (m >= 70) return "A";
if (m >= 60) return "A-";
if (m >= 50) return "B";
if (m >= 40) return "C";
if (m >= 33) return "D";
return "F";
}
console.log(gradeWrong(95), grade(95));
console.log(grade(72), grade(33), grade(20));
console.log(grade(Number("abc")), grade(105));
for (const qty of [0, "0", [], null, 3]) {
console.log(JSON.stringify(qty), qty ? "সত্য" : "মিথ্যা");
}প্রথম লাইনে D A+ — একই ৯৫ নম্বরে দুই function-এর দুই ফল। পরের লাইনে A D F: ৩৩ ঠিক সীমায় পড়ে বলে D (>= বনাম > — সীমার মানগুলো সবসময় আলাদা করে পরীক্ষা করুন)। তৃতীয় লাইনে দুবার অবৈধ নম্বর: Number("abc") দেয় NaN, আর ১০৫ পরিসীমার বাইরে — যাচাই না থাকলে প্রথমটি “F” আর দ্বিতীয়টি “A+” হয়ে যেত। grade-এ else নেই, কারণ প্রতিটি শাখা return করে — return হলে function সেখানেই শেষ, তাই পরের if নিজে থেকেই “else”. লুপের অংশ ছাপে 0 মিথ্যা, "0" সত্য, [] সত্য, null মিথ্যা, 3 সত্য — অংশ ২-এর ডায়াগ্রামের হুবহু মিল।
ক্রমের বাগ সবচেয়ে বিপজ্জনক কারণ এটি চুপচাপ. Test-এ যদি শুধু ৪৫ বা ২০ দেন, দুই function একই ফল দেবে। তাই পরিসীমার কোড পরীক্ষা করুন প্রতিটি সীমায় (০, ৩২, ৩৩, ৭৯, ৮০, ১০০) আর সীমার বাইরে (−১, ১০১, NaN).
৪ · Guard clause আর early return — nested if চ্যাপ্টা করা
একটি শর্তের ভেতরে আরেকটি, তার ভেতরে আরেকটি — এভাবে কোড ডান দিকে সরতে সরতে একটি পিরামিড হয়ে যায়। প্রতিটি স্তরে পাঠককে মাথায় রাখতে হয় “এখন পর্যন্ত কোন কোন শর্ত সত্য”। আর প্রতিটি if-এর else কোথায়, তা খুঁজতে চোখ ওপর-নিচ করতে হয়।
সমাধান হলো guard clause: অবৈধ বা বিশেষ অবস্থাগুলো function-এর শুরুতেই ধরে ফেলে early return করা। তারপর মূল কাজটি লেখা হয় সোজা, কোনো ইনডেন্ট ছাড়াই। একটি ডেলিভারি-চার্জ function ধাপে ধাপে বদলে দেখুন:
function charge(order) {
if (order) {
if (order.items.length > 0) {
if (order.total >= 2000) {
return 0;
} else {
return order.city === "ঢাকা" ? 60 : 120;
}
} else {
return null;
}
} else {
return null;
}
}
function charge(order) {
if (!order) return null;
if (order.items.length > 0) {
if (order.total >= 2000) {
return 0;
} else {
return order.city === "ঢাকা" ? 60 : 120;
}
} else {
return null;
}
}
function charge(order) {
if (!order) return null;
if (order.items.length === 0) return null;
if (order.total >= 2000) {
return 0;
} else {
return order.city === "ঢাকা" ? 60 : 120;
}
}
function charge(order) {
if (!order) return null; // পাহারা ১
if (order.items.length === 0) return null; // পাহারা ২
if (order.total >= 2000) return 0; // ফ্রি ডেলিভারি
return order.city === "ঢাকা" ? 60 : 120;
}
- শুরুর অবস্থা — পিরামিড। নিয়ম সহজ: অর্ডার না থাকলে বা ফাঁকা হলে
null; ২০০০ টাকা বা বেশি হলে ফ্রি ডেলিভারি; নইলে ঢাকায় ৬০, বাইরে ১২০ (উদাহরণের মান)। কিন্তু কোড পড়তে গিয়ে তিন স্তর ভেতরে ঢুকতে হয়, আর নিচের দুটিelseকোনif-এর, তা মেলাতে হয় বন্ধনী গুনে। - প্রথম guard clause. সবচেয়ে বাইরের শর্তটি উল্টে দিলাম: “order না থাকলে এখনই ফেরত যাও”। এর ফলে নিচের পুরো কোড এক স্তর বাঁয়ে সরল, আর শেষের
else { return null; }মুছে গেল। এখান থেকে নিচে পাঠক নিশ্চিত — order আছে। - দ্বিতীয় guard clause. একই কৌশল: “আইটেম না থাকলে ফেরত যাও”। আরেকটি স্তর আর আরেকটি দূরের
elseউধাও। এখন নিচের কোড ধরে নিতে পারে অর্ডারে অন্তত একটি আইটেম আছে। - চূড়ান্ত রূপ।
return-এর পরেelseঅর্থহীন, তাই সেটিও সরালাম। এখন function পড়া যায় ওপর থেকে নিচে, তালিকার মতো: দুটি পাহারা, একটি বিশেষ নিয়ম, একটি সাধারণ নিয়ম। যুক্তি একটুও বদলায়নি — শুধু আকার। (ESLint-এরno-else-returnনিয়ম এই শেষ ধাপটি মনে করিয়ে দেয়।)
Guard clause-এর মূল চিন্তা: “কখন কাজ করব না” আগে বলে দাও, তারপর “কী কাজ করব” নিশ্চিন্তে লেখো। তবে সব nesting খারাপ নয়। দুটি স্বাধীন প্রশ্নের চার রকম সমন্বয় (যেমন “সদস্য কি না” × “ঢাকায় কি না”) হলে nested if বা একটি ছোট টেবিল আসলেই পরিষ্কার। প্রশ্ন একটাই — পাঠক কি এক নজরে বুঝবে কোন পথে কখন যাওয়া হয়?
৫ · Ternary আর short-circuit — ছোট সিদ্ধান্তের ছোট রূপ
if…else একটি statement — এটি কাজ করে, কিন্তু কোনো মান দেয় না। অনেক সময় আমাদের দরকার একটি মান, যা শর্তের ওপর নির্ভর করে। এর জন্য আছে ternary operator শর্ত ? মান১ : মান২ — JavaScript-এর একমাত্র অপারেটর, যার তিনটি অংশ।
const charge = city === "ঢাকা" ? 60 : 120;
const label = stock === 0 ? "স্টক শেষ" : `${stock}টি আছে`;
// একটার ভেতরে আরেকটা — পড়তে কষ্ট
const fee = age < 3 ? 0 : age < 12 ? 150 : age >= 60 ? 200 : 300;প্রথম দুই লাইন ternary-র আদর্শ ব্যবহার: একটি শর্ত, দুটি ছোট মান, ফল সরাসরি const-এ। let charge; লিখে পরে if-else-এ মান বসানোর চেয়ে এটি নিরাপদ — variable-টি কখনো মানহীন থাকে না, পরে বদলানোও যায় না। তৃতীয় লাইনটি বৈধ (ternary ডান দিক থেকে জোড়া লাগে, তাই এটি অংশ ১-এর টিকিট function-এর মতোই কাজ করে), কিন্তু পড়তে গিয়ে প্রতিটি ? আর : জোড়া মেলাতে হয়। দুইয়ের বেশি পথ হলে if-else সারি বা lookup table ভালো। ESLint-এর no-nested-ternary নিয়ম এটাই ধরে।
&& দিয়ে শর্তসাপেক্ষে চালানো
&& আর || পর্ব ০৮-এ দেখেছেন — এরা short-circuit করে: বাঁ দিক থেকেই উত্তর জানা গেলে ডান দিক হিসাবই হয় না। এই গুণ দিয়ে অনেকে if-এর বদলে লেখেন isLoggedIn && showMenu(); — বাঁ দিক মিথ্যা হলে showMenu() ডাকাই হয় না।
- যেখানে মানানসই: একটি মান তৈরি করার সময়, যেমন
const name = user && user.name;(যদিও এখনuser?.nameআরও ভালো)। - যেখানে এড়ানো ভালো: শুধু একটি কাজ চালাতে।
if (isLoggedIn) showMenu();সবাই এক নজরে বোঝে;&&দেখে পাঠককে থেমে ভাবতে হয়। ESLint-এরno-unused-expressionsনিয়ম চালু করলে, ডিফল্ট সেটিংসে এমন লাইন ধরে। - React-এর পরিচিত ফাঁদ:
{count && <List />}— count0হলে&&মানটি ফেরত দেয়0, আর পাতায় একটি “0” ছাপা হয়ে যায়। লিখুনcount > 0 && …(framework পর্ব ৫৪-এ)।
Optional chaining শর্তের ভেতরে
গভীর object-এ কিছু আছে কি না দেখতে আগে লিখতে হতো if (order && order.customer && order.customer.phone). এখন optional chaining দিয়ে if (order?.customer?.phone) — মাঝপথে কোনো অংশ null/undefined হলে পুরোটা undefined হয়, error ছাড়াই (পর্ব ০৮)। তবে মনে রাখুন, শেষে আবার truthiness — phone যদি ফাঁকা string হয়, শর্ত মিথ্যা। আর ?. অবৈধ ডেটাকে “নেই” বলে লুকিয়ে ফেলতে পারে; যে তথ্য অবশ্যই থাকার কথা, সেখানে guard clause দিয়ে স্পষ্ট error দেওয়াই ভালো।
৬ · switch — === তুলনা, break আর fall-through
যখন একটিই মানকে অনেকগুলো নির্দিষ্ট মানের সাথে মেলাতে হয় — কমান্ড "add", "list", না "done"; অর্ডারের অবস্থা কোনটি — তখন switch statement পরিচিত পছন্দ:
function run(cmd) {
switch (cmd) {
case "add":
return "নতুন কাজ যোগ হলো";
case "list":
return "সব কাজের তালিকা";
case "done":
case "finish": // দুই নামে একই কাজ
return "কাজ শেষ চিহ্নিত";
default:
return `অজানা কমান্ড: ${cmd}`;
}
}switch (cmd) একবার cmd-এর মান হিসাব করে, তারপর ওপর থেকে প্রতিটি case-এর সাথে মেলায়। মিল পেলে সেই case থেকে কোড চলা শুরু হয়। এখানে প্রতিটি case return করে, তাই function সেখানেই শেষ। "done" আর "finish" পাশাপাশি — এভাবে কয়েকটি case একসাথে সাজিয়ে একই কাজ করানো যায়। কোনোটি না মিললে default. একটি সাধারণ command parser এভাবেই শুরু হয় — আর ব্যবহারকারী ভুল কমান্ড দিলে default তাকে বলে দেয় কী ভুল হলো।
switch তুলনা করে === দিয়ে
switch-এর মিলানো হয় strict equality (===) দিয়ে — কোনো type বদল ছাড়াই। তাই form থেকে আসা string "1" কখনো case 1:-এর সাথে মিলবে না, সোজা default-এ যাবে। এটি আসলে ভালো খবর (==-এর অদ্ভুত নিয়ম এখানে নেই), কিন্তু ইনপুটের type আগে ঠিক করে নিতে হবে — switch (Number(input)) বা case-এ string লিখে।
break না দিলে — fall-through
উপরের উদাহরণে প্রতিটি case return করেছে। কিন্তু function-এর বাইরে, বা কোনো মান ফেরত না দিয়ে কাজ করলে প্রতিটি case-এর শেষে লিখতে হয় break. ভুলে গেলে কী হয়, দেখুন:
1const log = [];2switch (cmd) { // cmd = "add"3 case "add":4 log.push("add"); // break নেই!5 case "list":6 log.push("list");7 break;8 case "done":9 log.push("done");10}11console.log(log);
| তুলনা | |
| log |
- মিল খোঁজা। switch
cmd-এর মান"add"নিয়ে প্রথম case-এর সাথে===দিয়ে তুলনা করল — মিলে গেছে। এখান থেকে কোড চলা শুরু। - case "add"-এর কাজ।
log-এ"add"যোগ হলো। এবার আসল প্রশ্ন: এরপর কী? এখানে কোনোbreakনেই। - Fall-through — পরের case-এ গড়িয়ে পড়া।
caseশুধু একটি “প্রবেশ-দরজা”র লেবেল, দেয়াল নয়। মিল একবার পাওয়ার পর switch আর কোনো case যাচাই করে না —case "list":লেবেল পেরিয়ে তার কোডও চালিয়ে দেয়। একে বলে fall-through."list"-ও যোগ হলো, যদিও কেউ "list" চায়নি। - break — অবশেষে থামা। লাইন ৭-এর
breakনিয়ন্ত্রণকে switch-এর বাইরে, লাইন ১১-এ পাঠায়।case "done"ছোঁয়া হয়নি — কিন্তু ততক্ষণে ক্ষতি হয়ে গেছে। - ফল:
[ 'add', 'list' ]. কোনো error নেই — engine-এর কাছে এটি সম্পূর্ণ বৈধ কোড। এজন্যই ESLint-এর recommended নিয়মেno-fallthroughআছে: ইচ্ছাকৃত fall-through হলে সেখানে// falls throughমন্তব্য লিখে বোঝাতে হয়। (খালি case পাশাপাশি সাজানো, যেমন "done"/"finish", নিয়মটি নিজেই মেনে নেয়।)
শেষ case-এ (সাধারণত default) break না দিলেও কোনো সমস্যা নেই, কারণ নিচে আর কিছু নেই। তবু অনেকে সেখানেও break লেখেন — পরে কেউ নিচে নতুন case যোগ করলে যেন fall-through না হয়। আর default সাধারণত শেষে রাখা হয়; মাঝখানে রাখা বৈধ, কিন্তু তখন তার পরের case-এ গড়িয়ে পড়ার ঝুঁকি থাকে।
৭ · switch-এর সূক্ষ্ম দিক — default, switch (true) আর case-এর ভেতরে let
default — “বাকি সব”-এর জন্য পরিকল্পনা
default বাধ্যতামূলক নয়। কিন্তু না থাকলে অজানা মান এলে switch চুপচাপ কিছুই করে না — আর সেই নীরবতাই পরে বাগ হয়। নতুন একটি অর্ডার-অবস্থা ("returned") যোগ হলো, কেউ switch আপডেট করল না — ব্যবহারকারী কোনো বার্তাই দেখল না। ভালো অভ্যাস: default-এ হয় একটি অর্থপূর্ণ বার্তা, নয়তো স্পষ্ট error (throw new Error(`অজানা অবস্থা: ${s}`) — error নিয়ে বিস্তারিত পর্ব ২৯-এ)। ESLint-এর default-case নিয়ম এটি জোর করাতে পারে।
switch (true) — পরিসীমার জন্য switch?
switch নির্দিষ্ট মান মেলায়, পরিসীমা নয়। তবু একটি কৌশল প্রচলিত আছে:
switch (true) {
case m >= 80: grade = "A+"; break;
case m >= 70: grade = "A"; break;
default: grade = "F";
}এখানে switch-এর মান true, আর প্রতিটি case-এ একটি তুলনা, যার ফল true বা false. প্রথম যে case-এর ফল === true, সেটি চলে — অর্থাৎ ঠিক else-if সারির মতো, ক্রমের নিয়মসহ। এটি নিয়ে বিতর্ক আছে: কেউ বলেন এটি সারিবদ্ধ আর পরিষ্কার দেখায়; অন্যরা বলেন এটি switch-এর উদ্দেশ্যের অপব্যবহার, আর if-else সারিই এখানে সরাসরি। একটি গোপন ফাঁদও আছে — case-এ boolean-এর বদলে truthy মান দিলে (যেমন case user.name:) মিলবে না, কারণ "Rafi" === true মিথ্যা। আমাদের পরামর্শ: দলের কোডে যা প্রচলিত তা মানুন; নিজে বেছে নিলে if-else সারি।
পুরো switch একটিই block — case-এর ভেতরে let
এটি অনেক অভিজ্ঞ প্রোগ্রামারকেও অবাক করে। প্রতিটি case-এর নিজস্ব scope নেই — পুরো switch-এর { } মিলে একটিই block scope. তাই দুই case-এ একই নামে let বা const ঘোষণা করলে:
switch (cmd) {
case "add":
let msg = "যোগ হলো";
break;
case "done":
let msg = "শেষ"; // একই block-এ আবার msg!
break;
}SyntaxError: Identifier 'msg' has already been declared
এটি parse time-এর error — Node.js আর Chrome/Edge (V8) এই বার্তাই দেয়, আর কোনো লাইন চলার আগেই (পর্ব ০৫)। দ্বিতীয় let মুছে দিলেও বিপদ শেষ নয়: cmd যদি "done" হয়, তাহলে "add" case-এর let msg লাইনটি কখনো চলেনি, অথচ msg একই block-এ আছে — তাতে মান বসাতে গেলে আসে ReferenceError: Cannot access 'msg' before initialization, অর্থাৎ TDZ (পর্ব ০৭)। সমাধান সহজ: প্রতিটি case-এর কোড নিজস্ব বন্ধনীতে রাখুন — case "add": { let msg = …; break; }. ESLint-এর recommended নিয়মে no-case-declarations ঠিক এই ভুলটি ধরে।
JavaScript-এর switch C থেকে নেওয়া — একই case/break/default, একই fall-through. পার্থক্য: C/C++-এর case-এ শুধু ধ্রুব পূর্ণসংখ্যা চলে, JavaScript-এ যেকোনো মান বা expression, তুলনা === দিয়ে। Java-র আধুনিক switch-এ -> দিয়ে fall-through নেই আর এটি মান ফেরত দেয় (Java-র সিদ্ধান্ত পর্ব); Python-এ switch ছিলই না, ৩.১০ থেকে এসেছে আরও শক্তিশালী match (Python-এর if আর match). Python-এর মতো JavaScript-এও শর্তে truthiness চলে, কিন্তু তালিকা আলাদা — Python-এ ফাঁকা list মিথ্যা, JavaScript-এ ফাঁকা array সত্য।
৮ · switch-এর বদলে lookup table — সিদ্ধান্ত যখন আসলে ডেটা
অনেক switch আসলে কোনো “যুক্তি” করে না — শুধু একটি মানের বদলে আরেকটি মান দেয়। অর্ডারের অবস্থা "shipped" → “পথে আছে”; সপ্তাহের দিন 5 → “শুক্রবার”; HTTP status 404 → “পাওয়া যায়নি”। এ ধরনের ক্ষেত্রে কোড না লিখে ডেটা লিখুন — একটি lookup table:
const BN_DAYS = ["রবিবার", "সোমবার", "মঙ্গলবার", "বুধবার",
"বৃহস্পতিবার", "শুক্রবার", "শনিবার"];
const dayName = (d) => BN_DAYS[d] ?? "অবৈধ দিন"; // 0 = রবিবার
const STATUS_TEXT = new Map([
[200, "ঠিক আছে"],
[404, "পাওয়া যায়নি"],
[500, "সার্ভারে সমস্যা"],
]);
const statusText = (code) => STATUS_TEXT.get(code) ?? "অজানা কোড";সাতটি case-এর একটি switch এখানে একটি array আর এক লাইন হয়ে গেল। দিনের নম্বর JavaScript-এর Date-এর নিয়মে — ০ মানে রবিবার (পর্ব ২০)। ?? এখানে default-এর কাজ করে। নতুন একটি status কোড যোগ করতে এখন কোনো যুক্তি ছুঁতে হয় না — শুধু টেবিলে একটি সারি। টেবিলটি চাইলে JSON ফাইল বা সার্ভার থেকেও আসতে পারে, যা switch দিয়ে সম্ভব নয়।
সাধারণ object দিয়েও lookup table বানানো যায় — বেশিরভাগ কোডে তা-ই দেখবেন। কিন্তু একটি সূক্ষ্ম ফাঁদ আছে। চালিয়ে দেখুন, সাথে switch-এর === আর fall-through-ও:
const input = "1"; // ধরুন form থেকে এসেছে — string!
switch (input) {
case 1: console.log("number 1 মিলেছে"); break;
default: console.log("মেলেনি — switch তুলনা করে === দিয়ে");
}
const log = [];
switch ("add") {
case "add": log.push("add"); // break নেই!
case "list": log.push("list"); break;
case "done": log.push("done");
}
console.log(log);
const statusMsg = { pending: "অর্ডার পেয়েছি", shipped: "পথে আছে" };
const show = (s) => statusMsg[s] ?? "অজানা অবস্থা";
console.log(show("shipped"), "|", show("lost"));
console.log(typeof show("constructor")); // ফাঁদ!
const safe = new Map(Object.entries(statusMsg));
const show2 = (s) => safe.get(s) ?? "অজানা অবস্থা";
console.log(show2("constructor"));প্রথম লাইন মেলেনি — switch তুলনা করে === দিয়ে: string "1" আর number 1 কখনো strict-সমান নয়। এরপর [ 'add', 'list' ] — অংশ ৬-এর fall-through. তারপর পথে আছে | অজানা অবস্থা — table ঠিকই কাজ করছে। কিন্তু চতুর্থ লাইন function! সাধারণ object তার prototype থেকে constructor, toString-এর মতো property উত্তরাধিকারসূত্রে পায় (পর্ব ২৫), তাই statusMsg["constructor"] কোনো বার্তা নয়, একটি function — আর ?? সেটি ধরতে পারে না, কারণ মানটি null/undefined নয়। ব্যবহারকারীর লেখা মান দিয়ে খুঁজলে এটি সত্যিকারের বাগ। Map-এ শুধু আপনার রাখা key থাকে, তাই শেষ লাইনে ঠিকঠাক অজানা অবস্থা. সাধারণ object রাখতে চাইলে Object.hasOwn(statusMsg, s) দিয়ে আগে যাচাই করুন।
| পরিস্থিতি | ভালো পছন্দ |
|---|---|
| দুই পথ, ছোট মান | ternary ? : |
| পরিসীমা বা জটিল শর্ত (নম্বর → গ্রেড, বয়স → টিকিট) | if / else if সারি, guard clause |
| একটি মান → আরেকটি মান (status → বার্তা, দিন → নাম) | lookup table — array, Map, বা Object.hasOwn-সহ object |
| একটি মান → আলাদা আলাদা কাজ (command parser) | switch, বা function-এর table: { add: addTask, list: listTasks } |
| কয়েকটি মানে একই কাজ, ইচ্ছাকৃত fall-through | switch (খালি case পাশাপাশি) |
খেয়াল করুন একটি পার্থক্য: সাধারণ object-এর key সবসময় string হয়ে যায়, তাই obj[1] আর obj["1"] একই জিনিস। অর্থাৎ object-table form-এর string ইনপুটও মেনে নেয়, switch নেয় না। Map আবার switch-এর মতো কড়া — map.get("1") আর map.get(1) আলাদা। কোনটি ভালো, তা নির্ভর করে ইনপুট কতটা বিশ্বাসযোগ্য তার ওপর; সবচেয়ে নিরাপদ পথ হলো ইনপুট আগে নির্দিষ্ট type-এ বদলে নেওয়া। Map নিয়ে বিস্তারিত পর্ব ১৮-এ।
- JavaScript-এ কোন কোন মান falsy? —
false,0,-0,0n,"",null,undefined,NaN; বাকি সব truthy,"0",[],{}সহ। - switch কোন তুলনা ব্যবহার করে? — strict equality (
===); তাই"1"আরcase 1মেলে না। - break না দিলে কী হয়? — fall-through: মিল পাওয়ার পর পরের case-গুলোর কোডও চলে, যতক্ষণ না
break,returnবা switch-এর শেষ আসে। - ফাঁকা array শর্তে কী হয়? — truthy; খালি কি না জানতে
arr.length === 0. - দুই case-এ একই নামে let দিলে? —
SyntaxError: Identifier '…' has already been declared, কারণ পুরো switch একটি block; প্রতিটি case-কে{ }-এ রাখুন।
৯ · AI-এর যুগে — AI-এর লেখা সিদ্ধান্ত যাচাই করা
AI সিদ্ধান্তের কোড খুব দ্রুত লেখে, আর প্রায়ই তা চলেও। কিন্তু সিদ্ধান্তের বাগগুলো ঠিক এমন যে কোড চলে, শুধু কোনো একটি বিশেষ ইনপুটে ভুল পথে যায়। AI-এর লেখায় যে ধরনগুলো বারবার দেখা যায়:
| AI প্রায়ই যা লেখে | সমস্যা | যা চাইবেন |
|---|---|---|
| ২০–৩০ শাখার বিশাল if/else বা switch | পড়া, পরীক্ষা আর বদলানো কঠিন; একটি শাখা বাদ পড়লে চোখে পড়ে না | মান → মান হলে lookup table |
শর্তে == (if (status == 1)) | অপ্রত্যাশিত coercion; "" == 0 সত্য | সবখানে === |
if (value) দিয়ে “আছে কি না” | 0, "" বাদ পড়ে; ফাঁকা array পাস করে | স্পষ্ট শর্ত, ?? |
switch-এ break বা default বাদ | নীরব fall-through, অজানা মানে নীরবতা | প্রতিটি case-এ break/return, অর্থপূর্ণ default |
| Nested ternary এক লাইনে | পড়তে গিয়ে জোড়া মেলাতে হয় | if-else সারি বা table |
পরিসীমার শর্তে ভুল ক্রম বা সীমা (> বনাম >=) | সীমার মানে ভুল ফল | প্রতিটি সীমায় test |
AI-কে সিদ্ধান্তের কোড লিখতে দিলে নিয়মগুলো আগেই বলে দিন, আর তাকে দিয়েই সীমার test লিখিয়ে নিন:
AI-এর লেখা সিদ্ধান্তের কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১switch-এর fall-through প্রায়ই বাগের উৎস — তাহলে এটি ভাষায় এল কেন? C, Duff's device আর JavaScript-এর উত্তরাধিকার।
JavaScript-এর switch সরাসরি C-এর উত্তরাধিকার, আর C-এর switch এসেছে তার পূর্বসূরি BCPL থেকে। এই ভাষাগুলো তৈরি হয়েছিল ১৯৬০–৭০-এর দশকে, যখন compiler-কে খুব কম মেমরিতে, যন্ত্রের কাছাকাছি কোড বানাতে হতো। C-এর চোখে switch আসলে একটি “লাফ দেওয়ার টেবিল” (jump table): মান দেখে নির্দিষ্ট ঠিকানায় লাফ দাও, তারপর সেখান থেকে সোজা চলতে থাকো। case কোনো আলাদা ঘর নয় — শুধু একটি লেবেল, অর্থাৎ লাফের গন্তব্য। থামতে চাইলে স্পষ্ট করে বলতে হয় break — অর্থাৎ “switch-এর শেষে লাফ দাও”। যন্ত্রের দৃষ্টিতে fall-through তাই কোনো বিশেষ বৈশিষ্ট্য নয়, বরং স্বাভাবিক অবস্থা।
এর সবচেয়ে বিখ্যাত (কুখ্যাতও বলা যায়) ব্যবহার Duff's device. ১৯৮৩ সালে Lucasfilm-এ কাজ করার সময় Tom Duff একটি কপি করার লুপ দ্রুত করতে switch-এর case লেবেলগুলো একটি do…while লুপের ভেতরে বসিয়ে দেন — প্রথমবার switch লুপের মাঝখানে লাফ দিয়ে বাড়তি কয়েকটি কপি সেরে নেয়, তারপর লুপ প্রতিবার আটটি করে কপি করে। এটি বৈধ C, আর এটি দেখায় case আসলে কতটা “শুধু লেবেল”। Duff নিজে লিখেছিলেন, এই আবিষ্কার fall-through-এর পক্ষে না বিপক্ষে যুক্তি, তা তিনি নিশ্চিত নন।
JavaScript (১৯৯৫) C/Java-র মতো দেখানোর জন্য switch-এর বাক্যগঠন হুবহু নিয়েছে, fall-through-সহ। তবে Duff's device JavaScript-এ সম্ভব নয় — এখানে case লেবেল শুধু switch-এর সরাসরি ভেতরেই থাকতে পারে, লুপের ভেতরে নয়। আর JavaScript-এর case-এ যেকোনো expression চলে, তাই engine সবসময় jump table বানাতে পারে না; সব case ধ্রুব ছোট সংখ্যা বা string হলে engine অপ্টিমাইজ করতে পারে, নইলে আসলে if-সারির মতোই একটি একটি তুলনা করে।
পরের ভাষাগুলো শিক্ষা নিয়েছে। C#-এ কোড আছে এমন case থেকে পরের case-এ নীরবে গড়িয়ে পড়া compile error; Go-তে ডিফল্টে কোনো fall-through নেই, চাইলে স্পষ্ট fallthrough লিখতে হয়; Swift-ও তাই; Java ১৪ থেকে -> রূপের switch-এ fall-through নেই। JavaScript-এ পেছনের সামঞ্জস্য (পুরোনো কোড যেন না ভাঙে) রাখার নিয়ম কঠোর, তাই আচরণ বদলানো সম্ভব নয় — সেই দায়িত্ব নিয়েছে ESLint-এর no-fallthrough.
মূল উপলব্ধি: ভাষার অনেক “অদ্ভুত” আচরণ আসলে অতীতের যন্ত্র আর সামঞ্জস্যের ছাপ। কোনো নিয়ম কেন আছে তা জানলে বুঝবেন কোথায় সতর্ক হতে হবে — আর কোথায় টুল দিয়ে নিজেকে রক্ষা করতে হবে।
প্র ০২Java শর্তে শুধু boolean মানে, অথচ JavaScript যেকোনো মান মেনে নেয়। এই নকশা কোথা থেকে এল, আর কোনটি ভালো?
“যেকোনো মানকে সত্য/মিথ্যা ধরা” ধারণাটি পুরোনো। Lisp-এ (১৯৫৮ থেকে) প্রচলিত নিয়ম ছিল: একটিমাত্র মান মিথ্যা — nil, যা একই সাথে ফাঁকা তালিকাও; বাকি সব সত্য। C-তে আলাদা boolean type-ই ছিল না (C99-এ _Bool আসার আগে) — শূন্য মানে মিথ্যা, অশূন্য মানে সত্য, আর pointer-এর ক্ষেত্রে null মানে মিথ্যা। Perl (১৯৮৭) এটিকে আরও ছড়িয়ে দেয়: 0, ফাঁকা string, "0" আর undefined মিথ্যা। স্ক্রিপ্টিং ভাষার জগতে ধারণাটি হয়ে যায় “স্বাভাবিক” — সংক্ষেপে লেখা যায়, if (line) পড়তেও সহজ।
JavaScript এই পরিবারের ধারায় চলে, কিন্তু নিজের তালিকা বানায় — আর ছোট ছোট পার্থক্যই ফাঁদ। Perl-এ "0" মিথ্যা, JavaScript-এ সত্য। Python-এ ফাঁকা list আর dict মিথ্যা, JavaScript-এ ফাঁকা array আর object সত্য। যিনি এক ভাষা থেকে আরেক ভাষায় যান, তার হাতে ঠিক এখানেই বাগ ঢোকে।
Java (১৯৯৫) উল্টো পথ নেয়। C-এর একটি কুখ্যাত বাগ ছিল if (x = 5) — তুলনার বদলে মান বসানো, যা সবসময় সত্য। Java-তে শর্তে boolean ছাড়া কিছু চলে না, তাই int-এর এই ভুল compile-ই হয় না। দাম হলো একটু বেশি লেখা: if (list != null && !list.isEmpty()). C#, Go, Swift, Rust — সবাই এই কড়া পথ নিয়েছে। TypeScript আবার মাঝামাঝি: ভাষার নিয়ম JavaScript-এরই, কিন্তু কিছু lint নিয়ম (যেমন typescript-eslint-এর strict-boolean-expressions) চাইলে কড়াকড়ি আনতে পারে।
কোনটি ভালো? দুই নকশাই একটি বিনিময়: উদার নিয়মে কোড ছোট, পড়তে স্বাভাবিক, কিন্তু “মান নেই” আর “মান শূন্য” গুলিয়ে যায়। কড়া নিয়মে লেখা লম্বা, কিন্তু প্রতিটি শর্ত ঠিক কী জিজ্ঞেস করছে তা স্পষ্ট। JavaScript নিজেও সময়ের সাথে কড়া দিকের সাহায্য যোগ করেছে — ?? আর ?. (ES2020) ঠিক এজন্যই এসেছে, যাতে “শুধু null/undefined” আলাদা করে ধরা যায়, 0 আর ""-কে বাদ না দিয়ে।
মূল উপলব্ধি: ভাষা কী মেনে নেয় আর আপনার কী লেখা উচিত — দুটি আলাদা প্রশ্ন। JavaScript truthiness মেনে নেয়, কিন্তু সংখ্যা ও string-এর ক্ষেত্রে পেশাদাররা স্পষ্ট শর্ত লেখেন, যেন কোড যা বলে তা-ই বোঝায়।
প্র ০৩অনেক ভাষায় এখন “pattern matching” আছে। JavaScript-এ কি আসছে? TC39-এর প্রস্তাবটির অবস্থা কী?
Pattern matching হলো switch-এর অনেক শক্তিশালী আত্মীয়। switch শুধু জিজ্ঞেস করে “মানটি কি ঠিক এটির সমান?”; pattern matching জিজ্ঞেস করে “মানটির আকার কি এমন?” — যেমন “এটি কি এমন object যার status 200 আর body-তে একটি array আছে?” — আর মিললে ভেতরের অংশগুলো সাথে সাথে variable-এ বের করে দেয়। ML, Haskell, Erlang-এর মতো functional ভাষায় এটি দশকের পর দশক ধরে আছে; Rust-এর match, Scala, Swift, Python ৩.১০-এর match আর Java-র সাম্প্রতিক switch-এও এসেছে।
JavaScript-এ এর জন্য TC39-এ একটি প্রস্তাব আছে — “Pattern Matching” proposal. ২০১৮ সালে এটি Stage 1-এ ওঠে, অর্থাৎ কমিটি মেনে নিয়েছে “সমস্যাটি নিয়ে কাজ করার মতো”, কিন্তু নকশা এখনো চূড়ান্ত নয়। এরপর বছরের পর বছর ধরে এর বাক্যগঠন কয়েকবার বদলেছে — কখনো case … when, কখনো match (x) { when …: … } ধরনের রূপ। আমাদের জানা সর্বশেষ তথ্যে এটি এখনো প্রাথমিক পর্যায়ের প্রস্তাব, কোনো ব্রাউজার বা Node.js-এ স্বাভাবিকভাবে চলে না। সঠিক বর্তমান অবস্থা দেখতে TC39-এর GitHub-এর proposals তালিকা দেখুন — সেখানে প্রতিটি প্রস্তাবের stage লেখা থাকে।
এত সময় লাগছে কেন? প্রথমত, JavaScript-এ একবার কিছু যোগ হলে তা আর সরানো যায় না — ভুল নকশা চিরকাল থেকে যায়। দ্বিতীয়ত, প্রশ্নগুলো কঠিন: pattern-এ তুলনা === হবে না অন্য কিছু? array pattern কি iterator ব্যবহার করবে? এটি statement হবে না expression? destructuring (পর্ব ১৭)-এর সাথে কীভাবে মিলবে? প্রতিটি সিদ্ধান্তের প্রভাব পড়ে কোটি কোটি পাতায়।
ততদিন আমাদের হাতে আছে কাছাকাছি কৌশল: lookup table, destructuring-সহ guard clause, আর TypeScript-এ “discriminated union” — যেখানে switch (shape.kind) লিখলে compiler প্রতিটি case-এ ঠিক type বুঝে নেয়, এমনকি কোনো case বাদ পড়লে সতর্কও করতে পারে (পর্ব ৪৭)।
মূল উপলব্ধি: একটি প্রস্তাব যতক্ষণ Stage 4-এ না পৌঁছায়, ততক্ষণ সেটি ভাষার অংশ নয়। ব্লগ বা AI যদি “JavaScript-এর নতুন match” দেখায়, আগে stage যাচাই করুন — নইলে এমন কোড লিখবেন যা কোথাও চলে না।
প্র ০৪Rob Pike বলেছিলেন “Data dominates”. সিদ্ধান্তের কোডকে ডেটায় (lookup table) রূপান্তর করা কেন এত শক্তিশালী, আর এর সীমা কোথায়?
১৯৮৯ সালে Bell Labs-এর Rob Pike (পরে Go ভাষার সহ-স্রষ্টা) “Notes on Programming in C” নামে একটি ছোট লেখায় প্রোগ্রামিংয়ের কয়েকটি নিয়ম লেখেন। তার পঞ্চম নিয়ম: “Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident.” — ঠিক ডেটা-কাঠামো বেছে নিলে যুক্তি প্রায় নিজে থেকেই স্পষ্ট হয়ে যায়। এর আগে ১৯৭৫ সালে Fred Brooks “The Mythical Man-Month” বইয়ে একই কথা বলেছিলেন: flowchart দেখালে তিনি বিভ্রান্ত থাকবেন, কিন্তু table দেখালে flowchart লাগবেই না।
সিদ্ধান্তের কোডে এর মানে কী? ৪০টি case-এর একটি switch আসলে ৪০ সারির একটি টেবিল, যা কোডের ছদ্মবেশে আছে। টেবিল হিসেবে লিখলে লাভ অনেক: (১) এক নজরে পুরোটা দেখা যায়, ফাঁক বা দুবার লেখা ধরা পড়ে; (২) নতুন সারি যোগ করতে যুক্তি ছুঁতে হয় না, তাই নতুন বাগের ঝুঁকি কম; (৩) টেবিল JSON ফাইল, ডেটাবেস বা সার্ভার থেকে আসতে পারে — অনুবাদক বা অ-প্রোগ্রামাররাও বার্তা বদলাতে পারেন, কোড deploy ছাড়াই; (৪) test সহজ — টেবিলের প্রতিটি সারি একটি test case.
এই ধারণা বড় পরিসরে সর্বত্র: compiler-এর parser table, নেটওয়ার্কের routing table, Redux-এর reducer-এ action type থেকে কাজ, বা state machine — যেখানে “এই অবস্থায় এই event এলে পরের অবস্থা কী” পুরোটাই একটি টেবিল। অর্ডারের অবস্থা (pending → shipped → delivered) এভাবে লিখলে অবৈধ লাফ (pending থেকে সরাসরি delivered) টেবিলেই আটকে দেওয়া যায়।
তবে সীমাও আছে। শর্ত যখন সত্যিকারের যুক্তি — পরিসীমা, কয়েকটি variable-এর সমন্বয়, ক্রমের গুরুত্ব — তখন জোর করে টেবিল বানালে উল্টো জটিল হয়; গ্রেডের সারি if-else-এই পরিষ্কার। টেবিলের বাগও আছে — অংশ ৮-এর constructor ফাঁদ, বা টেবিলে key-এর বানান ভুল, যা কোনো error দেয় না। আর অতিরিক্ত “configurable” করতে গিয়ে কোডকে এমন টেবিল-ইঞ্জিন বানিয়ে ফেলা যায় যে আসল আচরণ বুঝতে কয়েকটি ফাইল ঘাঁটতে হয়।
মূল উপলব্ধি: প্রতিটি লম্বা switch বা if-সারি দেখে জিজ্ঞেস করুন — “এটি কি যুক্তি, না ছদ্মবেশী ডেটা?” ডেটা হলে টেবিলে রাখুন; যুক্তি হলে স্পষ্ট শর্তে। এই একটি প্রশ্নই বহু কোডকে অর্ধেক ছোট করে দেয়।
প্র ০৫২০১৪ সালের Apple-এর “goto fail” বাগ কী ছিল? একটি বাড়তি লাইন আর বন্ধনীর অভাব কীভাবে কোটি ডিভাইসের নিরাপত্তা ভেঙে দিল?
২০১৪ সালের ফেব্রুয়ারিতে Apple iOS আর OS X-এর জন্য একটি জরুরি নিরাপত্তা আপডেট প্রকাশ করে (CVE-2014-1266). সমস্যাটি ছিল SecureTransport নামের library-তে, যা HTTPS-এর মতো নিরাপদ সংযোগ (SSL/TLS) যাচাই করে। একটি function-এ, যা সার্ভারের ডিজিটাল স্বাক্ষর পরীক্ষা করত, পরপর কয়েকটি C কোডের লাইন ছিল এই ধরনের: if ((err = …) != 0) goto fail; — অর্থাৎ কোনো ধাপ ব্যর্থ হলে fail লেবেলে লাফ দাও, যেখানে ফল ফেরত দেওয়া হয়।
কোনো এক সম্পাদনায় একটি লাইন দুবার পড়ে যায়:
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
goto fail;
goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
goto fail;
বন্ধনী না থাকায় প্রথম goto fail; ছিল if-এর অধীনে, কিন্তু দ্বিতীয়টি নয় — সেটি সবসময় চলত, ইনডেন্টেশন যা-ই দেখাক। তখন err-এর মান ছিল ০ (“সফল”), তাই function পরের আসল স্বাক্ষর-যাচাই এড়িয়ে “সব ঠিক আছে” বলে ফিরে যেত। ফলে একই Wi-Fi-তে থাকা একজন আক্রমণকারী নকল সার্ভার সেজে নিরাপদ সংযোগের মাঝখানে বসতে পারত (man-in-the-middle). iOS 7.0.6 আর OS X-এর পরের আপডেটে এটি ঠিক করা হয়।
শিক্ষাগুলো সরাসরি এই পর্বের বিষয়। প্রথমত, সবসময় বন্ধনী — থাকলে বাড়তি লাইনটি ক্ষতিহীন হতো। দ্বিতীয়ত, ইনডেন্টেশন মানুষকে ঠকায়, compiler-কে নয় — এজন্যই Prettier-এর মতো ফরম্যাটার কোডকে আসল গঠন অনুযায়ী ইনডেন্ট করে, যাতে এমন লাইন চোখে পড়ে। তৃতীয়ত, compiler বা linter-এর “unreachable code” সতর্কতা — এই বাগে নিচের যাচাইয়ের কোড কখনো চলত না, যা টুল ধরতে পারত। চতুর্থত, test: “ভুল স্বাক্ষর দিলে সংযোগ ব্যর্থ হয় কি?” — এমন একটি negative test থাকলেই বাগটি ধরা পড়ত। একই বছরে GnuTLS-এও একটি আলাদা কিন্তু কাছাকাছি ধরনের সার্টিফিকেট-যাচাইয়ের বাগ পাওয়া যায়।
মূল উপলব্ধি: সিদ্ধান্তের কোডের ছোট ছোট অভ্যাস — বন্ধনী, ফরম্যাটার, linter, “ব্যর্থ হওয়ার” test — শুধু রুচির ব্যাপার নয়। নিরাপত্তার কোডে একটি শাখা ভুল পথে গেলে পুরো ব্যবস্থাই অর্থহীন হয়ে যায়।
অনুশীলন
বেশিরভাগ কাজে আগে ভাবুন, খাতায় উত্তর লিখুন — তারপর ব্রাউজারের console-এ চালিয়ে মেলান।
-
সত্য না মিথ্যা? চালানোর আগে লিখুন, নিচের প্রতিটি মান
if-এ সত্য না মিথ্যা:0," ","false",NaN,{},-1. তারপর[0, " ", "false", NaN, {}, -1].map(Boolean)চালিয়ে মেলান।0মিথ্যা;" "সত্য (একটি space আছে, তাই ফাঁকা নয়);"false"সত্য (ফাঁকা নয় এমন string — লেখা কী, তাতে কিছু যায় আসে না);NaNমিথ্যা;{}সত্য (সব object);-1সত্য (শুধু ০ আর −০ মিথ্যা)। ফল:[ false, true, true, false, true, true ]."false"নিয়ে ভুল সবচেয়ে বেশি হয় — সার্ভার বা localStorage থেকে"false"string এলেifসেটিকে সত্য ধরে। -
টিকিট function আবার লিখুন: অংশ ১-এর
ticketPrice-এ guard clause যোগ করুন — বয়স সংখ্যা না হলে, ঋণাত্মক হলে বা ১২০-এর বেশি হলেnullফেরত দেবে। তারপরelseছাড়া, শুধুifআরreturnদিয়ে লিখুন। কোন কোন বয়সে test করবেন?একটি সমাধান:
if (typeof age !== "number" || Number.isNaN(age)) return null; if (age < 0 || age > 120) return null; if (age < 3) return 0; if (age < 12) return 150; if (age >= 60) return 200; return 300;. শেষ তিনটির ক্রম নিয়ে ভাবুন:age >= 60-কে প্রথমে রাখলেও এখানে সমস্যা নেই, কারণ শিশু আর প্রবীণের পরিসীমা মেশে না — কিন্তু< 12-কে< 3-এর আগে রাখলে ২ বছরের শিশু ১৫০ টাকার টিকিট পাবে। Test:-1, 0, 2, 3, 11, 12, 59, 60, 120, 121, NaN, "10". খেয়াল করুন"10"(string) এখানেnullপায় — ইনপুট form থেকে এলে আগেNumber()করতে হবে। -
বাগ খুঁজুন: একটি পাতায় ব্যবহারকারী ড্রপডাউন থেকে বিভাগ বাছাই করেন, আর কোড লেখা:
switch (select.value) { case 1: area = "ঢাকা"; case 2: area = "চট্টগ্রাম"; break; default: area = "অন্যান্য"; }. ব্যবহারকারী “১” বাছাই করলেও সবসময় “অন্যান্য” আসে। কেন? এখানে আর কোন বাগ লুকিয়ে আছে?প্রথম বাগ:
select.valueসবসময় string ("1"), আর switch তুলনা করে===দিয়ে, তাইcase 1কখনো মেলে না — সবসময়default. সমাধান:switch (Number(select.value)), অথবা case-এ"1","2". দ্বিতীয় বাগ লুকিয়ে আছে প্রথমটির পেছনে:case 1-এর পরেbreakনেই। প্রথম বাগ ঠিক করলেই দেখা যাবে “১” বাছলেareaহয়ে যায় “চট্টগ্রাম” — fall-through. একটি বাগ আরেকটিকে ঢেকে রেখেছিল। আরও ভালো সমাধান:const AREAS = { "1": "ঢাকা", "2": "চট্টগ্রাম" }; area = Object.hasOwn(AREAS, v) ? AREAS[v] : "অন্যান্য"; -
Table না switch? নিচের তিনটি কাজের প্রতিটির জন্য বলুন if-সারি, switch, না lookup table ভালো, আর কেন: (ক) bKash/Nagad/কার্ড — পেমেন্ট পদ্ধতি থেকে ফি-এর হার; (খ) cart-এর মোট দাম থেকে ডিসকাউন্ট (৫০০০+ হলে ১০%, ২০০০+ হলে ৫%); (গ) কমান্ড "add", "list", "done" — প্রতিটির জন্য আলাদা কয়েক লাইনের কাজ।
(ক) Lookup table — একটি নাম থেকে একটি সংখ্যা, কোনো যুক্তি নেই।
Map-এ রাখলে নতুন পদ্ধতি যোগ করা এক লাইন, আর হার সার্ভার থেকেও আনা যায়। (খ) if-সারি — পরিসীমা আর ক্রম গুরুত্বপূর্ণ (আগে ৫০০০, তারপর ২০০০); switch বা সাধারণ table এখানে স্বাভাবিক নয়। (গ) switch, অথবা function-এর table{ add: addTask, list: listTasks, done: markDone }— প্রতিটি কমান্ড আলাদা কাজ করে। কমান্ড অনেক হলে table ভালো, কারণ নতুন কমান্ড মানে নতুন একটি function আর table-এ একটি সারি; অজানা কমান্ড ধরতেObject.hasOwnবাMapলাগবে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমJavaScript-এর একটি switch statement চলার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- switch-এর বন্ধনীর expression একবার হিসাব হয়
- ফলটি ওপর থেকে প্রতিটি case-এর মানের সাথে === দিয়ে তুলনা হয়
- প্রথম যে case মেলে, সেখান থেকে কোড চলা শুরু হয়
- break, return বা switch-এর শেষ না আসা পর্যন্ত পরের case-এর কোডও চলে
- break পেলে নিয়ন্ত্রণ switch-এর ঠিক পরের লাইনে চলে যায়
JavaScript-এ const v = "1"; switch (v) { case 1: console.log("A"); break; default: console.log("B"); } চালালে কী ছাপা হবে?
- A
- A আর B দুটোই
- B
- TypeError
নিচের কোন মানটি JavaScript-এর if শর্তে সত্য (truthy) হিসেবে গণ্য হয়?
- 0
- "0"
- ""
- NaN
একটি array items-এ অন্তত একটি উপাদান আছে কি না, JavaScript-এ কোন শর্তটি সঠিকভাবে যাচাই করে?
if (items)if (items == true)if (items !== null)if (items.length > 0)
let out = ""; switch ("b") { case "a": out += "a"; case "b": out += "b"; case "c": out += "c"; break; case "d": out += "d"; } — শেষে out-এর মান কী?
- "bc"
- "b"
- "abc"
- "bcd"
একটি switch-এর দুটি case-এ বন্ধনী ছাড়া একই নামে let total ঘোষণা করলে JavaScript-এ কী হয়?
- দুটি আলাদা variable তৈরি হয়, কোনো সমস্যা নেই
- শুধু যে case চলে, তার ঘোষণা কাজ করে; বাকিটা উপেক্ষিত
- SyntaxError: Identifier 'total' has already been declared
- দ্বিতীয় ঘোষণা প্রথমটির মান নীরবে বদলে দেয়
গ্রেড বের করার কোড: if (m >= 40) g = "C"; else if (m >= 80) g = "A+"; else g = "F"; — m = 85 হলে g-এর মান কী হবে?
- "A+"
- "C"
- "F"
- SyntaxError
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- if / else if / else-এ শর্ত ওপর থেকে দেখা হয়, প্রথম সত্যটিই জেতে; পরিসীমার সারি এক দিকে সাজান (বড় থেকে ছোট), আর সবসময় বন্ধনী দিন।
- শর্তে যেকোনো মান চলে — falsy:
false,0,-0,0n,"",null,undefined,NaN. সংখ্যা, string বা array হলে স্পষ্ট শর্ত লিখুন (arr.length > 0,Number.isNaN). - Guard clause + early return nested পিরামিড চ্যাপ্টা করে; ternary দুই পথের ছোট মানের জন্য, nested নয়।
- switch তুলনা করে
===দিয়ে;breakনা দিলে fall-through; অর্থপূর্ণdefaultরাখুন; case-এরletবন্ধনীতে। - সিদ্ধান্ত যখন “মান → মান”, তখন lookup table — array বা
Map(সাধারণ object হলেObject.hasOwn).
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১২ · লুপ — for, while, for…of আর for…inপরবর্তী পর্বএকই কাজ বারবার — লুপের ধরন আর তাদের ফাঁদ।
- JavaScript প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Java-তে if, switch আর আধুনিক switch expressionতুলনা করুনকড়া boolean শর্ত আর fall-through ছাড়া switch.
- Python-এ if আর matchতুলনা করুনTruthiness-এর আরেক তালিকা আর structural pattern matching.
- C টুলচেইন — compiler, linker আর debuggerপাশের ট্র্যাকযে ভাষা থেকে switch আর fall-through এসেছে।