সরাসরি পর্বে যান
পর্ব ১১ · ৫৬-এর মধ্যে · মডিউল ২

সিদ্ধান্ত — প্রোগ্রাম যেভাবে পথ বেছে নেয় (if, else, switch)

Decisions in C — if, else, else-if, switch, ternary & testing every branch

Taught in Bangla, this lesson covers decision making in C: how C treats true and false, if/else and else-if ladders, the dangling-else trap, switch and fall-through, the ternary operator, guard clauses, and testing every branch.

এই পর্বে যা শিখবেন

  • C কীভাবে “সত্য” আর “মিথ্যা” বোঝে, আর <stdbool.h> কী সুবিধা দেয়
  • if, else, else-if মই আর nested if — কোনটি কখন, আর কোথায় ফাঁদ
  • switch দিয়ে মেনু বানানো, break আর default-এর আসল ভূমিকা
  • Ternary আর guard clause দিয়ে সিদ্ধান্তের কোড ছোট ও পরিষ্কার রাখা
  • Decision table, boundary value আর branch coverage দিয়ে প্রতিটি পথ যাচাই করা

১ · সিদ্ধান্ত মানে কী — আর C কীভাবে “সত্য” বোঝে

মেট্রো রেলের গেটে কার্ড ছোঁয়ালে কী হয়, ভাবুন। গেটের ভেতরের ছোট্ট কম্পিউটার একটি প্রশ্ন করে: “কার্ডে কি যথেষ্ট টাকা আছে?” উত্তর হ্যাঁ হলে দরজা খোলে, না হলে বিপ শব্দ হয়। এখানে দুটি রাস্তা আছে, আর কোন রাস্তায় যাবে, সেটি ঠিক হয় একটি প্রশ্নের উত্তরে।

এ পর্যন্ত আমাদের সব প্রোগ্রাম ওপর থেকে নিচে একটি সোজা লাইনে চলেছে। কিন্তু বাস্তবের প্রায় প্রতিটি প্রোগ্রামকে পথ বেছে নিতে হয় — পাসওয়ার্ড ঠিক না ভুল, নম্বর ৮০-এর বেশি না কম, ব্যবহারকারী মেনুতে ১ চাপলেন না ২. পর্ব ০২-এর flowchart-এ যে ◇ আকারের সিদ্ধান্তের বাক্স এঁকেছিলেন, এই পর্বে সেটিই C কোডে রূপ নেবে।

যে প্রশ্নের উত্তর শুধু “হ্যাঁ” বা “না” হতে পারে, তাকে বলে condition বা শর্ত। যেমন balance >= fare. এখন মূল প্রশ্ন: C এই “হ্যাঁ” আর “না” কীভাবে প্রকাশ করে?

মূল ধারণা

C-তে আলাদা কোনো “হ্যাঁ/না” জিনিস ছিল না — আছে শুধু সংখ্যা। নিয়মটি খুব সোজা: 0 মানে মিথ্যা, 0 ছাড়া যেকোনো মান সত্য। আর >, ==, !=-এর মতো তুলনা সবসময় একটি int ফেরত দেয় — সত্য হলে 1, মিথ্যা হলে 0.

এক্সপ্রেশনমানif-এর চোখে
5 > 31সত্য
5 == 30মিথ্যা
00মিথ্যা
-7-7সত্য (শূন্য নয়, তাই)
'A'65সত্য (অক্ষরও আসলে সংখ্যা)
0.00.0মিথ্যা
0.0010.001সত্য

শুধু 0 আর 1 দিয়ে কাজ চললেও পড়তে অসুবিধা হয়। int is_member = 1; দেখে বোঝা কঠিন যে এটি আসলে হ্যাঁ/না প্রশ্ন। তাই ১৯৯৯ সালের C99 standard থেকে <stdbool.h> header এসেছে। এতে পাওয়া যায় boolean টাইপ bool আর দুটি নাম — true ও false. ভেতরে এগুলো এখনো 1 আর 0, কিন্তু পাঠকের কাছে উদ্দেশ্য পরিষ্কার হয়।

#include <stdbool.h>

bool has_card   = true;
bool is_holiday = false;
bool can_enter  = (balance >= fare);   // তুলনার ফল সরাসরি bool-এ রাখা যায়

সবচেয়ে নতুন standard C23-এ bool, true, false ভাষার নিজস্ব keyword হয়ে গেছে — header ছাড়াই কাজ করে। তবে পুরোনো compiler-এর কথা ভেবে #include <stdbool.h> লিখে রাখাই নিরাপদ অভ্যাস।

২ · if আর else — দুই রাস্তার মোড়

C-তে সিদ্ধান্তের সবচেয়ে মৌলিক যন্ত্র হলো if statement. গঠন খুব সহজ — বন্ধনীর ভেতরে শর্ত, তারপর বাঁকা বন্ধনীর ভেতরে সেই কাজ, যা শর্ত সত্য হলে চলবে। শর্ত মিথ্যা হলে অন্য কিছু করতে চাইলে যোগ করুন else.

if (balance >= fare) {
    open_gate();
    balance = balance - fare;
} else {
    beep();
    printf("Insufficient balance\n");
}

এখানে দুটি block আছে, কিন্তু প্রতিবার ঠিক একটিই চলবে — কখনো দুটি একসাথে নয়, কখনো একটিও বাদ নয়। else ছাড়া শুধু if লিখলে মানে দাঁড়ায়: “শর্ত সত্য হলে এই কাজটি করো, নইলে কিছুই করো না।”

দুটি পুরোনো ফাঁদ, যা আগেই দেখেছেন:

  • = বনাম ==: if (marks = 100) তুলনা করে না, মান বসিয়ে দেয় — আর 100 শূন্য নয়, তাই শর্ত সবসময় সত্য। পর্ব ০৩-এর warning-এর উদাহরণে এটি নিজে চালিয়ে দেখেছিলেন। এখন বুঝতে পারছেন কেন: C-র চোখে “শূন্য নয়” মানেই সত্য।
  • বাঁকা বন্ধনী বাদ দেওয়া: { } না দিলে if-এর অধীনে থাকে শুধু ঠিক পরের একটি statement. দ্বিতীয় লাইনটি দেখতে ভেতরে মনে হলেও আসলে সবসময় চলে। Apple-এর ২০১৪ সালের বিখ্যাত “goto fail” নিরাপত্তা-বাগ ঠিক এভাবেই হয়েছিল — বিস্তারিত পর্ব ০৪-এর ভাবনার প্রশ্নে আছে।
নিয়ম

এক লাইনের কাজ হলেও প্রতিটি if আর else-এর পরে বাঁকা বন্ধনী দিন। এতে খরচ দুটি অক্ষর, কিন্তু বাঁচে ঘণ্টার পর ঘণ্টা debugging. বেশিরভাগ পেশাদার কোডিং নিয়মেও এটি বাধ্যতামূলক।

৩ · else-if মই — অনেক ধাপের নিয়ম

অনেক সিদ্ধান্তে দুটির বেশি রাস্তা থাকে। SSC বা HSC-র গ্রেডের কথা ভাবুন: ৮০ বা বেশি হলে A+, ৭০ থেকে ৭৯ হলে A, ৬০ থেকে ৬৯ হলে A-, ৫০ থেকে ৫৯ হলে B, ৪০ থেকে ৪৯ হলে C, ৩৩ থেকে ৩৯ হলে D, আর ৩৩-এর নিচে F. এমন ধাপে ধাপে নিয়ম লেখার জন্য একের পর এক else if জুড়ে দেওয়া হয়। একে বলে else-if ladder বা else-if মই।

