Consolidate Conditional Expression
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”เอาชุดเงื่อนไขแยกกันที่แต่ละตัวคุ้มกันผลลัพธ์ เดียวกัน มาพับรวมเป็นเงื่อนไขผสมเดียว แล้วสกัดเงื่อนไขนั้นออกมาเป็น function ที่มีชื่อ การตรวจสอบสามตัวที่กระจัดกระจายซึ่งมี return 0 เหมือนกัน กลายเป็น if (isNotEligible(employee)) return 0 เพียงตัวเดียว และเหตุผลของผลลัพธ์ก็ถูกจับใจความไว้ในชื่อ
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”นี่คือวิธีแก้อาการ เงื่อนไขเรียงกันหลายตัวที่ให้ผลลัพธ์เดียวกัน การตรวจสอบแต่ละตัวแยกกันดูไม่มีพิษภัย แต่พออยู่ด้วยกันกลับซ่อนแนวคิดเดียวไว้หลัง code สามบรรทัด ผู้อ่านต้องสังเกตเองให้ได้ก่อนว่าทั้งสามตัวคืนค่าเหมือนกัน ถึงจะเข้าใจว่าจริง ๆ แล้วเป็นคำถามเดียว — คนนี้ขาดคุณสมบัติใช่ไหม พอรวมเข้าด้วยกัน คำถามเดียวนั้นก็โผล่ออกมาชัด ๆ และ helper ที่มีชื่อก็บอกผู้อ่านคนถัดไปได้เลยว่า ทำไม โดยไม่ต้องนั่งแกะเอง
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”การคำนวณเงินสวัสดิการคนพิการ return ศูนย์เมื่อเข้าเงื่อนไขตัดสิทธิข้อใดข้อหนึ่ง ตอนแรกเงื่อนไขตัดสิทธิแต่ละข้อเป็น if ของตัวเอง หลัง refactor เหลือการตรวจสอบที่มีชื่อเพียงตัวเดียว
// Beforefunction disabilityAmount(employee: Employee): number { if (employee.seniority < 2) return 0; if (employee.monthsDisabled > 12) return 0; if (employee.isPartTime) return 0; // ... compute the real amount return baseAmount(employee);}
// Afterfunction disabilityAmount(employee: Employee): number { if (isNotEligible(employee)) return 0; // ... compute the real amount return baseAmount(employee);}
function isNotEligible(employee: Employee): boolean { return ( employee.seniority < 2 || employee.monthsDisabled > 12 || employee.isPartTime );}# Beforedef disability_amount(employee): if employee.seniority < 2: return 0 if employee.months_disabled > 12: return 0 if employee.is_part_time: return 0 # ... compute the real amount return base_amount(employee)
# Afterdef disability_amount(employee): if is_not_eligible(employee): return 0 # ... compute the real amount return base_amount(employee)
def is_not_eligible(employee): return ( employee.seniority < 2 or employee.months_disabled > 12 or employee.is_part_time )// Beforefunc DisabilityAmount(employee Employee) int { if employee.Seniority < 2 { return 0 } if employee.MonthsDisabled > 12 { return 0 } if employee.IsPartTime { return 0 } // ... compute the real amount return baseAmount(employee)}
// Afterfunc DisabilityAmount(employee Employee) int { if isNotEligible(employee) { return 0 } // ... compute the real amount return baseAmount(employee)}
func isNotEligible(employee Employee) bool { return employee.Seniority < 2 || employee.MonthsDisabled > 12 || employee.IsPartTime}// Beforefn disability_amount(employee: &Employee) -> i64 { if employee.seniority < 2 { return 0; } if employee.months_disabled > 12 { return 0; } if employee.is_part_time { return 0; } // ... compute the real amount base_amount(employee)}
// Afterfn disability_amount(employee: &Employee) -> i64 { if is_not_eligible(employee) { return 0; } // ... compute the real amount base_amount(employee)}
fn is_not_eligible(employee: &Employee) -> bool { employee.seniority < 2 || employee.months_disabled > 12 || employee.is_part_time}flowchart LR
subgraph Before["Before"]
A["if seniority < 2 return 0<br/>if monthsDisabled > 12 return 0<br/>if isPartTime return 0"]
end
subgraph After["After"]
B["if isNotEligible(emp)<br/>return 0"]
end
Before -.->|"Consolidate Conditional Expression"| After กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- ยืนยันว่าเงื่อนไขเหล่านั้นมีผลลัพธ์ร่วมกันจริง ๆ และไม่มีผลข้างเคียง (side effect) ระหว่างกัน หากการตรวจสอบหนึ่งเปลี่ยนสถานะที่ตัวถัดไปพึ่งพา อย่ารวม
- รวมการทดสอบด้วย
||เมื่อทั้งหมดนำไปสู่สาขาเดียวกัน (หรือ&&เมื่อโครงสร้างเป็นการตรวจสอบซ้อนกันที่ต้องผ่านทั้งหมด) วางเงื่อนไขที่รวมแล้วไว้ติดกับผลลัพธ์ร่วม - รัน test behavior ต้องตรงกับโซ่ของ
ifแยกกันทุกประการ รวมถึงลำดับการลัดวงจร (short-circuit) - ใช้ Extract Function กับเงื่อนไขที่รวมแล้ว ตั้งชื่อตามคำถามที่เงื่อนไขนั้นตอบ (
isNotEligible) - รัน test อีกครั้ง
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”หยิบ Consolidate Conditional Expression มาใช้ทุกครั้งที่เห็นการตรวจสอบเรียงติดกันแล้วทุกตัวให้ผลลัพธ์เดียวกัน ท่านี้มีค่าที่สุดในฐานะท่าตั้งต้น เพราะพอการตรวจสอบทั้งชุดกลายเป็น function ชื่อเดียว คุณก็เอากลับมาใช้ซ้ำได้ และมักเปิดทางให้ Replace Nested Conditional with Guard Clauses ต่อทันที
อย่ารวมถ้าการตรวจสอบแต่ละตัวเป็นการตัดสินใจอิสระจริง ๆ ที่บังเอิญให้ผลเหมือนกันวันนี้ แต่พรุ่งนี้อาจแยกทางกัน เพราะรวมแล้วเท่ากับมัดกฎที่ไม่เกี่ยวกันเข้าด้วยกัน และห้ามรวมข้าม side effect เด็ดขาด เพราะการ short-circuit ของ expression ที่รวมแล้วอาจข้ามงานที่ chain เดิมเคยทำ
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Consolidate Conditional Expression เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| หลายเงื่อนไขต่างนำไปสู่ผลลัพธ์เดียวกัน | เงื่อนไขมี side effect ระหว่างกัน |
| ต้องการ Extract Function ตั้งชื่อแนวคิดนั้น | เงื่อนไขเป็นกฎที่เป็นอิสระที่อาจแยกทางกันทีหลัง |
| เงื่อนไขที่กระจัดกระจายซ่อนแนวคิดเดียว | การรวมทำให้ short-circuit ข้าม side effect สำคัญ |
⚠️ ไม่ควร Consolidate Conditional Expression เมื่อ:
- เงื่อนไขแต่ละตัวมี side effect ที่ต้องรันทุกครั้ง — การรวมด้วย
||จะ short-circuit ข้ามได้- ผลลัพธ์เหมือนกันในวันนี้แต่เหตุผลต่างกัน — อาจแยกทางในอนาคต
- การรวมทำให้เงื่อนไขยาวเกินกว่าจะตั้งชื่อที่ชัดเจนได้