ข้ามไปยังเนื้อหา

Replace Control Flag with Break/Return

control flag คือตัวแปรที่มีหน้าที่เดียวคือตัดสินว่า loop จะวนต่อไหม คุณตั้งค่าไว้ก่อน เช็คที่หัว loop พลิกค่าที่ไหนสักแห่งตรงกลาง แล้ว loop ค่อยรู้ตัวในรอบถัดไป Replace Control Flag with Break/Return ลบตัวแปรนั้นทิ้งทั้งหมด แล้วเขียนการออกจาก loop ตรง ๆ ด้วย break, return หรือ continue ณ จุดที่ตัดสินใจพอดี

โครงสร้างควบคุมของภาษารู้วิธีหยุดและข้ามอยู่แล้ว flag คือเวอร์ชันทำมือที่ล่าช้าของสิ่งนั้น — ดังนั้นการใช้ construct ของภาษาโดยตรงจึงทั้งสั้นกว่าและชัดกว่า

คุณเห็น boolean ชื่อ found, done หรือ finished ประกาศไว้ก่อน loop แล้วถูกอ่านในเงื่อนไขของ loop จะรู้ว่า loop จบเมื่อไรก็ต้องกวาดทั้ง body หาทุกจุดที่พลิก flag แล้วยังต้องคิดต่อว่าจะวนกลับมาเช็คเงื่อนไขอีกทีตอนไหน เท่ากับการออกถูกป้ายกระจายไว้หลายบรรทัด แทนที่จะระบุตรงจุดที่เกิดจริง ยิ่งถ้า flag ถูกอ่าน หลัง loop ด้วย ยิ่งเป็นสัญญาณชัด เพราะนั่นคือสองแนวคิด — “หยุดวน” กับ “รายงานผล” — ที่มาใช้ตัวแปรเดียวร่วมกัน

ค้นหาในรายการเพื่อหาลูกค้าที่ติดหนี้ ก่อนหน้านี้ flag found ขับเคลื่อน while หลังจากนั้น function return ทันทีที่เจอ

// Before
function hasDebtor(customers: Customer[]): boolean {
let found = false;
let i = 0;
while (!found && i < customers.length) {
if (customers[i].balance < 0) {
found = true;
}
i++;
}
return found;
}
// After
function hasDebtor(customers: Customer[]): boolean {
for (const customer of customers) {
if (customer.balance < 0) {
return true;
}
}
return false;
}
flowchart LR
  subgraph Before["Before"]
    A["found = false"] --> B["loop while !found"]
    B --> C["set found = true"]
    C --> B
  end
  subgraph After["After"]
    D["loop"] --> E["break / return"]
  end
  Before -.->|"Replace Control Flag"| After
loop ที่ขับเคลื่อนด้วย flag กลายเป็น break หรือ return โดยตรง
  1. หา loop กับ flag ที่คุมอยู่ แล้วยืนยันว่า flag มีไว้คุม loop อย่างเดียว ไม่ได้แบกผลลัพธ์ที่ code ข้างล่างต้องใช้ด้วย
  2. เลือกตัวแทน ถ้า loop ไม่มีงานเหลือหลังตั้ง flag ก็ return คำตอบตรงนั้นเลย ถ้ายัง มี งานต่อหลัง loop ให้ใช้ break ออกมา ถ้าแค่อยากข้ามส่วนที่เหลือของรอบนั้น ให้ใช้ continue
  3. แทนที่บรรทัดที่ตั้ง flag ด้วยการกระโดดที่เลือก
  4. ลบ flag ออกจากเงื่อนไขของ loop ทำให้เงื่อนไขเรียบง่ายเหลือเท่าที่เหลือจริง ๆ (มักเป็นขอบเขตการวน หรือไม่มีอะไรเลย — for…of ธรรมดา)
  5. ลบการประกาศ flag และ return flag ตอนท้ายใด ๆ แทนที่ด้วยค่าที่ loop ตอนนี้ตกผ่านมาถึง
  6. รัน test หลังการลบแต่ละครั้ง เพื่อให้ regression ชี้ไปที่บรรทัดเดียวที่คุณเพิ่งเปลี่ยน

ใช้ท่านี้ทุกครั้งที่ boolean มีไว้เพื่อจบหรือข้าม loop ล้วน ๆ ผลตอบแทนคือเงื่อนไขออกไปโผล่ตรงจุดที่ตัดสินใจพอดี ผู้อ่านจึงเห็นว่า ทำไม loop ถึงหยุด โดยไม่ต้องรันในหัว แถมมักปลดล็อกการเก็บกวาดต่อ เพราะพอ flag หายไป body ของ loop มักเปลี่ยนไปใช้ built-in อย่าง any, find หรือ filter ได้เลย

ข้อควรระวังคือการใช้การกระโดดมากเกินไป การออกด้วย break/return ไม่กี่จุดอ่านชัดเจน แต่ loop ที่มี continue ห้าตัวและ break สามตัวฝังในเงื่อนไขซ้อนกันอาจตามยากกว่าตอนที่มี flag เสียอีก ถ้าการลบ flag จะทำให้การออกหลายจุดกระจายไปทั่วการซ้อนที่ลึก ให้ใช้ Replace Nested Conditional with Guard Clauses ก่อนเพื่อทำให้ body แบนลง หรือ extract loop เป็น function ของตัวเองเพื่อให้การออกก่อนแต่ละจุดเป็นคำตอบที่สะอาด ทางกลับแทบไม่คุ้มที่จะทำ: การนำ flag กลับมาแทบไม่เคยทำให้อ่านง่ายขึ้น

ใช้ Replace Control Flag with Break/Return เมื่อหลีกเลี่ยงเมื่อ
boolean มีไว้เพื่อควบคุม loop เท่านั้นflag ยังถูกใช้หลัง loop เพื่อรายงานผล
while (!found) อ่านยากกว่า return true ตรงจุดการแทนที่ทำให้มี break/return หลายจุดในซ้อนที่ลึก
loop สามารถเปลี่ยนเป็น built-in (any, find) ได้team มีนโยบาย single exit point ต่อ function

⚠️ ไม่ควร Replace Control Flag with Break/Return เมื่อ:

  • flag ทำสองหน้าที่: หยุด loop และบันทึกผล — ต้องแยกออกก่อน
  • การลบ flag ทำให้มี break/return กระจายในซ้อนที่ลึกมาก — ให้ flatten loop ด้วย guard clauses ก่อน
  • ภาษา/style guide ของ team บังคับ single return point
"control flag" คืออะไร?
เมื่อ loop ไม่มีงานเหลือหลังตั้ง flag อะไรคือตัวแทนที่สะอาดที่สุด?
การกระโดดแบบใดออกจาก loop แต่ยังให้ code หลัง loop ทำงานต่อ?
บทเรียนแนะนำอะไรถ้าการลบ flag จะทำให้การออกหลายจุดกระจายไปทั่วการซ้อนที่ลึก?