মইয়ের সবচেয়ে গুরুত্বপূর্ণ নিয়ম: ওপর থেকে একটি একটি করে শর্ত দেখা হয়, আর প্রথম যে শর্ত সত্য হয়, শুধু সেটির কাজ চলে — বাকি সব বাদ। নিচের ডায়াগ্রামে একজন ছাত্রের ৭৩ নম্বর মইয়ের ভেতর দিয়ে কীভাবে যায়, দেখুন। তারপর সীমানার দুটি মান — ৮০ আর ৭৯ — দিয়েও চালানো হবে।

1int marks = /* ইনপুট */;2if      (marks >= 80) printf("A+\n");3else if (marks >= 70) printf("A\n");4else if (marks >= 60) printf("A-\n");5else if (marks >= 50) printf("B\n");6else if (marks >= 40) printf("C\n");7else if (marks >= 33) printf("D\n");8else                  printf("F\n");9printf("done\n");
ভ্যারিয়েবলের মান (Watch)
marks
শেষ তুলনা
কয়টি তুলনা হলো
  1. শুরু। marks-এ রাখা হলো 73. এখনো কোনো শর্ত দেখা হয়নি। মইয়ের একদম ওপর থেকে যাত্রা শুরু হবে।
  2. প্রথম ধাপ: 73 ≥ 80? তুলনার ফল 0, অর্থাৎ মিথ্যা। তাই printf("A+\n") চলল না, প্রোগ্রাম পরের else if-এ নামল।
  3. দ্বিতীয় ধাপ: 73 ≥ 70? ফল 1 — সত্য! এই লাইনের কাজটি চলল, স্ক্রিনে এলো A. খেয়াল করুন, এখানে “৭৯-এর কম কি না” আলাদা করে দেখতে হয়নি — ৮০ বা বেশি হলে তো আগের ধাপেই থেমে যেত।
  4. বাকি মই পুরো বাদ। লাইন ৪ থেকে ৮ একবারও দেখা হলো না। একটি শর্ত সত্য হওয়ামাত্র প্রোগ্রাম মইয়ের শেষে লাফিয়ে যায়, তারপর লাইন ৯ চলে। মোট তুলনা হলো মাত্র ২টি।
  5. নতুন রান — সীমানা: marks = 80. 80 ≥ 80? সত্য, তাই A+. এখানে >=-এর বদলে ভুল করে > লেখা থাকলে ঠিক ৮০ পাওয়া ছাত্রটি A পেত — অথচ ৭৩ বা ৯৫ দিয়ে পরীক্ষা করলে এই ভুল কখনো ধরা পড়ত না।
  6. নতুন রান — সীমানার ঠিক নিচে: marks = 79. প্রথম শর্ত মিথ্যা, দ্বিতীয়টি সত্য, ফল A. সীমানা ঠিকমতো কাজ করছে কি না জানতে হলে ঠিক সীমানায় (৮০) আর ঠিক এক ঘর নিচে (৭৯) — দুটিতেই পরীক্ষা করতে হয়।
  7. নতুন রান — marks = 32. ছয়টি শর্তই একে একে মিথ্যা হলো, তাই শেষের else চলল: F. else হলো মইয়ের “বাকি সবাই” — কোনো শর্ত না মিললে এটিই ধরে। যত নিচে নামে, তত বেশি তুলনা লাগে।

শর্তের ক্রম বদলালে ফলও বদলায়

মইয়ের নিয়ম “প্রথম সত্যটিই জেতে” — তাই ক্রম ভুল হলে কোড চলবে ঠিকই, কিন্তু ভুল উত্তর দেবে। যদি কেউ লেখেন if (marks >= 33) সবার আগে, তাহলে ৩৩ বা বেশি পাওয়া প্রত্যেকে — ৯৫ পাওয়া ছাত্রও — পাবে D, কারণ প্রথম ধাপেই শর্ত সত্য হয়ে যায়। নিয়ম: >= দিয়ে লিখলে বড় সীমা আগে, ছোট সীমা পরে।

এবার একটি ব্যবসার উদাহরণ নিজে চালিয়ে দেখুন। ধরুন একটি অনলাইন দোকানের ডেলিভারি চার্জের নিয়ম (সংখ্যাগুলো শুধু উদাহরণ, কোনো আসল কোম্পানির রেট নয়): অর্ডার ১,০০০ টাকা বা বেশি হলে ডেলিভারি ফ্রি, ৫০০ থেকে ৯৯৯ টাকায় ৪০ টাকা, ৫০০-এর কম হলে ৬০ টাকা। আর ০ বা ঋণাত্মক অঙ্ক মানে ভুল অর্ডার। পরীক্ষার জন্য আমরা প্রতিটি সীমানার দুই পাশ বেছে নিয়েছি:

#include <stdio.h>

int delivery_charge(int amount) {
    int charge;
    if (amount <= 0)          charge = -1;  // ভুল অর্ডার
    else if (amount >= 1000)  charge = 0;   // ফ্রি
    else if (amount >= 500)   charge = 40;
    else                      charge = 60;
    return charge;
}

void check(int amount) {
    printf("amount %4d -> charge %d\n", amount, delivery_charge(amount));
}

int main(void) {
    check(0);   check(1);   check(499);
    check(500); check(999); check(1000);
    return 0;
}

ছয়টি পরীক্ষার মান এলোমেলো নয়। 0 আর 1 হলো “ভুল অর্ডার”-এর সীমানার দুই পাশ, 499 আর 500 হলো ৪০ টাকার সীমানা, 999 আর 1000 হলো ফ্রি ডেলিভারির সীমানা। প্রত্যাশিত ফল: -1, 60, 60, 40, 40, 0. এবার নিজে একটি ভুল ঢুকিয়ে দেখুন — >= 1000-কে > 1000 করুন। শুধু check(1000)-এর ফল বদলে 40 হবে। অর্থাৎ ঠিক সীমানায় পরীক্ষা না থাকলে এই ভুল কখনো ধরা পড়ত না। এমন সীমানার মানকে বলে boundary value, আর পর্ব ০২-এর edge case ধারণার এটিই সবচেয়ে কাজের রূপ।

৪ · if-এর ভেতরে if — আর dangling else-এর ফাঁদ

কখনো একটি সিদ্ধান্তের পরে আরেকটি সিদ্ধান্ত নিতে হয়। bKash বা Nagad-এ Send Money করার কথা ভাবুন: প্রথমে দেখা হয় অ্যাকাউন্ট সচল কি না, তারপর দেখা হয় ব্যালেন্স যথেষ্ট কি না। একটি if-এর ভেতরে আরেকটি if রাখাকে বলে nested if.

Nested if-এ একটি বিখ্যাত ফাঁদ আছে — dangling else. বাঁকা বন্ধনী না দিলে একটি else দুটি if-এর যেকোনোটির হতে পারে বলে মনে হয়। আপনি indentation দিয়ে যা বোঝাতে চাইছেন, compiler সেটি দেখেই না — তার নিজের একটি নির্দিষ্ট নিয়ম আছে। নিচে ধাপে ধাপে দেখুন:

send.cপ্রোগ্রামার যেভাবে লিখেছেন
if (balance > 0)
    if (balance >= amount)
        printf("Send OK\n");
else
    printf("Your balance is zero\n");   // ইচ্ছা: বাইরের if-এর else
compiler-এর চোখেelse যায় সবচেয়ে কাছের if-এর সাথে
if (balance > 0) {
    if (balance >= amount)
        printf("Send OK\n");
    else
        printf("Your balance is zero\n");
}
রান ১balance = 0, amount = 500
balance > 0 ?  0 (মিথ্যা)
→ বাইরের if-এর কোনো else নেই
→ কিছুই ছাপা হলো না

$ ./send
(খালি স্ক্রিন)
রান ২balance = 100, amount = 500
balance > 0 ?        1 (সত্য)
balance >= amount ?  0 (মিথ্যা)
→ ভেতরের if-এর else চলল

