মূল বিষয়বস্তুতে যান
Clean Code Mastery

Remove Control Flag: পেয়ে গেলেই থেমে যাও

Remove Control Flag refactoring শেখো একজন দারোয়ানের গল্পের মাধ্যমে। TypeScript আর C# এর উদাহরণ দিয়ে বুঝবে break আর return কীভাবে control flag-এর জায়গা নেয়।

21 মিনিট আপডেট: June 11, 2026beginner
refactoringsimplifying conditionalsremove control flagbreakearly returnloopsclean code

দারোয়ানের পতাকার গল্প

ধরো ঢাকার মিরপুর আবাসিক এলাকায় রাতের ডিউটি চলছে। রাত সাড়ে দশটা। দারোয়ান রহিম চাচা কেবিনে চা খাচ্ছেন, হঠাৎ ইন্টারকম বেজে উঠল। গেট থেকে নাসরিন বেগম বলছেন: "রহিম চাচা, জামাল সাহেবের কাছে একজন মেহমান এসেছে — পুরোনো বন্ধু, ময়মনসিংহ থেকে এসেছে। কিন্তু জামাল সাহেব গত মাসে ফ্ল্যাট বদলে গেছেন, কোন বাসায় আছেন জানি না। এক থেকে চল্লিশ নম্বর বাসা একটু খুঁজে দিন।"

এখন ভাবো রহিম চাচা কাজটা অদ্ভুতভাবে করছেন। কেবিন থেকে একটা ছোট লাল পতাকা তুলে নিলেন, বগলের নিচে চাপা দিয়ে হাঁটা শুরু করলেন। ১ নম্বর বাসা — নক করলেন, জামাল নেই। ২ নম্বর — নেই। ৫ নম্বর — করিম সাহেবরা, জামাল একেবারেই নেই। ৭ নম্বর — জামাল পাওয়া গেছে! জামাল সাহেব নিজেই দরজা খুললেন। রহিম চাচা বাসা নম্বর মনে রাখলেন, মাথার উপর লাল পতাকা উঁচু করলেন... আর হাঁটতে থাকলেন। ৮ নম্বর — পতাকা উপরে আছে, নক করলেন না। ৯ নম্বর — পতাকা উপরে, চলে গেলেন। ১০, ১১, ১২... চল্লিশ নম্বর পর্যন্ত, প্রতিটি গেটে নিজের পতাকা দেখলেন আর কিছু করলেন না।

চল্লিশটা বাসা ঘোরা হলো। তেত্রিশটা সম্পূর্ণ বৃথা। ময়মনসিংহের মেহমান গেটে আরও বিশ মিনিট অপেক্ষা করলেন, অন্ধকারে দেখলেন দারোয়ানের পতাকা রাস্তায় দুলতে দুলতে যাচ্ছে। আর জামাল সাহেব পাজামা পরে দরজায় দাঁড়িয়ে ভাবলেন দারোয়ান নক করে কেন চলে গেল।

যেকোনো বুদ্ধিমান দারোয়ান স্বাভাবিক কাজটাই করতেন: ৭ নম্বরে জামাল পেয়ে সেখানেই থামতেন, সরাসরি গেটে ফিরে উত্তর দিতেন। জিনিস পেলেই খোঁজ শেষ। কোনো পতাকা নেই, কোনো বৃথা চক্কর নেই।

চিত্র ১: পতাকা-মার্চ বনাম সরাসরি খোঁজ — মেহমানের অভিজ্ঞতা

রহিম চাচাকে নিয়ে হাসো যতখুশি — কিন্তু beginner কোড (আর অনেক পুরোনো professional কোড) এই পতাকা-মার্চই করে সব সময়:

found = false set করো। সব কিছুর উপর loop চালাও। Loop-এর ভেতরে প্রথমে if (!found) check করো। Target পাওয়া গেলে found = true করো। তারপরও একেবারে শেষ পর্যন্ত loop চালিয়ে যাও।

সেই boolean হলো রহিম চাচার লাল পতাকা। এটা মেহমান বা বাসা সম্পর্কে কোনো তথ্য নয় — হাতে বানানো একটা সংকেত যার একমাত্র কাজ হলো loop-এর বাকি অংশকে বলা "কিছু করো না, আমরা শেষ", আর loop তখনও চলতে থাকে। এই refactoring যা পতাকা retire করে আর খোঁজকে সরাসরি থামতে দেয় তার নাম Remove Control Flag

Remove Control Flag কী?

