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 ทันทีที่เจอ
// Beforefunction 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;}
// Afterfunction hasDebtor(customers: Customer[]): boolean { for (const customer of customers) { if (customer.balance < 0) { return true; } } return false;}# Beforedef has_debtor(customers): found = False i = 0 while not found and i < len(customers): if customers[i].balance < 0: found = True i += 1 return found
# Afterdef has_debtor(customers): for customer in customers: if customer.balance < 0: return True return False// Beforefunc HasDebtor(customers []Customer) bool { found := false i := 0 for !found && i < len(customers) { if customers[i].Balance < 0 { found = true } i++ } return found}
// Afterfunc HasDebtor(customers []Customer) bool { for _, c := range customers { if c.Balance < 0 { return true } } return false}// Beforefn has_debtor(customers: &[Customer]) -> bool { let mut found = false; let mut i = 0; while !found && i < customers.len() { if customers[i].balance < 0.0 { found = true; } i += 1; } found}
// Afterfn has_debtor(customers: &[Customer]) -> bool { customers.iter().any(|c| c.balance < 0.0)}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 ที่คุมอยู่ แล้วยืนยันว่า flag มีไว้คุม loop อย่างเดียว ไม่ได้แบกผลลัพธ์ที่ code ข้างล่างต้องใช้ด้วย
- เลือกตัวแทน ถ้า loop ไม่มีงานเหลือหลังตั้ง flag ก็
returnคำตอบตรงนั้นเลย ถ้ายัง มี งานต่อหลัง loop ให้ใช้breakออกมา ถ้าแค่อยากข้ามส่วนที่เหลือของรอบนั้น ให้ใช้continue - แทนที่บรรทัดที่ตั้ง flag ด้วยการกระโดดที่เลือก
- ลบ flag ออกจากเงื่อนไขของ loop ทำให้เงื่อนไขเรียบง่ายเหลือเท่าที่เหลือจริง ๆ (มักเป็นขอบเขตการวน หรือไม่มีอะไรเลย —
for…ofธรรมดา) - ลบการประกาศ flag และ
return flagตอนท้ายใด ๆ แทนที่ด้วยค่าที่ loop ตอนนี้ตกผ่านมาถึง - รัน 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 Nested Conditional with Guard Clauses
- Decompose Conditional
- Consolidate Conditional Expression
| ใช้ 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