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

Remove Flag Argument

เอา function ที่ใช้ argument แบบ boolean เลือกระหว่างสอง behavior มาแยกเป็นสอง function หนึ่ง function ต่อหนึ่ง behavior โดยตั้งชื่อให้บอกตรง ๆ ว่าทำอะไร caller จะได้เลิกส่ง true ลอย ๆ ที่ไม่มีใครถอดรหัสออก แล้วหันมาเรียก function ที่ชื่อคือคำตอบในตัวเอง

flag argument ซ่อนเจตนาไว้ตรงจุดที่เรียก พออ่าน sendEmail(order, true) เจ้า true ไม่ได้บอกอะไรเลย คุณต้องเปิด function ไปดูเองว่า true แปลว่า “ส่งด่วน” ส่วน false แปลว่า “เข้าคิวไว้ทีหลัง” เท่ากับ boolean กำลังทำงานที่ชื่อ function ควรทำ แถมยังมักลาก body ให้แตก branch ตามไปด้วย เป็น if (urgent) ... else ... ที่ยัด workflow สองอันซึ่งไม่เกี่ยวกันไว้ใน function เดียว ผลคืออ่านไม่สะอาดทั้งคู่ และต้อง test ผ่านประตูเดียวกันทั้งคู่

function แจ้งเตือนที่ boolean เลือกระหว่างเส้นทางด่วนและเส้นทางปกติ หลังจากนั้น สอง function ที่มีชื่อ แต่ละตัวมุ่งเน้น

// Before
function 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);
// After
function notifyUrgent(message: string): void {
console.log(`[URGENT] ${message}`);
}
function notifyInfo(message: string): void {
console.log(`[info] ${message}`);
}
notifyUrgent("Server down");
notifyInfo("Backup finished");
  1. ยืนยันว่า flag เลือกระหว่าง behavior จริง ๆ ไม่ใช่แค่สลับค่า ถ้าแค่สลับตัวเลขหรือ label การใช้ Parameterize Function อาจเหมาะกว่าการแยก
  2. สร้าง function ใหม่สำหรับแขนงหนึ่งของ flag ตั้งชื่อให้จับเจตนาของแขนงนั้น อย่าง notifyUrgent
  3. คัดลอก logic ของแขนงที่เกี่ยวข้องเข้าไปใน function ใหม่ แล้วทิ้งแขนงที่ตายแล้ว รัน test
  4. หา caller ทุกตัวที่ส่ง flag ด้วยค่านั้น แล้วเปลี่ยนเส้นทางไปยัง function ใหม่ พร้อมลบ argument boolean ออก
  5. ทำซ้ำขั้นที่ 2 ถึง 4 สำหรับอีกแขนงหนึ่ง
  6. รัน test หลังการเปลี่ยนเส้นทางแต่ละครั้ง เพื่อให้ความล้มเหลวชี้ไปยัง caller จุดเดียว
  7. เมื่อไม่มี 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 ไปข้างนอก
ทำไม flag argument จึงซ่อนเจตนาไว้ที่จุดเรียกใช้?
เมื่อใดที่การแยกออกเป็นสอง function เป็นทางเลือกที่ถูกต้องแทนที่จะ parameterize?
หลังจากแยกแล้ว คุณควรทำอย่างไรกับ logic การเตรียมการที่ทั้งสองแขนงใช้ร่วมกัน?
หลังจากเปลี่ยนเส้นทาง caller ทั้งหมดออกจาก flag แล้ว ขั้นตอนสุดท้ายคืออะไร?