Remove Control Flag হলো এমন একটা refactoring যেখানে তুমি একটা boolean variable মুছে দাও যেটা শুধু control flow নিয়ন্ত্রণ করার জন্য আছে, আর সেটাকে একটা direct jump statement দিয়ে replace করো — loop থামাতে break, পরের iteration-এ যেতে continue, অথবা উত্তর নিয়ে method ছাড়তে return

মূল কথা হলো control। একটা control flag domain data নয়। isPassed, hasPaidFees, prefersVeg — এই boolean গুলো তোমার সমস্যা সম্পর্কে কিছু বলে; এগুলো রাখো। কিন্তু found, done, stop, keepGoing — এই boolean গুলো শুধু এই জন্য আছে যাতে পরের কোড check করতে পারে "আমি কি এখনও কাজ করব?" — এগুলো হাতে বানানো এক-bit state machine যা তোমার ভাষা নিজেই already করতে পারে।

চিত্র ২: আসল data আর হাতে বানানো ট্রাফিক সিগন্যাল আলাদা করা

এই style কোথা থেকে এল? ইতিহাস থেকে — আর ইতিহাসটা বেশ মজার।

কলেজ কর্নার: ১৯৬৮ সালে Edsger Dijkstra বিখ্যাত চিঠি "Go To Statement Considered Harmful" লিখলেন। তর্ক করলেন যে অনিয়ন্ত্রিত goto jump প্রোগ্রামকে বোঝার অযোগ্য করে তোলে। এর পরে structured programming আন্দোলন Böhm আর Jacopini-এর ১৯৬৬ সালের result-এর উপর ভরসা রাখল — প্রমাণ হলো যেকোনো computation মাত্র তিনটা structure দিয়ে লেখা যায়: sequence, selection, আর repetition। প্রতিটিতে এক entry আর এক exit। এটাই পরে single-entry, single-exit (SESE) নিয়ম হয়ে গেল: মাঝখান থেকে return নেই, loop থেকে break নেই। তাহলে আগে থামার একমাত্র উপায় ছিল ভান করা — একটা boolean set করো, loop condition পরে সেটা দেখুক। Control flag তাই বোকামি নয়; এগুলো একটা নিয়মের জীবাশ্ম যা ৩০০ লাইনের goto-ভরা routine-এর বিরুদ্ধে দারুণ কাজ করেছিল। এখন কী বদলেছে? আধুনিক method ছোট, আর break/return হলো নিয়ন্ত্রিত structured jump — Dijkstra যে wild goto আক্রমণ করেছিলেন তার মতো কিছু নয়। Martin Fowler-এর refactoring catalog স্পষ্টভাবে control flag-কে break, continue, আর return দিয়ে replace করার পরামর্শ দেয়।

Flag সরালে কী পাওয়া যায়?

  • একটা কম mutable variable track করতে হয়, initialize করতে হয়, মাথায় রাখতে হয়।
  • সিদ্ধান্ত আর কাজের মধ্যে কোনো ফাঁক নেই। Flag-এ "আমরা পেয়েছি" (flag set) আর "আমরা থেমেছি" (loop শেষ) অনেক iteration দূরে — এই ফাঁকেই bug লুকায়। break/return-এ সিদ্ধান্ত আর কাজ একই লাইনে।
  • প্রতি iteration-এ guard noise নেই। if (!found) wrapper সব cycle-কে ভারী করছিল, সেটা সরাসরি উড়ে যায়।
  • Control flow সৎ থাকে। Loop ঠিক যা করে তাই বলে: খুঁজে পাওয়া পর্যন্ত search করো, তারপর থামো।
💡

মনে রাখার মতো একটাই কথা: flag মনে রাখে যে থামা উচিত; break বা return আসলে থামে। মনে রাখার চেয়ে করাই ভালো। যে boolean-এর একমাত্র কাজ পরের কোডকে কিছু না করতে বলা, সেটাকে যে jump সে নকল করছে সেটা দিয়ে replace করো।

কখন এটা দরকার?

এই pattern গুলো দেখলে সতর্ক হও:

  1. found loop। একটা boolean false দিয়ে শুরু, match-এ true, আর প্রতিটি পরের iteration-এ check। এটাই textbook case — break বা return দিয়ে replace করো।
  2. done while-loop। Loop-এর condition !done, আর done ভেতর থেকে flip হচ্ছে — loop condition একটা পুতুল যা ভেতর থেকে নিয়ন্ত্রিত। প্রায়ই সিদ্ধান্তের জায়গায় সরাসরি break দিলেই হয়।
  3. Flag আর result variable একসাথে চলছে। found আর result পাশাপাশি declare করা — দুটো variable একটা কাজ করছে। Match point-এ result সরাসরি return করলে প্রায়ই দুটোই মুছে যায়।
  4. Nested if (!errorOccurred) সিঁড়ি। ব্যর্থতায় flag set, তারপর প্রতিটি পরের কাজের আগে check — method একটা সিঁড়ির মতো indent হচ্ছে। Early return এটা সমতল করে — Replace Nested Conditional with Guard Clauses-এর মতো একই চিন্তা।
  5. Flag bookkeeping-এ method আরও বড় হচ্ছে। Flag declare, flag check, flag update — এগুলো pure overhead লাইন। সরালে Long Method smell কমে, আর loop যথেষ্ট ছোট হয় যে নিজের method-এ extract করা যায়।

কখন সাবধান থাকবে:

  • Match-এর পরে, একই iteration-এ কাজ বাকি আছে। Naive break সেটা skip করবে। Jump-এর আগে সেই কাজ সরাও, অথবা আগে restructure করো।
  • Loop body অনেক বড়। ৬০ লাইনের loop-এর ৪০ নম্বর লাইনে break লুকিয়ে থাকলে সহজে miss হয়। আগে Extract Method করো — দশ লাইনের method-এ early return miss করার সুযোগ নেই।
  • Team single-exit style enforce করে। কিছু safety-critical standard (আর কিছু স্যার) এখনও প্রতি function-এ একটাই exit চান। Team-এর নিয়ম মেনে চলো — আর পাঁচ লাইনের search loop-এ নিয়মটা আসলে কতটা সাহায্য করছে সেটা নিয়ে আলোচনা করো।

যে flag-এর সাথে দেখা হয় তার মাপ নিতে দুটো প্রশ্ন: এটা কি শুধুই একটা ট্রাফিক সিগন্যাল নাকি গোপনে domain data বহন করছে? আর loop-এর কতটুকু flag bookkeeping-এ ভরা?

চিত্র ৩: সরানোর আগে flag-কে যাচাই করা

আগে আর পরে এক নজরে

রহিম চাচার খোঁজ, ঠিক যেভাবে তিনি করেছিলেন সেভাবে লেখা:

interface House {
  number: number;
  resident: string;
}
 
// BEFORE: the flag-march — checks every house even after finding Joshi
function findHouse(houses: House[], name: string): number | null {
  let found = false;          // the folded red flag
  let houseNumber: number | null = null;
 
  for (const house of houses) {
    if (!found) {             // look at the flag before every gate
      if (house.resident === name) {
        houseNumber = house.number;
        found = true;         // raise the flag... and keep walking
      }
    }
  }
  return houseNumber;
}

আর বুদ্ধিমান দারোয়ান:

// AFTER: stop at house 7 and walk back with the answer
function findHouse(houses: House[], name: string): number | null {
  for (const house of houses) {
    if (house.resident === name) {
      return house.number;    // found = stop = answer, all in one line
    }
  }
  return null;                // checked every house; not here
}

দেখো কী উধাও হয়ে গেছে: flag variable, result variable, প্রতি iteration-এর flag check, আর nesting-এর একটা পুরো স্তর। যা বাকি থাকে সেটা কাজটার মতোই পড়ে: প্রতিটি বাসার জন্য, বাসিন্দা মিললে নম্বর return করো; না হলে null। বোনাস হিসেবে, refactored version আসলেই কম কাজ করে — ৪০টির বদলে ৭ নম্বরে থামে। তবে মনে রেখো, মূল পুরস্কার হলো স্পষ্টতা, গতি হলো おまけ।

চিত্র ৪: Flag-march বনাম direct exit

আগের version-এর প্রতিটি লাইনকে "আসল search কাজ" বনাম "flag bookkeeping"-এ ভাগ করো। Flag-এর জন্য রায়টা লজ্জাজনক:

চিত্র ৫: flag version-এর লাইনগুলো আসলে কী করে

কলেজ কর্নার: একটা control flag আসলে দুটো state-এর finite state machine — "এখনও খুঁজছি" আর "শেষ" — তোমার loop-এর ভেতরে হাতে implement করা, প্রতিটি পদক্ষেপে if (!found) transition guard দিয়ে। কিন্তু ভাষা তোমার জন্য already একটা ভালো state machine চালায়: loop নিজেই। for loop-এর machine-এ iterating আর exited state আছে, আর break/return হলো native transition। যখন তুমি flag লেখো, তুমি তোমার এক-bit machine-টা loop-এর machine-এর উপরে চালাচ্ছো আর দুটোকে manually sync রাখছো — manual synchronization-এই bug লুকায়। Flag সরালে দুটো machine আবার একটায় মিলে যায়।