$ ./send
Your balance is zero   // অথচ ব্যালেন্স 100!
send.c (ঠিক করা)বন্ধনী দিলে আর কোনো সন্দেহ থাকে না
if (balance > 0) {
    if (balance >= amount) {
        printf("Send OK\n");
    } else {
        printf("Not enough balance\n");
    }
} else {
    printf("Your balance is zero\n");
}
  1. প্রোগ্রামারের ইচ্ছা। else-টিকে বাইরের if-এর সমান সারিতে লেখা হয়েছে — অর্থাৎ লেখকের মনে হচ্ছে “ব্যালেন্স 0 হলে এই মেসেজ দাও।” কিন্তু কোনো বাঁকা বন্ধনী নেই।
  2. Compiler-এর নিয়ম: else যায় সবচেয়ে কাছের, এখনো জোড়া-না-পাওয়া if-এর সাথে। Compiler ফাঁকা জায়গা (indentation) একেবারেই দেখে না। তাই এই else আসলে ভেতরের if (balance >= amount)-এর। GCC-তে -Wall দিলে এখানে warning দেয়: suggest explicit braces to avoid ambiguous 'else'.
  3. রান ১: ব্যালেন্স সত্যিই 0. বাইরের শর্ত মিথ্যা। কিন্তু বাইরের if-এর তো কোনো else নেই! তাই কিছুই ছাপা হয় না — ব্যবহারকারী শুধু একটি খালি স্ক্রিন দেখেন। যে মেসেজের জন্য কোডটি লেখা, ঠিক সেই ক্ষেত্রেই সেটি আসে না।
  4. রান ২: ব্যালেন্স 100, পাঠাতে চান 500. বাইরের শর্ত সত্য, ভেতরেরটা মিথ্যা — তাই ভেতরের if-এর else চলল। স্ক্রিনে এলো “ব্যালেন্স শূন্য”, অথচ ব্যালেন্স 100! একটি বাগ, দুটি ভুল আচরণ।
  5. সমাধান: সব জায়গায় বাঁকা বন্ধনী। এখন কোন else কার, সেটি কোডেই লেখা আছে — indentation-এর ওপর নির্ভর করতে হয় না। সাথে একটি বাদ পড়া ক্ষেত্রও (ব্যালেন্স আছে কিন্তু কম) ধরা পড়ল। বন্ধনী শুধু বাগ ঠেকায় না, চিন্তাকেও পরিষ্কার করে।

গভীর nesting কমানো

তিন-চার স্তর nested if হলে কোড ডান দিকে সরতে সরতে পড়াই কঠিন হয়ে যায়। দুটি উপায়ে এটি সমতল করা যায়। প্রথম উপায় হলো পর্ব ০৭-এর && (এবং) দিয়ে শর্ত জোড়া: if (balance > 0 && balance >= amount). দ্বিতীয় উপায় guard clause — যা আমরা অংশ ৭-এ দেখব।

৫ · switch — মেনু থেকে একটি বেছে নেওয়া

মোবাইলে *247#-এর মতো USSD কোড ডায়াল করলে একটি মেনু আসে: ১ চাপলে Send Money, ২ চাপলে Cash Out, ৩ চাপলে Recharge… এখানে একটিমাত্র ভ্যারিয়েবল (আপনার চাপা সংখ্যা) কয়েকটি নির্দিষ্ট মানের কোনটির সাথে মেলে, সেটিই প্রশ্ন। এমন ক্ষেত্রে if-মইয়ের চেয়ে পরিষ্কার একটি যন্ত্র আছে — switch.

#include <stdio.h>

int main(void) {
    int choice;
    printf("1. Send Money\n2. Cash Out\n3. Recharge\n4. My Account\n");
    printf("Select: ");
    if (scanf("%d", &choice) != 1) return 1;
    printf("%d\n", choice);
    switch (choice) {
        case 1:  printf("Enter receiver number...\n"); break;
        case 2:  printf("Enter agent number...\n");    break;
        case 3:  printf("Enter mobile number...\n");   break;
        case 4:  printf("Showing account info...\n");  break;
        default: printf("Invalid option. Try again.\n");
    }
    return 0;
}

এই উদাহরণে ইনপুট হিসেবে আগে থেকেই 2 দেওয়া আছে, তাই চালালে আসবে Enter agent number.... মেনুটি কাল্পনিক — কোনো আসল সেবার হুবহু নকল নয়। প্রতিটি case হলো একটি লেবেল: “মান এটি হলে এখান থেকে শুরু করো।” প্রতিটির শেষের break বলে, “কাজ শেষ, switch থেকে বেরিয়ে যাও।” আর default ধরে বাকি সব মান — 7, 0, -1, যা-ই চাপুন। কোডটি কপি করে OnlineGDB-তে চালিয়ে 5 বা -1 দিয়ে দেখুন। printf("%d\n", choice) লাইনটি শুধু অনলাইনে চালালে আপনার দেওয়া ইনপুট দেখানোর জন্য।

switch-এর তিনটি কঠিন নিয়ম

  • শুধু পূর্ণসংখ্যা বা অক্ষর। switch-এর ভেতরের মান integer জাতীয় হতে হবে — int, char, long ইত্যাদি। double বা string (যেমন "bkash") দিয়ে switch চলে না।
  • প্রতিটি case-এ ধ্রুবক মান। case 1: বা case 'y': লেখা যায়, কিন্তু case x: (ভ্যারিয়েবল) বা case marks >= 80: (পরিসর) লেখা যায় না। একই মান দুটি case-এ থাকলেও error.
  • শুধু “সমান কি না” প্রশ্ন। switch প্রশ্ন করে “মানটি কি ঠিক এটি?” — “এর চেয়ে বড় কি না” নয়। তাই গ্রেডের মতো পরিসরভিত্তিক নিয়মে switch অচল।

৬ · break ভুললে কী হয় — fall-through

switch বুঝতে একটি জিনিস মাথায় রাখুন: case আসলে একটি ঠিকানার লেবেল, আলাদা কোনো ঘর নয়। switch মান দেখে সরাসরি মিলে যাওয়া লেবেলে লাফ দেয়, তারপর সেখান থেকে নিচের দিকে লাইনের পর লাইন চলতে থাকে — যতক্ষণ না break বা switch-এর শেষ আসে। মাঝখানের অন্য case লেবেল তাকে থামায় না। একে বলে fall-through. নিচে break ছাড়া আর break সহ — দুই রকম কোড পাশাপাশি দেখুন:

1int choice = 2;2switch (choice) {3    case 1:  printf("Send Money\n");4    case 2:  printf("Cash Out\n");5    case 3:  printf("Recharge\n");6    default: printf("Invalid\n");7}
break ছাড়া
choice
কয়টি লাইন ছাপা হলো
1int choice = 2;2switch (choice) {3    case 1:  printf("Send Money\n"); break;4    case 2:  printf("Cash Out\n");   break;5    case 3:  printf("Recharge\n");   break;6    default: printf("Invalid\n");7}
break সহ
choice
কয়টি লাইন ছাপা হলো
  1. সরাসরি লাফ। choice = 2. switch “case 1 কি মেলে? না। case 2?” — এভাবে একটি একটি করে প্রশ্ন করে না, বরং সরাসরি case 2: লেবেলে লাফ দেয়। এখানে কোনো লাইনেই break নেই।
  2. case 2-এর কাজ চলল। স্ক্রিনে এলো Cash Out. এ পর্যন্ত সব ঠিক।
  3. Fall-through শুরু! break না থাকায় প্রোগ্রাম থামল না, নিচের লাইনে নেমে গেল। case 3: শুধু একটি লেবেল, তাই সেটি বাধা দেয় না — Recharge-ও ছাপা হলো। ব্যবহারকারী কিন্তু ৩ চাপেননি!
  4. default-ও চলল। একইভাবে নিচে নেমে Invalid-ও ছাপা হলো। একটি অপশন বেছে নিয়ে ব্যবহারকারী তিনটি পরস্পরবিরোধী মেসেজ পেলেন।
  5. switch শেষ। বন্ধ বন্ধনীতে পৌঁছে তবেই থামল। Compiler কোনো error দেয়নি — কারণ C-র নিয়মে এটি বৈধ। -Wextra চালু থাকলে GCC অবশ্য সতর্ক করে: this statement may fall through.
  6. এবার break সহ। আবার case 2:-এ লাফ, আর Cash Out ছাপা হলো।
  7. break-এর কাজ। একই লাইনের break বলল “switch থেকে বেরিয়ে যাও।” case 3 আর default-এর লাইনগুলো একবারও চলল না।
  8. পরিষ্কার ফল। একটি অপশন, একটি মেসেজ। শেষ case (এখানে default)-এর পরে break না দিলেও চলে, কারণ নিচে আর কিছু নেই — তবু অনেকে নিয়ম হিসেবে সেখানেও দেন, যাতে পরে নতুন case যোগ করলে ভুল না হয়।

