Remove Flag Argument
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”เอา function ที่ใช้ argument แบบ boolean เลือกระหว่างสอง behavior มาแยกเป็นสอง function หนึ่ง function ต่อหนึ่ง behavior โดยตั้งชื่อให้บอกตรง ๆ ว่าทำอะไร caller จะได้เลิกส่ง true ลอย ๆ ที่ไม่มีใครถอดรหัสออก แล้วหันมาเรียก function ที่ชื่อคือคำตอบในตัวเอง
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”flag argument ซ่อนเจตนาไว้ตรงจุดที่เรียก พออ่าน sendEmail(order, true) เจ้า true ไม่ได้บอกอะไรเลย คุณต้องเปิด function ไปดูเองว่า true แปลว่า “ส่งด่วน” ส่วน false แปลว่า “เข้าคิวไว้ทีหลัง” เท่ากับ boolean กำลังทำงานที่ชื่อ function ควรทำ แถมยังมักลาก body ให้แตก branch ตามไปด้วย เป็น if (urgent) ... else ... ที่ยัด workflow สองอันซึ่งไม่เกี่ยวกันไว้ใน function เดียว ผลคืออ่านไม่สะอาดทั้งคู่ และต้อง test ผ่านประตูเดียวกันทั้งคู่
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”function แจ้งเตือนที่ boolean เลือกระหว่างเส้นทางด่วนและเส้นทางปกติ หลังจากนั้น สอง function ที่มีชื่อ แต่ละตัวมุ่งเน้น
// Beforefunction notify(message: string, urgent: boolean): void { if (urgent) { console.log(`[URGENT] ${message}`); } else { console.log(`[info] ${message}`); }}
notify("Server down", true);notify("Backup finished", false);
// Afterfunction notifyUrgent(message: string): void { console.log(`[URGENT] ${message}`);}
function notifyInfo(message: string): void { console.log(`[info] ${message}`);}
notifyUrgent("Server down");notifyInfo("Backup finished");# Beforedef notify(message, urgent): if urgent: print(f"[URGENT] {message}") else: print(f"[info] {message}")
notify("Server down", True)notify("Backup finished", False)
# Afterdef notify_urgent(message): print(f"[URGENT] {message}")
def notify_info(message): print(f"[info] {message}")
notify_urgent("Server down")notify_info("Backup finished")// Beforefunc Notify(message string, urgent bool) { if urgent { fmt.Printf("[URGENT] %s\n", message) } else { fmt.Printf("[info] %s\n", message) }}
Notify("Server down", true)Notify("Backup finished", false)
// Afterfunc NotifyUrgent(message string) { fmt.Printf("[URGENT] %s\n", message)}
func NotifyInfo(message string) { fmt.Printf("[info] %s\n", message)}
NotifyUrgent("Server down")NotifyInfo("Backup finished")// Beforefn notify(message: &str, urgent: bool) { if urgent { println!("[URGENT] {message}"); } else { println!("[info] {message}"); }}
notify("Server down", true);notify("Backup finished", false);
// Afterfn notify_urgent(message: &str) { println!("[URGENT] {message}");}
fn notify_info(message: &str) { println!("[info] {message}");}
notify_urgent("Server down");notify_info("Backup finished");กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- ยืนยันว่า flag เลือกระหว่าง behavior จริง ๆ ไม่ใช่แค่สลับค่า ถ้าแค่สลับตัวเลขหรือ label การใช้ Parameterize Function อาจเหมาะกว่าการแยก
- สร้าง function ใหม่สำหรับแขนงหนึ่งของ flag ตั้งชื่อให้จับเจตนาของแขนงนั้น อย่าง
notifyUrgent - คัดลอก logic ของแขนงที่เกี่ยวข้องเข้าไปใน function ใหม่ แล้วทิ้งแขนงที่ตายแล้ว รัน test
- หา caller ทุกตัวที่ส่ง flag ด้วยค่านั้น แล้วเปลี่ยนเส้นทางไปยัง function ใหม่ พร้อมลบ argument boolean ออก
- ทำซ้ำขั้นที่ 2 ถึง 4 สำหรับอีกแขนงหนึ่ง
- รัน test หลังการเปลี่ยนเส้นทางแต่ละครั้ง เพื่อให้ความล้มเหลวชี้ไปยัง caller จุดเดียว
- เมื่อไม่มี caller ตัวใดส่ง flag อีกแล้ว ให้ลบ function เดิมทิ้ง
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”แยก flag argument ออกเมื่อใดก็ตามที่ parameter boolean เปลี่ยน สิ่งที่ function ทำ เมื่อใดที่ true หรือ false แบบ literal ที่จุดเรียกใช้อ่านไม่รู้เรื่อง หรือเมื่อใดที่เนื้อในแตกออกเป็นสองกระแสงานที่แทบไม่มีอะไรร่วมกัน สอง function ที่มีชื่อทำให้การเรียกใช้แต่ละครั้งอธิบายตัวเองได้ และให้แต่ละเส้นทางถูก test ได้โดยตรง
ต้นทุนคือ namespace มี function เยอะขึ้น และ code เตรียมการที่ใช้ร่วมกันจริง ๆ อาจไปโผล่สองที่ ให้ดึงส่วนที่ใช้ร่วมกันออกมาเป็น private helper จะได้ไม่ต้องเขียนซ้ำ และใช้สามัญสำนึกด้วย flag ที่แค่พลิกค่า config โดยไม่แตก branch เชิง behavior เก็บไว้ได้ไม่เป็นไร smell ที่พูดถึงตรงนี้เจาะจงที่ boolean ซึ่ง ตัดสินใจเลือกระหว่าง behavior เท่านั้น
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Remove Flag Argument เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| boolean เลือกระหว่าง behavior สองแบบ | boolean แค่สลับค่า config ไม่มีการ branch |
true/false ที่ call site อ่านไม่รู้เรื่อง | ทั้งสอง path แชร์ logic ส่วนใหญ่ร่วมกัน |
| สองแขนงแทบไม่มี code ร่วมกัน | มี flags มากกว่า 1 ตัวซ้อนกัน — Extract Class แทน |
⚠️ ไม่ควร Remove Flag Argument เมื่อ:
- flag เป็น enum ที่มี 3+ ค่า — แยกแต่ละค่าออกเป็น function อาจสร้าง duplication มากกว่าเดิม
- สองแขนง share prep logic มาก — extract helper ก่อนค่อยแยก function
- caller ผ่าน flag จาก variable — การแยก function อาจแค่ย้าย if ไปข้างนอก