চিত্র ৬: clean state machine হিসেবে search — loop-ই machine, flag দরকার নেই

ধাপে ধাপে, নিরাপদে

একটু জটিল উদাহরণে পুরো discipline দিয়ে refactor করা যাক। এটায় flag আর trailing কাজ দুটোই আছে, তাই ধাপগুলো জরুরি:

// STARTING POINT: flag, result variable, and a visit counter
function findContact(records: ContactRecord[], target: string): string | null {
  let found = false;
  let phone: string | null = null;
  let scanned = 0;
 
  for (const r of records) {
    if (!found) {
      if (r.name === target && r.isActive) {
        phone = r.phone;
        found = true;
      }
      scanned = scanned + 1;   // counts only records actually checked
    }
  }
  console.log(`Scanned ${scanned} records`);
  return phone;
}

ধাপ ১ — Flag audit করো। found-এর প্রতিটি read আর write খোঁজো। Write: false দিয়ে initialize, match-এ true set। Read: if (!found) guard। নিশ্চিত করো এর একমাত্র কাজ flow control — store, return, বা data হিসেবে ব্যবহার হচ্ছে না। হচ্ছে না। ভালো: এটা pure control flag।

ধাপ ২ — Flag-এর guard আর কী protect করছে দেখো। if (!found)-এর ভেতরে scanned += 1ও আছে। মানে "checking বন্ধ" মানে "counting বন্ধ"ও। Replacement-কে এটা preserve করতে হবে। এটাই হলো jump করার আগে দেখো মুহূর্ত — এটা skip করলেই naive break bug তৈরি করে।

ধাপ ৩ — Flag-set-কে jump দিয়ে replace করো। Loop-এর পরে কাজ আছে (log লাইন), তাই match-এ return দিলে সেই log skip হয়ে যাবে। break বেছে নাও:

// Step 3: INTERMEDIATE — break replaces the flag-set; flag still declared
function findContact(records: ContactRecord[], target: string): string | null {
  let found = false;           // now never read — dead weight
  let phone: string | null = null;
  let scanned = 0;
 
  for (const r of records) {
    if (r.name === target && r.isActive) {
      phone = r.phone;
      break;                   // stop the search right here
    }
    scanned = scanned + 1;
  }
  console.log(`Scanned ${scanned} records`);
  return phone;
}

একটু ভাবো: scanned dissolved guard থেকে বেরিয়ে এসেছে, আর এর অর্থ check করতে হবে। Original-এ, matching iteration সেও scanned increment করত (দুটো statement if (!found)-এর ভেতরে ছিল); নতুন version-এ, break increment-এর আগে fire করে, তাই match count হয় না। এটা একটা one-count behaviour পার্থক্য। তোমার program-এর জন্য কোন অর্থটা সঠিক ঠিক করো, সচেতনভাবে encode করো, আর একটা test দিয়ে pin down করো। এটাই সেই সূক্ষ্ম পার্থক্য ধরার জন্য per-step test run আছে।

ধাপ ৪ — Dead flag মুছে দাও। found এখন লেখা হয় কিন্তু কখনও পড়া হয় না। Declaration আর assignment সরাও। Tests চালাও।

ধাপ ৫ — Result-variable plumbing collapse করো যদি পারো। এখানে phone loop-এর পরেও টিকতে হবে (log পরে আসে), তাই এটা থাকবে। কিন্তু log না থাকলে সেরা শেষ অবস্থা হতো সরাসরি return r.phone — result variable-ও উড়ে যেত।

ধাপ ৬ — Extract Method বিবেচনা করো। যদি surrounding method searching ছাড়াও বেশি কিছু করে, loop-কে findActivePhone(records, target)-এ extract করো clean early return সহ, আর caller logging করুক। Early return সবচেয়ে ভালো কাজ করে ছোট, single-purpose method-এ।

⚠️

প্রতিটি ধাপের পরে tests চালাও — বিশেষত flag-set-কে jump দিয়ে replace করার পরে। Flag code আর jump code সূক্ষ্মভাবে আলাদা হতে পারে: matching iteration বাকি statement শেষ করছে কিনা, counter match include করছে কিনা, "not found" path সঠিক default return করছে কিনা। প্রতিটা এক-লাইন behaviour পার্থক্য যা silently compile হয়। প্রতিটি ধাপের মাঝে green test suite রাখলে এই ফাঁদগুলো non-event হয়ে যায়।

চিত্র ৭: flag থেকে jump-এর নিরাপদ পথ

একটা বড় real-life উদাহরণ

আবাসিক এলাকার app-এ একটা register আছে, আর original developer রহিম চাচার পতাকা-মার্চ পুরোপুরি লিখেছিলেন — flag, guard, result variables, আর উপরে একটা done while-loop:

interface Flat {
  number: number;
  resident: string;
  hasGuestParking: boolean;
}
 
// BEFORE: two control flags steering one simple errand
function receiveVisitor(flats: Flat[], visitorFor: string): string {
  let i = 0;
  let done = false;
  let found = false;
  let flatNumber = 0;
  let parking = false;
 
  while (!done) {
    if (i >= flats.length) {
      done = true;
    } else {
      if (!found) {
        if (flats[i].resident === visitorFor) {
          found = true;
          flatNumber = flats[i].number;
          parking = flats[i].hasGuestParking;
        }
      }
      i = i + 1;
    }
  }
 
  if (found) {
    return parking
      ? `Go to flat ${flatNumber}. Guest parking available.`
      : `Go to flat ${flatNumber}. Please park outside.`;
  }
  return "Resident not found. Please check at the office.";
}

একটা খোঁজের জন্য পাঁচটা mutable variable। এর মধ্যে দুটো (done, found) pure control flag। এই কোড বুঝতে মাথায় simulate করতে হয়, প্রতিটি মুহূর্তে কোন flag উপরে আছে track করতে — ঠিক সেই mental flag-march যা retire করতে চাইছি।

Flag গুলো সরাও, একবারে একটা নিরাপদ ধাপে, আর পুরো structure চুপসে যায়:

// AFTER: the search stops when it succeeds — flags retired
function receiveVisitor(flats: Flat[], visitorFor: string): string {
  for (const flat of flats) {
    if (flat.resident === visitorFor) {
      return flat.hasGuestParking
        ? `Go to flat ${flat.number}. Guest parking available.`
        : `Go to flat ${flat.number}. Please park outside.`;
    }
  }
  return "Resident not found. Please check at the office.";
}

পঁচিশ লাইন হলো নয়টা। done for loop-এর নিজস্ব bounds-এ মিলিয়ে গেল। found early return-এ মিলিয়ে গেল। Result variable গুলো মিলিয়ে গেল কারণ উত্তর ঠিক সেই জায়গায় তৈরি হয় যেখানে আবিষ্কার হয়। আর "not found" message loop-এর পরে স্বাভাবিকভাবে বসে — অবস্থানটাই বলে "আমরা সবাইকে check করেছি"।

চিত্র ৮: refactoring-এর পরে পুরো কাজ — আবিষ্কারের মুহূর্তেই উত্তর

দারোয়ানের সাথে গভীর মিল দেখো: after-version-এ কোডের আকার কাজের আকারের সাথে মেলে। বাসাগুলো হাঁটো; যে মুহূর্তে মানুষটাকে খুঁজে পাও, উত্তর দাও; বাসা শেষ হলে report করো। কোনো props দরকার নেই। পূর্ণ design-এ search নিজের home পায়, আর flag গুলো আর ফিরে আসে না:

চিত্র ৯: Register-ই search-এর মালিক; caller শুধু জিজ্ঞেস করে

আর বাঁচানো কাজটা বাস্তব। ময়মনসিংহের মেহমানের রাতে, পতাকা-মার্চ সাত নম্বরে থাকা উত্তরের জন্য চল্লিশটা বাসা ঘুরেছিল:

চিত্র ১০: একটা প্রশ্নের উত্তর দেওয়ার আগে কতটা বাসা দেখা হলো

C# আর Python-এ একই refactoring

Classic search-with-flag-এর compact C# version:

// BEFORE: flag plus guard plus result variable
string FindRepresentative(List<Customer> customers, string city)
{
    bool found = false;
    string code = null;
 
    foreach (var c in customers)
    {
        if (!found)
        {
            if (c.City == city && c.IsActive)
            {
                code = c.Code;
                found = true;
            }
        }
    }
    return code;
}
 
// AFTER: return at the point of discovery
string FindRepresentative(List<Customer> customers, string city)
{
    foreach (var c in customers)
    {
        if (c.City == city && c.IsActive)
            return c.Code;
    }
    return null;
}

আর idiomatic C# প্রায়ই আরও একধাপ এগিয়ে যায় — পুরো loop একটা standard library প্রশ্ন:

// BONUS: LINQ states the intent as a single expression
string FindRepresentative(List<Customer> customers, string city) =>
    customers.FirstOrDefault(c => c.City == city && c.IsActive)?.Code;

FirstOrDefault হলো "খুঁজে পেলে থামো" একটা named operation হিসেবে package করা — library author ইতিমধ্যেই তোমার জন্য control flag সরিয়ে দিয়েছেন। Python-এ generator expression-এর উপর next-এ একই উপহার:

# BEFORE: the flag-march in Python
def find_representative(customers, city):
    found = False
    code = None
    for c in customers:
        if not found:
            if c.city == city and c.is_active:
                code = c.code
                found = True
    return code
 
# AFTER: return at the match — or let the language search for you
def find_representative(customers, city):
    for c in customers:
        if c.city == city and c.is_active:
            return c.code
    return None
 
# BONUS: next() with a default is FirstOrDefault in Python clothing
def find_representative(customers, city):
    return next((c.code for c in customers if c.city == city and c.is_active), None)

কলেজ কর্নার: এই library form গুলো — FirstOrDefault, Array.prototype.find, Python-এর next — হলো iterator abstraction: early exit এখনও হয়, কিন্তু একটা precise contract সহ named operation-এর ভেতরে encapsulate করা। এটাই single-exit debate-এর সবচেয়ে পরিষ্কার উত্তর: যে function find call করে তার একটা expression আছে আর কোনো visible jump নেই, jump থাকে পুরোপুরি tested library-এর ভেতরে। Loop theory দিয়ে দেখলে: একটা ভালো search loop-এর invariant হলো "target এখন পর্যন্ত দেখা অংশে নেই", আর exit condition হলো "match পেয়েছি অথবা list শেষ"। Flag version exit condition-কে invariant-এর মধ্যে লুকিয়ে দেয় — "...অথবা flag উপরে আছে, তাহলে কিছু করো না" — এ কারণেই এটা নিয়ে reasoning কাদার মতো মনে হয়।

IDE support

Control flag সরানো judgement-এর কাজ, কিন্তু tool গুলো flag চিনতে সাহায্য করে:

Toolকী সাহায্য করে
IntelliJ IDEA / Rider / WebStorm"boolean variable is always inverted/assigned" আর "loop can be replaced with findFirst/find" inspection Alt+Enter-এ direct form suggest করে
Visual Studio + ReSharper"Convert loop to LINQ expression" search loop-কে FirstOrDefault/Any call-এ বদলায়, পার্শ্ব প্রতিক্রিয়া হিসেবে flag মুছে
SonarQube / SonarLintUnused বা write-only variable report করে — refactor-এর মাঝপথে flag এমনই হয় — আর অতিরিক্ত জটিল loop condition
VS Code (TS/JS)tsconfig-এ noUnusedLocals break/return-এ switch করার পরে dead flag ধরে

একটা সুবিধাজনক workflow: নিজে jump edit করো (ধাপ ৩), তারপর IDE-এর "unused variable" warning-কে প্রমাণ করতে দাও যে flag dead — তারপর মুছো। Warning না এলে মানে flag কোথাও এখনও পড়া হচ্ছে — audit-এ ফিরে যাও। আর যদি IDE "convert to LINQ" বা "replace with find" offer করে, সেই single action প্রায়ই পুরো refactoring করে দেয়, tested আর typo-free।

সুবিধা আর ঝুঁকি

সুবিধাকেন গুরুত্বপূর্ণ
একটা কম mutable variableকম state track মানে কম ভুলের সুযোগ
সিদ্ধান্ত আর কাজ একসাথে"flag set" আর "loop আসলে শেষ"-এর মধ্যে কোনো ফাঁক নেই যেখানে bug লুকাবে
Guard clutter উধাওপ্রতিটি iteration-এর if (!found) wrapper সরে যায়
সৎ termination conditionLoop-এর stopping rule stop site-এ দৃশ্যমান
প্রায়ই দ্রুতSearch শেষ পর্যন্ত march না করে match-এ থামে
ঝুঁকিকীভাবে handle করবে
Trailing per-iteration কাজ skip হওয়াJump করার আগে flag-এর guard আর কী protect করত audit করো
Long body-তে jump লুকানোআগে Extract Method করো যাতে early exit miss করা অসম্ভব হয়
Flag গোপনে result বহন করছেData job আর control job আলাদা করো; data সরাসরি return করো
Team single-exit conventionসৎভাবে আলোচনা করো; সম্মত style মেনে চলো

Single-exit debate, সৎভাবে। এমন অভিজ্ঞ মানুষ পাবে যারা break আর early return পছন্দ করেন না। তাদের নিয়ম — one entry, one exit — দুর্দান্ত পরামর্শ ছিল যখন function শত শত লাইনে চলত আর exit যেকোনো জায়গায় লুকিয়ে থাকত। তখন single exit সত্যিই audit করা সহজ ছিল; কিছু safety-critical standard (পুরোনো MISRA C) ঠিক এ কারণে এটা require করত। আধুনিক counter-argument, Fowler আর বেশিরভাগ current style guide-এর, হলো আমরা এখন ছোট method লিখি, আর দশ লাইনের method-এ early exit হলো সবচেয়ে স্পষ্ট বাক্য — যেখানে flag version ওই method-এ আর নেই এমন সমস্যা এড়াতে variable আর indentation যোগ করে। দুই পক্ষই একই জিনিস চায়: readability আর auditability। ব্যবহারিক পরামর্শ: ছোট method-এ direct jump prefer করো; বড় method-এ আগে method ছোট করো; team standard থাকলে সেটা মেনে চলো আর pull request-এ লড়াই না করে আলোচনা খোলা রাখো।

কোন smell গুলো সারে?

Code smellRemove Control Flag কীভাবে সাহায্য করে
Long MethodFlag declaration, guard, আর update মুছে দেয়; প্রায়ই loop এতটা ছোট হয় যে extract করা যায়
Duplicate CodeBlock-এর চারপাশে বারবার if (!flag) guard wrapper একবারে অদৃশ্য হয়
Comments"// once found, skip the rest"-এর মতো note অপ্রয়োজনীয় হয় — break নিজেই বলে দেয়

দ্রুত revision box

+----------------------------------------------------------------+
|              REMOVE CONTROL FLAG — CHEAT SHEET                 |
+----------------------------------------------------------------+
| Signal  : boolean like found/done used ONLY to steer flow      |
| Ask     : is the flag data about the domain? keep it.          |
|           is it only a traffic signal? remove it.              |
| Replace : need the answer out now      -> return value         |
|           stop loop, more work after   -> break                |
|           skip rest of THIS iteration  -> continue             |
| Audit   : what else did the flag guard protect? preserve it!   |
| Test    : after the jump edit AND after deleting the flag      |
| Debate  : single-exit style is history for short methods,      |
|           but respect your team's agreed conventions           |
| Cures   : Long Method, guard-wrapper duplication               |
+----------------------------------------------------------------+

অনুশীলন

এই উপস্থিতি checker একটা না দুটো flag দিয়ে attendance list হাঁটছে। দুটোই retire করো:

// Two flags to remove — and one subtlety to preserve!
function firstAbsentee(rollList: StudentEntry[]): string {
  let searching = true;
  let found = false;
  let absentee = "";
  let position = 0;
 
  while (searching) {
    if (position >= rollList.length) {
      searching = false;
    } else {
      if (!found) {
        if (!rollList[position].present) {
          absentee = rollList[position].name;
          found = true;
        }
      }
      position = position + 1;
    }
  }
 
  if (found) {
    return `First absentee: ${absentee}`;
  }
  return "Full attendance today!";
}

তোমার checklist:

  1. দুটো flag audit করো। কোনটা loop-ender, কোনটা match-rememberer? নিশ্চিত করো কোনোটাই domain data বহন করছে না।
  2. while-with-puppet-condition structure-কে plain for...of বা indexed for loop দিয়ে replace করো — এটা searching retire করবে।
  3. Match point-এ তোমার jump বেছে নাও: সেখানেই পুরো message return করতে পারো? তাহলে absentee আর found-এর কী হবে?
  4. "Full attendance today!" return-কে loop-এর পরে রাখো আর নিজেকে বোঝাও কেন এর অবস্থানটাই এটাকে সঠিক করে।
  5. কলেজ bonus: found হাতে যে two-state machine implement করছে সেটা আঁকো, তারপর তোমার refactoring-এর পরে loop-এর নিজস্ব state machine আঁকো। নিজেকে convince করো এগুলো একই machine — একটা তুমি লিখেছ, একটা ভাষা দিয়েছে।
  6. প্রায় ছয় লাইনের final version target করো, তারপর tests চালাও — একটা list যেখানে কেউ অনুপস্থিত নেই আর একটা যেখানে প্রথম দুজন ছাত্র অনুপস্থিত (তোমার version কি প্রথমজনকেই return করে?)।

যদি তোমার final code একজন বুদ্ধিমান দারোয়ানের মতো পড়ে — প্রতিটি entry check করো, প্রথম miss-এ উত্তর দাও, শেষে পৌঁছালে খুশি মনে report করো — তাহলে তুমি Remove Control Flag পুরোপুরি বুঝেছ। শাবাশ!

সচরাচর জিজ্ঞাসা

Control flag আসলে কী জিনিস?
Control flag হলো একটা boolean variable যার একমাত্র কাজ loop বা block-এর flow নিয়ন্ত্রণ করা — যেমন found = true বা done = true। এটা তোমার সমস্যার domain সম্পর্কে কিছুই বলে না, শুধু হাতে বানানো একটা ট্রাফিক সিগন্যাল। Remove Control Flag এটাকে ভাষার আসল flow tool দিয়ে replace করে: break, continue, অথবা return।
কখন break দেব, কখন return দেব?
return দাও যখন loop-এর পুরো উদ্দেশ্যই হলো একটা উত্তর খোঁজা — যেখানে পাও সেখান থেকেই return করো। break দাও যখন loop থামাতে হবে কিন্তু method-এ আরও কাজ বাকি আছে। continue দাও যখন শুধু current iteration-এর বাকি অংশটা skip করতে চাও।
break বা early return কি খারাপ? আমার স্যার বলেছেন one entry, one exit মানতে।
one-entry-one-exit নিয়মটা structured programming-এর যুগ থেকে এসেছে, যখন function-গুলো অনেক লম্বা ছিল আর jump মানে ছিল wild goto। আজকের ছোট method-এর জন্য, বেশিরভাগ expert — Martin Fowler-সহ — early exit পছন্দ করেন কারণ এটা bookkeeping variable সরিয়ে দেয় আর intent স্পষ্ট করে। তবে team-এর style guide যদি single exit চায়, team মেনে চলো আর trade-off নিয়ে সৎভাবে আলোচনা করো।
আমার flag যদি result-ও রাখে, যেমন matchedCustomer?
তাহলে সে দুটো কাজ করছে: flow নিয়ন্ত্রণ আর data বহন করা। কাজ দুটো আলাদা করো। প্রায়ই সবচেয়ে পরিষ্কার সমাধান হলো match point-এ সরাসরি result return করা — এতে flag আর result variable দুটোই উড়ে যায়। যদি break করতেই হয়, result variable রাখো কিন্তু boolean অংশটা মুছে দাও।
আমি কি সব সময় সব control flag সরাতে পারি?
প্রায় সব সময়, কিন্তু চোখ বন্ধ করে নয়। যদি match-এর পরে একই iteration-এ কাজ বাকি থাকে, naive break সেটা skip করবে। Loop অনেক বড় হলে আগে নিজের method-এ extract করো যাতে early return পরিষ্কার থাকে। আর async বা resource-cleanup code-এ try/finally-এর মতো আলাদা structure দরকার হতে পারে।

আরো দেখো

সম্পর্কিত পাঠ

Guard Clause দিয়ে Nested Conditional সরাও: Arrow সমান করো

Guard clause কী, কীভাবে nested if-else-এর arrow shape ভেঙে code সমান করা যায় — মসজিদের গেটের গল্পের মাধ্যমে TypeScript আর C#-এ step-by-step শেখো।

আরও পড়ুন

Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো

Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।

আরও পড়ুন

Decompose Conditional: জটিল if-কে সহজ নামে ভেঙে ফেলো

Decompose Conditional refactoring শেখো স্কুলের নোটিশের গল্প দিয়ে — সহজ TypeScript ও C# উদাহরণ, নিরাপদ ধাপ, আর IDE shortcut সহ।

আরও পড়ুন

Consolidate Conditional Expression: অনেক ছোট চেক, একটাই পরিষ্কার প্রশ্ন

স্কুল গেটের গল্প দিয়ে Consolidate Conditional Expression শেখো — TypeScript আর C# উদাহরণ, নিরাপদ ধাপ, আর side-effect-এর ফাঁদ যেটা না জানলেই নয়।

আরও পড়ুন