ইচ্ছাকৃত fall-through — একাধিক মানে একই কাজ

Fall-through সবসময় বাগ নয়। একাধিক মানে একই কাজ করতে চাইলে এটি কাজে লাগে। যেমন ব্যবহারকারী ছোট বা বড় হাতের y চাপলে দুটোই “হ্যাঁ”:

switch (answer) {
    case 'y':
    case 'Y':
        confirm_payment();
        break;
    case 'n':
    case 'N':
        cancel_payment();
        break;
    default:
        printf("Please press y or n\n");
}

এখানে case 'y':-এর নিচে কোনো কাজ নেই, তাই সেটি সরাসরি case 'Y':-এর কাজে গড়িয়ে যায় — ঠিক যা চাই। কিন্তু একটি case-এ কিছু কাজ করে তারপর ইচ্ছা করে পরের case-এ গড়ানো বিরল ও বিপজ্জনক। সেক্ষেত্রে অবশ্যই /* fall through */ ধরনের একটি কমেন্ট লিখে রাখুন। C23 standard-এ এর জন্য আলাদা একটি চিহ্নই যোগ হয়েছে — [[fallthrough]]; — যা দেখে compiler বোঝে “এটি ইচ্ছাকৃত, warning দিও না।”

switch না if-মই — কখন কোনটি

পরিস্থিতিভালো পছন্দকারণ
একটি ভ্যারিয়েবল, কয়েকটি নির্দিষ্ট পূর্ণসংখ্যা মান (মেনু, কমান্ড কোড)switchপড়তে সহজ; compiler বাদ পড়া মান নিয়েও সতর্ক করতে পারে
একটি অক্ষর ('y', 'n', '+')switchঅক্ষরও আসলে পূর্ণসংখ্যা
পরিসর (নম্বর ৮০ বা বেশি, অর্ডার ৫০০-এর কম)if-মইswitch শুধু “সমান কি না” দেখে
দশমিক সংখ্যা বা stringif-মইswitch এগুলো নেয় না
একাধিক ভ্যারিয়েবল মিলিয়ে শর্ত (বয়স এবং দিন)if (অথবা decision table)switch একবারে একটি মানই দেখে

৭ · ছোট সিদ্ধান্তের দুটি হাতিয়ার — ternary আর guard clause

Ternary — এক লাইনে দুই পথ

অনেক সময় সিদ্ধান্তের কাজ শুধু দুটি মানের একটি বেছে নেওয়া। এর জন্য C-তে একটি ছোট রূপ আছে — ternary operator: শর্ত ? সত্য-হলে-এই-মান : মিথ্যা-হলে-এই-মান.

// if-else দিয়ে পাঁচ লাইন
int fee;
if (is_student) {
    fee = 20;
} else {
    fee = 50;
}

// ternary দিয়ে এক লাইন — অর্থ হুবহু এক
fee = is_student ? 20 : 50;

Ternary তখনই ভালো, যখন দুটি সহজ মানের মধ্যে একটি বেছে নিতে হয়। একটির ভেতরে আরেকটি ternary বসালে (a ? b : c ? d : e) কোড ধাঁধা হয়ে যায় — সেখানে সাধারণ if-মই লিখুন। মনে রাখুন, কোড একবার লেখা হয় কিন্তু পড়া হয় বহুবার।

Guard clause — আগে বাধা সরান, তারপর আসল কাজ

বাসে ওঠার আগে কন্ডাক্টর আগেই বলে দেন, “সিট নেই” — তারপর আর ভাড়ার আলোচনাই হয় না। কোডেও একই কৌশল কাজে লাগে। ফাংশনের শুরুতেই ভুল বা বিশেষ ইনপুট ধরে সঙ্গে সঙ্গে return করে দিন — একে বলে guard clause. তাতে বাকি কোড ধরে নিতে পারে যে ইনপুট ঠিক আছে, আর nesting-এর সিঁড়ি আর থাকে না।

int withdraw(int balance, int amount) {
    if (amount > 0) {
        if (amount <= balance) {
            if (amount <= 25000) {
                return balance - amount;
            } else {
                return -3;
            }
        } else {
            return -2;
        }
    } else {
        return -1;
    }
}

আসল কাজটি (balance - amount) তিন স্তর গভীরে লুকানো, আর কোন else কোন শর্তের, মেলাতে চোখ ওপর-নিচ করতে হয়।

int withdraw(int balance, int amount) {
    if (amount <= 0)      return -1;   // ভুল অঙ্ক
    if (amount > balance) return -2;   // ব্যালেন্স কম
    if (amount > 25000)   return -3;   // সীমার বেশি (উদাহরণ)

    return balance - amount;           // আসল কাজ
}

প্রতিটি বাধা নিজের লাইনে, কারণসহ। আসল কাজ একদম শেষে, কোনো nesting ছাড়া। এখানে ২৫,০০০ শুধু উদাহরণের সীমা। এই ধরনকে early return-ও বলে।

৮ · Decision table — কোড লেখার আগে সব পথ কাগজে

নিয়মে দুই বা তার বেশি জিনিস একসাথে বিবেচনা করতে হলে মাথায় রেখে if লেখা ঝুঁকিপূর্ণ — কোনো একটি মিশ্রণ বাদ পড়ে যায়। পেশাদাররা তখন আগে একটি decision table বানান: প্রতিটি সম্ভাব্য অবস্থা এক সারিতে, পাশে তার ফল।

ধরুন একটি চিড়িয়াখানার টিকিটের নিয়ম (দামগুলো কাল্পনিক উদাহরণ): ৩ বছরের কম শিশু ফ্রি; ৬০ বা বেশি বয়সীদের সব দিন ২৫ টাকা; ৩ থেকে ১২ বছর হলে সাধারণ দিনে ২০, ছুটির দিনে ৩০ টাকা; বাকি সবাই সাধারণ দিনে ৫০, ছুটির দিনে ৮০ টাকা। লেখায় পড়ে কি সব ক্ষেত্র পরিষ্কার? এবার টেবিলে দেখুন:

সারিবয়সদিনদাম (টাকা)
R0ঋণাত্মকযেকোনোভুল ইনপুট (-1)
R1০–২যেকোনো০
R2৩–১২সাধারণ২০
R3৩–১২ছুটি৩০
R4১৩–৫৯সাধারণ৫০
R5১৩–৫৯ছুটি৮০
R6৬০ বা বেশিযেকোনো২৫

টেবিল বানাতে গিয়েই একটি প্রশ্ন বেরিয়ে এলো, যা লেখায় ছিল না: ঋণাত্মক বয়স এলে কী হবে? সেজন্য R0 যোগ হলো। এটাই decision table-এর আসল শক্তি — ফাঁকা ঘর মানে নিয়মে ফাঁক। এবার দেখুন টেবিলটি কীভাবে সারি ধরে ধরে কোডে রূপ নেয়, আর প্রতিটি সারি একটি করে পরীক্ষাও হয়ে যায়:

1int ticket_price(int age, bool holiday) {2    if (age < 0)   return -1;  // R03    if (age < 3)   return 0;   // R14    if (age >= 60) return 25;  // R65    if (age <= 12) return holiday ? 30 : 20;  // R2, R36    return holiday ? 80 : 50;  // R4, R57}
পরীক্ষার ইনপুট ও ফল
age
holiday
কোন সারি
দাম
  1. সারি R0 → guard clause. ভুল ইনপুট সবার আগে সরিয়ে দেওয়া হলো। পরীক্ষা: বয়স -4 দিলে লাইন ২-এই -1 ফেরত আসে। বাকি কোড এখন নিশ্চিন্তে ধরে নিতে পারে বয়স 0 বা বেশি।
  2. সারি R1 → লাইন ৩. পরীক্ষা: বয়স 2 (সীমানার ঠিক নিচে), ছুটির দিন। লাইন ২ মিথ্যা, লাইন ৩ সত্য — দাম 0. খেয়াল করুন, “দিন” এখানে কোনো প্রশ্নই নয়; টেবিলের “যেকোনো” মানে কোডে সেই ভ্যারিয়েবল দেখার দরকার নেই।
  3. সারি R6 → লাইন ৪ — ক্রম বদলেছে! টেবিলে প্রবীণরা শেষ সারিতে, কিন্তু কোডে তাদের আগে আনা হয়েছে, কারণ তাদের দাম দিনের ওপর নির্ভর করে না — আগে সরিয়ে দিলে পরের লাইনগুলো সহজ হয়। পরীক্ষা: বয়স 65, ছুটি — 25.
  4. সারি R2 → লাইন ৫, ternary-র “মিথ্যা” দিক। পরীক্ষা: বয়স 3 — শিশুর সীমানার একদম শুরু। 3 < 3 মিথ্যা, 3 <= 12 সত্য, ছুটি নয় — 20. এই একটি পরীক্ষাই প্রমাণ করে লাইন ৩-এ <= ভুল করে লেখা হয়নি।
  5. সারি R3 → লাইন ৫, ternary-র “সত্য” দিক। পরীক্ষা: বয়স 12 — শিশুর সীমানার একদম শেষ, ছুটির দিন — 30. একই লাইনের ভেতরে দুটি পথ আছে, তাই দুটিরই আলাদা পরীক্ষা লাগে।
  6. সারি R4 → লাইন ৬. পরীক্ষা: বয়স 13 — বড়দের দামের প্রথম বছর, সাধারণ দিন — 50.
  7. সারি R5 → লাইন ৬-এর অন্য দিক। পরীক্ষা: বয়স 59 — ৬০-এর সীমানার ঠিক নিচে, ছুটি — 80. সাতটি সারি, সাতটি পরীক্ষা, সবগুলো পাস। কোডের প্রতিটি পথ অন্তত একবার চলেছে — এটিকেই বলে পূর্ণ branch coverage.

Branch coverage — প্রতিটি পথের জন্য একটি পরীক্ষা

প্রতিটি সিদ্ধান্তে রাস্তা দুই বা তার বেশি ভাগে ভাগ হয়। প্রতিটি ভাগকে বলে branch. পরীক্ষার সময় প্রতিটি branch অন্তত একবার চলেছে কি না — এর হিসাবকে বলে branch coverage. যে branch কখনো চলেনি, তার ভেতরের বাগ সম্পর্কে আপনি কিছুই জানেন না। GCC-তে --coverage flag আর gcov টুল দিয়ে এটি মাপা যায় — পর্ব ৩২-এ (টেস্টিং) বিস্তারিত দেখবেন।

সিদ্ধান্তের কোড পরীক্ষার তিন নিয়ম
  • প্রতিটি branch: প্রতিটি if-এর সত্য ও মিথ্যা দিক, প্রতিটি case আর default — সব অন্তত একবার।
  • প্রতিটি সীমানার দুই পাশ: age < 3 থাকলে 2 আর 3 দুটোই।
  • অস্বাভাবিক ইনপুট: শূন্য, ঋণাত্মক, মেনুতে নেই এমন সংখ্যা।

৯ · AI-এর যুগে — সিদ্ধান্তের কোড যাচাই

AI-কে “ডেলিভারি চার্জের ফাংশন লিখে দাও” বললে সে সাধারণত দেখতে সুন্দর, চলেও এমন কোড দেয়। সমস্যা হলো, সিদ্ধান্তের কোডের ভুল প্রায় কখনো সাধারণ ইনপুটে ধরা পড়ে না — ধরা পড়ে সীমানায়, বা এমন ক্ষেত্রে যার কথা কেউ ভাবেনি। আর AI-এর লেখা পরীক্ষাগুলোও প্রায়ই শুধু “সুন্দর” মান দিয়ে করা: ২০০, ৭০০, ১,৫০০.

AI-এর সাধারণ ভুলফলযেভাবে ধরবেন
>=-এর জায়গায় > (বা উল্টো)ঠিক সীমানার মানে ভুল দাম বা গ্রেডসীমানায় ও তার ঠিক নিচে পরীক্ষা
শর্তের ক্রম ভুলনিচের branch কখনো চলে নাbranch coverage — কোনো পথ কি কখনো চলেনি?
switch-এ break বাদএকটির বদলে কয়েকটি কাজ-Wextra-এর fall-through warning
default বা শেষের else নেইঅপ্রত্যাশিত ইনপুটে নীরবতামেনুর বাইরের মান দিয়ে পরীক্ষা
চার-পাঁচ স্তরের nested ifপড়া ও যাচাই কঠিন, dangling else-এর ঝুঁকিguard clause দিয়ে সমতল করতে বলুন

সমাধান হলো AI-কে আগে কোড নয়, decision table আর সীমানার পরীক্ষা চাওয়া। টেবিল ভুল হলে আপনি নিজেই সহজে ধরতে পারবেন — কোড পড়ার চেয়ে টেবিল পড়া অনেক সহজ।

আমি C-তে একটি ফাংশন লিখতে চাই: int delivery_charge(int amount). নিয়ম: amount 1000 বা বেশি হলে চার্জ 0, 500 থেকে 999 হলে 40, 500-এর কম হলে 60, আর 0 বা ঋণাত্মক হলে -1 (ভুল অর্ডার). কোড লেখার আগে: ১. পুরো নিয়মটি একটি decision table আকারে দেখাও। নিয়মে কোনো ফাঁক বা অস্পষ্টতা থাকলে বলো। ২. প্রতিটি সীমানার ঠিক ওপরে ও নিচের মান দিয়ে boundary test-এর তালিকা দাও (ইনপুট ও প্রত্যাশিত ফল). ৩. তারপর কোড দাও — guard clause ব্যবহার করো, nesting দুই স্তরের বেশি নয়, প্রতিটি if-এ বাঁকা বন্ধনী। ৪. শেষে বলো কোন পরীক্ষা কোন branch চালায়, যাতে প্রতিটি branch অন্তত একবার চলে।

AI-এর লেখা সিদ্ধান্তের কোড হাতে পেলে — চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১C কেন যেকোনো non-zero মানকে “সত্য” ধরে? আলাদা bool টাইপ শুরু থেকেই রাখল না কেন?

উত্তর লুকিয়ে আছে C-র জন্মের পরিবেশে। C তৈরি হয়েছিল B আর BCPL নামের দুটি পুরোনো ভাষা থেকে, যেগুলোতে কার্যত একটিই ডেটা টাইপ ছিল — মেশিনের একটি “word”. সেখানে সত্য-মিথ্যার জন্য আলাদা টাইপ রাখার প্রশ্নই ছিল না।

দ্বিতীয় কারণ হার্ডওয়্যার। CPU-র কাছে সবচেয়ে সস্তা প্রশ্নগুলোর একটি হলো “এই মান কি শূন্য?” প্রায় প্রতিটি CPU-তে “শূন্য হলে লাফ দাও” বা “শূন্য না হলে লাফ দাও” ধরনের নির্দেশ আছে। তাই if (x)-কে সরাসরি এমন একটি নির্দেশে অনুবাদ করা যায় — কোনো বাড়তি রূপান্তর লাগে না। C-র দর্শনই ছিল মেশিনের যতটা সম্ভব কাছে থাকা।

এই নিয়ম থেকে কিছু সুবিধাজনক অভ্যাসও তৈরি হয়েছে, যা পরের পর্বগুলোতে দেখবেন: if (count) মানে “গণনা শূন্য নয়”, আর string-এর শেষে শূন্য অক্ষর থাকে বলে “শূন্য না আসা পর্যন্ত চলো” লেখা খুব সহজ।

দাম কী দিতে হলো? যেহেতু যেকোনো মানই শর্ত হতে পারে, if (x = 5) ব্যাকরণগতভাবে বৈধ — আর এটিই =/== বাগের মূল। কেউ কেউ তাই ধ্রুবক বাঁ দিকে লেখেন (if (5 == x)) যাতে ভুলে = হলে compile-ই না হয়; একে বলে “Yoda condition”. আজকাল -Wall-এর warning-ই এ কাজ ভালোভাবে করে।

পরে ভাষা ধীরে ধীরে বদলেছে: C99 এনেছে _Bool টাইপ আর <stdbool.h>, আর C23-এ bool, true, false পুরোদস্তুর keyword. Java বা Rust-এর মতো পরের ভাষাগুলো আরও কড়া — সেখানে if-এর ভেতরে সংখ্যা দিলেই error, শুধু boolean চলে।

মূল উপলব্ধি: ভাষার প্রতিটি “অদ্ভুত” নিয়মের পেছনে সাধারণত তার সময়ের হার্ডওয়্যার আর সীমাবদ্ধতা থাকে। সুবিধা আর নিরাপত্তার মধ্যে C সুবিধা বেছে নিয়েছিল; নতুন ভাষাগুলো সেই অভিজ্ঞতা থেকে নিরাপত্তার দিকে ঝুঁকেছে।

প্র ০২switch-এ fall-through কেন রাখা হলো, যদি এটি এত বাগের উৎস? নতুন ভাষাগুলো কী করেছে?

Fall-through আসলে কোনো আলাদা “ফিচার” নয় — এটি switch-এর গঠনের স্বাভাবিক ফল। C-র switch মূলত একটি হিসাব করা লাফ: মান দেখে একটি লেবেলে goto-র মতো লাফ দেওয়া। লেবেল কোডকে ভাগ করে না, শুধু একটি ঠিকানা চিহ্নিত করে। তাই লাফের পরে কোড স্বাভাবিকভাবে নিচের দিকে চলতেই থাকে, যতক্ষণ না break নামের আরেকটি লাফ আসে। Assembly-তে কোড এভাবেই চলে, আর C সেটিকে প্রায় হুবহু রেখেছে।

এর সুবিধাও আছে। একাধিক case-এ একই কাজ (case 'y': case 'Y':) খুব সহজে লেখা যায়। আর একটি বিখ্যাত চতুর কৌশল আছে — ১৯৮৩ সালে Lucasfilm-এ Tom Duff-এর লেখা “Duff's device”, যেখানে একটি switch-এর case-গুলো একটি লুপের ভেতরে ছড়িয়ে দিয়ে fall-through কাজে লাগানো হয়েছিল। এটি দেখায় C কতটা নমনীয় — আবার এটিও দেখায় কেন কোড এত সহজে ধাঁধা হয়ে যায়।

কিন্তু বাস্তবে ইচ্ছাকৃত fall-through-এর চেয়ে ভুলে break বাদ পড়া অনেক বেশি ঘটে। তাই পরের ভাষাগুলো ডিফল্ট উল্টে দিয়েছে:

  • Go আর Swift: প্রতিটি case নিজে থেকেই শেষ হয়; গড়াতে চাইলে স্পষ্ট করে fallthrough লিখতে হয়।
  • C#: কোনো কাজ আছে এমন case থেকে চুপচাপ পরের case-এ গড়ানো compile error.
  • Java: পুরোনো switch C-র মতোই, কিন্তু নতুন “arrow” রূপে (case 1 ->) fall-through নেই।

C নিজে পুরোনো আচরণ বদলাতে পারে না — তাহলে কোটি কোটি লাইনের পুরোনো কোড ভেঙে যাবে। তাই C নিয়েছে মাঝামাঝি পথ: GCC-তে -Wextra দিলে সন্দেহজনক fall-through-এ warning আসে, আর C23-এ এসেছে [[fallthrough]]; চিহ্ন, যা দিয়ে বলা যায় “এটি ইচ্ছাকৃত।”

মূল উপলব্ধি: ভালো নকশায় ডিফল্ট আচরণ হওয়া উচিত সবচেয়ে নিরাপদটি, আর ঝুঁকির কাজটি হওয়া উচিত স্পষ্টভাবে চাইতে হয় এমন। পুরোনো সিদ্ধান্ত বদলানো কঠিন — তাই প্রথমবারেই ডিফল্ট ঠিক করা এত গুরুত্বপূর্ণ।

প্র ০৩Compiler switch-কে কীভাবে অনুবাদ করে? switch কি সবসময় if-মইয়ের চেয়ে দ্রুত?

if-মই মানে একের পর এক তুলনা — শেষের দিকের মানে পৌঁছাতে আগের সবগুলো পেরোতে হয়। গ্রেডের stepper-এ দেখেছেন, ৩২ নম্বরের জন্য ছয়টি তুলনা লেগেছে।

switch-এর case-গুলো যদি কাছাকাছি পূর্ণসংখ্যা হয় (যেমন 1, 2, 3, 4, 5), compiler একটি চমৎকার কৌশল ব্যবহার করতে পারে — jump table. মেমরিতে একটি তালিকা বানানো হয়, যার প্রতিটি ঘরে একটি case-এর কোডের ঠিকানা। তারপর মান দিয়ে সরাসরি তালিকার সেই ঘরে গিয়ে লাফ — case যতগুলোই থাকুক, সময় প্রায় একই। ঠিক যেমন লিফটে ৭ চাপলে লিফট ১ থেকে ৬ পর্যন্ত প্রতিটি তলায় থেমে জিজ্ঞেস করে না।

কিন্তু case-গুলো ছড়ানো হলে (যেমন 1, 500, 90000) এমন তালিকা বানালে বিশাল জায়গা নষ্ট হবে। তখন compiler সাধারণত মানগুলো সাজিয়ে binary search-এর মতো তুলনার গাছ বানায়, বা সাধারণ তুলনার সারিই রাখে। উল্টো দিকেও ঘটে: optimization চালু থাকলে আধুনিক compiler একটি সরল if-মইকেও চিনে jump table বানাতে পারে।

তাই “switch দ্রুত” কথাটি অর্ধসত্য। আসল উত্তর হলো — compiler খুব বুদ্ধিমান, আর কয়েকটি তুলনার পার্থক্য প্রায় কোনো প্রোগ্রামেই বোঝা যায় না। গতির কথা ভাববেন তখনই, যখন মেপে দেখেছেন সেই অংশটিই ধীর। godbolt.org-এ একটি পাঁচ-case-এর switch লিখে -O2 দিয়ে দেখলে নিজেই তালিকাটি খুঁজে পাবেন।

তাহলে switch কেন বেছে নেবেন? পড়ার সুবিধার জন্য, আর কারণ switch-এ compiler আপনাকে সাহায্য করতে পারে — যেমন একটি enum-এর কোনো মানের case বাদ পড়লে GCC -Wall-এ সতর্ক করে (তখন যদি default না থাকে)। if-মইয়ে এমন সাহায্য পাওয়া যায় না।

মূল উপলব্ধি: আপনি কোড লেখেন মানুষের জন্য, compiler সেটিকে মেশিনের জন্য দ্রুত করে। গঠন বেছে নিন অর্থ ও পরিষ্কারতা দেখে; গতির সিদ্ধান্ত নিন মেপে।

প্র ০৪CPU-র কাছে একটি if-এর আসল দাম কত? “Branch prediction” কী?

আধুনিক CPU একটি কারখানার assembly line-এর মতো কাজ করে — একটি নির্দেশ শেষ হওয়ার আগেই পরের অনেকগুলো নির্দেশ লাইনে ঢুকে আধা-তৈরি অবস্থায় থাকে। একে বলে pipeline. কিন্তু একটি if-এ এসে সমস্যা: শর্তের ফল না জানা পর্যন্ত বোঝা যাচ্ছে না পরের নির্দেশ কোনটি — if-এর ভেতরের, না else-এর?

অপেক্ষা করলে পুরো লাইন থেমে যায়। তাই CPU অনুমান করে — একে বলে branch prediction. আগের আচরণ মনে রেখে সে ধরে নেয় “গতবারের মতো এবারও সম্ভবত সত্য হবে”, আর সেই পথের কাজ আগেভাগে শুরু করে দেয়। অনুমান ঠিক হলে কোনো সময় নষ্ট হয় না। ভুল হলে আগাম করা কাজ ফেলে দিয়ে নতুন করে শুরু করতে হয় — এতে বেশ কয়েকটি cycle নষ্ট হয়।

বেশিরভাগ if-এর আচরণ নিয়মিত (যেমন “ইনপুট ভুল কি না” প্রায় সবসময় মিথ্যা), তাই আধুনিক CPU-র অনুমান খুবই নির্ভুল। সমস্যা হয় যখন শর্ত এলোমেলোভাবে সত্য-মিথ্যা হয়। একটি পরিচিত উদাহরণ: একটি বড় তালিকা থেকে নির্দিষ্ট সীমার বেশি মানগুলো যোগ করার একই কোড তালিকা আগে সাজিয়ে নিলে লক্ষণীয়ভাবে দ্রুত চলতে পারে — কারণ সাজানো তালিকায় শর্তের ফল দীর্ঘক্ষণ একই থাকে, অনুমান সহজ হয়।

এর একটি অন্ধকার দিকও আছে। ২০১৮ সালে প্রকাশিত “Spectre” নিরাপত্তা-ত্রুটি দেখিয়েছিল যে CPU-কে ইচ্ছা করে ভুল অনুমান করিয়ে, আগাম করা কাজের সূক্ষ্ম ছাপ থেকে গোপন তথ্য বের করা সম্ভব। গতির জন্য নেওয়া একটি নকশা-সিদ্ধান্ত নিরাপত্তার সমস্যা হয়ে দাঁড়াল।

নতুনদের জন্য ব্যবহারিক পরামর্শ: এ নিয়ে এখনই দুশ্চিন্তার কারণ নেই। পরিষ্কার, সঠিক কোড লিখুন। খুব গতি-নির্ভর কাজে (গেম ইঞ্জিন, ভিডিও প্রসেসিং) প্রোগ্রামাররা মেপে দেখে প্রয়োজনে branch এড়িয়ে হিসাব দিয়ে কাজ সারেন।

মূল উপলব্ধি: কোডের একটি সাধারণ লাইনের নিচে হার্ডওয়্যারের জটিল জগৎ কাজ করে। একই সত্য গতি ও নিরাপত্তা দুই দিকেই প্রভাব ফেলে — Computer Architecture কোর্সে এর গভীরে যেতে পারবেন।

প্র ০৫সীমানায় পরীক্ষা করলেই কেন এত বাগ ধরা পড়ে? সব সম্ভাব্য ইনপুট পরীক্ষা করা যায় না কেন?

প্রথমে দ্বিতীয় প্রশ্নটি। একটি int প্রায় ৪৩০ কোটি আলাদা মান নিতে পারে। দুটি int ইনপুট হলে মিশ্রণের সংখ্যা এত বড় যে পৃথিবীর সব কম্পিউটার মিলেও সব পরীক্ষা শেষ করতে পারবে না। তাই পরীক্ষা মানেই বুদ্ধি করে কয়েকটি প্রতিনিধি মান বেছে নেওয়া।

বেছে নেওয়ার কৌশলটি দুই ধাপের। প্রথমে ইনপুটকে এমন ভাগে ভাগ করুন, যার ভেতরে প্রোগ্রাম একই রকম আচরণ করে — ডেলিভারি চার্জে “৫০০-এর কম”, “৫০০–৯৯৯”, “১,০০০ বা বেশি”, “০ বা কম”। একই ভাগের ৬০০ আর ৭০০ আলাদা কিছু পরীক্ষা করে না; প্রতিটি ভাগ থেকে একটি মানই যথেষ্ট। একে বলে equivalence partitioning.

দ্বিতীয় ধাপ: ভাগগুলোর সীমানা। কারণ মানুষের সবচেয়ে সাধারণ ভুলগুলো ঠিক সীমানায় থাকে — > বনাম >=, “৫০০ পর্যন্ত” মানে ৫০০ সহ না ছাড়া, লুপ এক ঘর বেশি বা কম চলা (যাকে বলে off-by-one ভুল)। নিয়মের লেখাতেও অস্পষ্টতা প্রায়ই সীমানায় — “৬০ বছরের বেশি” মানে কি ৬০ সহ? Boundary value analysis বলে: প্রতিটি সীমানায়, আর তার ঠিক দুই পাশে পরীক্ষা করুন।

Branch coverage এর সাথে আরেকটি মাপকাঠি যোগ করে — প্রতিটি পথ অন্তত একবার চলেছে কি না। তবে মনে রাখুন, ১০০% coverage মানে বাগমুক্ত নয়; এর মানে শুধু “কোনো পথ অপরীক্ষিত নেই।” বিমানের সবচেয়ে গুরুত্বপূর্ণ সফটওয়্যারে (যেমন DO-178C মানদণ্ডের সর্বোচ্চ স্তরে) এর চেয়েও কড়া MC/DC coverage চাওয়া হয়, যেখানে জটিল শর্তের প্রতিটি অংশ আলাদাভাবে ফলকে প্রভাবিত করে কি না, তা-ও দেখাতে হয়।

এর পেছনের গণিত — সেট, ভাগ, যুক্তি — Discrete Mathematics-এ পাবেন, আর পরীক্ষার পেশাদার পদ্ধতি Software Testing কোর্সে।

মূল উপলব্ধি: ভালো পরীক্ষক বেশি পরীক্ষা করেন না, ঠিক জায়গায় পরীক্ষা করেন। ভাগ চিনুন, সীমানা খুঁজুন, প্রতিটি পথ চালান — AI-এর কোড যাচাইয়ের সবচেয়ে কার্যকর অস্ত্র এটিই।

অনুশীলন

এগুলোর বেশিরভাগে কোড লিখতে হবে না — হাতে-কলমে দেখা আর ভাবাই আসল কাজ।

  1. সত্য না মিথ্যা? কোড না চালিয়ে বলুন, নিচের কোন শর্তে if-এর ভেতরের কাজ চলবে: (ক) if (-1) (খ) if (0.0) (গ) if ('0') (ঘ) if (5 > 3 > 1)

    (ক) চলবে — -1 শূন্য নয়, তাই সত্য। ঋণাত্মক মানও সত্য।

    (খ) চলবে না — 0.0 শূন্য।

    (গ) চলবে — '0' হলো অক্ষর শূন্য, যার ASCII মান 48. সংখ্যা 0 আর অক্ষর '0' এক জিনিস নয়।

    (ঘ) চলবে না — এটিই ফাঁদ। C বাঁ থেকে হিসাব করে: 5 > 3 হলো 1, তারপর 1 > 1 হলো 0. গণিতের মতো “৩ সংখ্যাটি ১-এর চেয়ে বড় আর ৫-এর চেয়ে ছোট” বোঝাতে লিখতে হয় 5 > 3 && 3 > 1. GCC -Wall-এ এ নিয়ে সতর্কও করে।

  2. সীমানা খুঁজুন: একটি কাল্পনিক বিদ্যুৎ বিলের নিয়ম: মাসে ০–৭৫ ইউনিট হলে এক রেট, ৭৬–২০০ হলে আরেক রেট, ২০০-এর বেশি হলে তৃতীয় রেট। পরীক্ষার জন্য কোন কোন ইউনিট মান বেছে নেবেন? নিয়মে কোনো অস্পষ্টতা আছে কি?

    প্রতিটি সীমানার দুই পাশ: 0, 75, 76, 200, 201. সাথে অস্বাভাবিক মান: -1 (মিটার রিডিং ভুল) আর একটি খুব বড় মান। প্রতিটি ভাগের মাঝখান থেকে একটি করে (যেমন 40, 150, 500) রাখলে ভালো, কিন্তু আসল কাজ সীমানাতেই।

    অস্পষ্টতা: ৭৫.৫ ইউনিটের মতো ভগ্নাংশ এলে কোন ভাগে যাবে? আর বিলের হিসাব কি পুরো ইউনিটে একটি রেট, না প্রতিটি ধাপের ইউনিট আলাদা রেটে (slab)? বাস্তবের বিদ্যুৎ বিলে সাধারণত দ্বিতীয়টি হয় — এই প্রশ্ন কোড লেখার আগেই করতে হবে।

  3. Dangling else শিকার: নিচের কোডে x = 5, y = -2 হলে কী ছাপা হবে? আর x = -5 হলে? if (x > 0) if (y > 0) printf("both\n"); else printf("x not positive\n");

    else যায় কাছের if (y > 0)-এর সাথে। তাই x = 5, y = -2 হলে ছাপা হবে x not positive — অথচ x ধনাত্মক! আর x = -5 হলে বাইরের শর্ত মিথ্যা, বাইরের if-এর কোনো else নেই, তাই কিছুই ছাপা হবে না। মেসেজটি যে ক্ষেত্রের জন্য লেখা, ঠিক সেখানেই আসে না। সমাধান: বাইরের if-এর কাজ বাঁকা বন্ধনীতে মুড়িয়ে else-কে বন্ধনীর বাইরে রাখা।

  4. AI অডিট: যেকোনো AI-কে বলুন “C-তে একটি ফাংশন লেখো যা নম্বর থেকে SSC-র গ্রেড (A+, A, A-, B, C, D, F) ফেরত দেয়।” তারপর ১০০, ৮০, ৭৯, ৩৩, ৩২, ০, -৫ আর ১০৫ দিয়ে পরীক্ষা করুন। কী পেলেন?

    সাধারণত যা দেখা যায়:

    • মূল সীমানাগুলো (৮০, ৭০ …) প্রায়ই ঠিক থাকে, কিন্তু কখনো > 80 লেখা থাকে — তখন ৮০ পাওয়া ছাত্র A পায়।
    • -৫ বা ১০৫-এর মতো অসম্ভব নম্বরের জন্য কোনো guard clause থাকে না — ১০৫ দিলে A+ আর -৫ দিলে F আসে, কোনো সতর্কতা ছাড়াই।
    • AI-এর দেওয়া পরীক্ষাগুলোতে সাধারণত ৮৫, ৭৫-এর মতো “আরামদায়ক” মান থাকে, সীমানার মান নয়।

    এরপর একই AI-কে উপরের prompt-এর মতো আগে decision table আর boundary test চাইলে পার্থক্যটা নিজেই দেখবেন।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি নিয়মকে নির্ভুল কোডে আনার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 📋 নিয়মটি পড়ে সব শর্ত আর ফল আলাদা করা
  2. 🗂️ Decision table বানিয়ে ফাঁকা বা অস্পষ্ট ঘর খোঁজা
  3. 📏 প্রতিটি সীমানার দুই পাশের পরীক্ষার মান ঠিক করা
  4. ⌨️ Guard clause আর if/switch দিয়ে কোড লেখা
  5. ✅ সব পরীক্ষা চালিয়ে দেখা — প্রতিটি branch অন্তত একবার চলেছে কি না

C-তে if (-3)-এর ভেতরের কাজ কি চলবে?

  • না, ঋণাত্মক সংখ্যা মিথ্যা
  • হ্যাঁ, কারণ শূন্য ছাড়া সব মান সত্য
  • না, compile error হবে
  • কখনো চলবে, কখনো চলবে না

গ্রেডের মইয়ে প্রথম লাইন if (marks >= 33) printf("D");, তারপর else if (marks >= 80) printf("A+");. ৯৫ নম্বরে কী ছাপা হবে?

  • A+
  • D আর A+ দুটোই
  • D
  • কিছুই না

নিয়ম: “১,০০০ টাকা বা বেশি অর্ডারে ডেলিভারি ফ্রি।” কোডে লেখা আছে if (amount > 1000). কোন পরীক্ষা এই ভুল ধরবে?

  • amount = 1000
  • amount = 1500
  • amount = 200
  • amount = 5000

choice = 1, আর switch-এ case 1:, case 2:, default: — কোনোটিতেই break নেই। প্রতিটি একটি করে লাইন ছাপে। কয়টি লাইন ছাপা হবে?

  • ১টি — শুধু case 1-এর
  • ২টি — case 1 আর case 2
  • কিছুই না
  • ৩টি — case 1, case 2 আর default

নিচের কোন কাজে switch ব্যবহার করা যায় না?

  • মেনুতে ১, ২, ৩ চাপা অনুযায়ী কাজ
  • নম্বর ৮০ বা বেশি হলে A+, ৭০ বা বেশি হলে A
  • ক্যালকুলেটরে '+', '-', '*' অক্ষর অনুযায়ী হিসাব
  • 'y' বা 'Y' চাপলে নিশ্চিত করা

বন্ধনী ছাড়া if (a) if (b) x(); else y(); — else-টি কার?

  • বাইরের if (a)-এর
  • কারো নয় — compile error
  • ভেতরের if (b)-এর
  • Compiler ভেদে আলাদা

AI একটি টিকিটের দামের ফাংশন দিল, সাথে তিনটি পরীক্ষা — সবগুলো পাস। পরের কাজ কোনটি সবচেয়ে ভালো?

  • Decision table বানিয়ে প্রতিটি সারি ও সীমানার জন্য পরীক্ষা যোগ করা
  • পরীক্ষা পাস করেছে, তাই কোড ঠিক
  • AI-কে জিজ্ঞেস করা, “ঠিক আছে তো?”
  • সব if-কে ternary-তে বদলে ছোট করা

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. C-তে 0 মিথ্যা, বাকি সব সত্য; তুলনার ফল সবসময় 0 বা 1. পড়ার সুবিধায় <stdbool.h>-এর bool.
  2. else-if মইয়ে প্রথম সত্য শর্তটিই জেতে — তাই ক্রম ও >=/> সাবধানে বাছুন।
  3. সবসময় বাঁকা বন্ধনী — নইলে else যায় সবচেয়ে কাছের if-এর সাথে (dangling else).
  4. switch শুধু পূর্ণসংখ্যা/অক্ষরের ধ্রুবকে; প্রতিটি case-এ break, শেষে default.
  5. কোডের আগে decision table; পরীক্ষায় প্রতিটি branch আর প্রতিটি সীমানার দুই পাশ — AI-এর কোডেও।

পরের পর্বে: সিদ্ধান্ত দিয়ে প্রোগ্রাম পথ বেছে নিতে শিখল। এবার শিখবে একই পথে বারবার ফিরে আসতে — লুপ। আর লুপ কখন থামবে, সেটিও ঠিক হয় একটি শর্ত দিয়েই।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান