Decompose Conditional
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”เอาเงื่อนไขที่ทั้ง การทดสอบ และ สาขา ต่างเป็นก้อนรายละเอียด แล้วแทนแต่ละก้อนด้วยการเรียก function ที่ตั้งชื่อตามเจตนา จากนั้น if ก็จะอ่านได้เหมือนประโยค — ถ้าไม่ใช่หน้าร้อน ให้ใช้ค่าธรรมเนียมฤดูหนาว มิฉะนั้นใช้ค่าธรรมเนียมฤดูร้อน — และการคำนวณที่รกรุงรังก็ถูกย้ายไปพ้นทาง
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”นี่คือวิธีแก้สำหรับ เงื่อนไขที่ซับซ้อน — ชนิดที่คุณบอกไม่ได้เลยว่าทำไมสาขาหนึ่งถึงถูกเลือก ถ้าไม่ได้แกะ expression ออกมาดู การทดสอบเป็นการเปรียบเทียบวันที่ที่พันกัน ส่วนสาขาก็เป็นสูตรที่เต็มไปด้วยตัวเลขวิเศษ ลำดับการควบคุมที่คุณสนใจ (กรณีไหนใช้ได้) จมหายไปในการคำนวณ เมื่อเงื่อนไขและแต่ละสาขามีชื่อเป็นของตัวเอง การตัดสินใจก็จะชัดเจน และคณิตศาสตร์ก็ถูกซุกไว้ในตัวช่วยที่คุณอ่านได้เมื่อต้องการ
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”function คิดค่าบริการคิดอัตราฤดูหนาวนอกหน้าร้อน และอัตราฤดูร้อนภายในหน้าร้อน ก่อนหน้านี้ การทดสอบและสาขาทั้งสองเป็นการคำนวณแบบ inline หลังจากนั้น แต่ละส่วนกลายเป็นตัวช่วยที่มีชื่อ
// Beforefunction charge(date: Date, quantity: number): number { let result: number; if (date.getMonth() < 5 || date.getMonth() > 8) { result = quantity * 12 + 100; } else { result = quantity * 8; } return result;}
// Afterfunction charge(date: Date, quantity: number): number { return notSummer(date) ? winterCharge(quantity) : summerCharge(quantity);}
function notSummer(date: Date): boolean { return date.getMonth() < 5 || date.getMonth() > 8;}
function winterCharge(quantity: number): number { return quantity * 12 + 100;}
function summerCharge(quantity: number): number { return quantity * 8;}# Beforedef charge(date, quantity): if date.month < 6 or date.month > 9: result = quantity * 12 + 100 else: result = quantity * 8 return result
# Afterdef charge(date, quantity): return winter_charge(quantity) if not_summer(date) else summer_charge(quantity)
def not_summer(date): return date.month < 6 or date.month > 9
def winter_charge(quantity): return quantity * 12 + 100
def summer_charge(quantity): return quantity * 8// Beforefunc Charge(date time.Time, quantity int) int { var result int if date.Month() < time.June || date.Month() > time.September { result = quantity*12 + 100 } else { result = quantity * 8 } return result}
// Afterfunc Charge(date time.Time, quantity int) int { if notSummer(date) { return winterCharge(quantity) } return summerCharge(quantity)}
func notSummer(date time.Time) bool { return date.Month() < time.June || date.Month() > time.September}
func winterCharge(quantity int) int { return quantity*12 + 100}
func summerCharge(quantity int) int { return quantity * 8}// Beforefn charge(month: u32, quantity: i64) -> i64 { let result; if month < 6 || month > 9 { result = quantity * 12 + 100; } else { result = quantity * 8; } result}
// Afterfn charge(month: u32, quantity: i64) -> i64 { if not_summer(month) { winter_charge(quantity) } else { summer_charge(quantity) }}
fn not_summer(month: u32) -> bool { month < 6 || month > 9}
fn winter_charge(quantity: i64) -> i64 { quantity * 12 + 100}
fn summer_charge(quantity: i64) -> i64 { quantity * 8}flowchart LR
subgraph Before["Before"]
A["if (date before summer<br/>or date after summer)<br/>charge = qty * winterRate + flat<br/>else<br/>charge = qty * summerRate"]
end
subgraph After["After"]
B["if notSummer(date)"]
B --> C["winterCharge(qty)"]
B --> D["summerCharge(qty)"]
end
Before -.->|"Decompose Conditional"| After กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- ใช้ Extract Function กับเงื่อนไข: ยกการทดสอบ boolean ทั้งก้อนออกมาเป็น function ที่ชื่อตอบคำถาม ทำไม สาขานี้ถึงถูกเลือก (
notSummer) ไม่ใช่ อย่างไร ที่ถูกคำนวณ - ใช้ Extract Function กับสาขา then ตั้งชื่อตามผลลัพธ์ที่สร้าง (
winterCharge) - ใช้ Extract Function กับสาขา else ในแบบเดียวกัน (
summerCharge) - รัน test หลังการสกัดแต่ละครั้ง behavior ต้องไม่เปลี่ยนแปลง
- เมื่อทั้งสามส่วนมีชื่อแล้ว
ifเดิมก็สั้นพอที่จะปล่อยไว้ตามเดิม หรือยุบให้เป็น expression เดียวได้หากภาษาของคุณมี expression เงื่อนไขที่สะอาด
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”หยิบ Decompose Conditional มาใช้ทุกครั้งที่ เงื่อนไข ของ if อ่านยาก หรือเมื่อ branch ใด branch หนึ่งเป็นก้อนคำนวณที่บังการตัดสินใจไว้ ท่านี้เข้าคู่กับ Extract Function อย่างเป็นธรรมชาติ เพราะต้องพึ่งเครื่องมือตัวนั้นถึงสามรอบ
ต้นทุนก็เหมือนการสกัดทั่วไป: สามก้าวสั้น ๆ จาก if ไปยังตัวช่วย นั่นแทบจะคุ้มค่าเสมอ เพราะผู้อ่านที่กวาดสายตาหาลำดับการควบคุมตอนนี้สามารถอ่านการตัดสินใจได้โดยไม่ต้องดำดิ่งลงไปในการคำนวณ หากสาขาหนึ่งเป็น expression ที่ง่ายเล็กน้อยอยู่แล้ว ก็ปล่อยไว้แบบ inline — การตั้งชื่อ quantity * 8 ไม่ได้เพิ่มอะไรเลย
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Decompose Conditional เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| เงื่อนไขต้องแกะ logic เพื่อเข้าใจว่าถามว่าอะไร | เงื่อนไขสั้น ๆ ที่ชัดเจนอยู่แล้ว |
| สาขา then/else เป็นการคำนวณที่ซับซ้อน | ตั้งชื่อที่ดีกว่าเดิมไม่ได้ — แสดงว่า logic ยังไม่ชัด |
| ต้องการให้ผู้อ่านเห็น decision โดยไม่ดำดิ่งรายละเอียด | function ที่ extract ออกมาถูกใช้ที่เดียวและเพิ่ม indirection ฟรี ๆ |
⚠️ ไม่ควร Decompose Conditional เมื่อ:
- เงื่อนไขนั้นง่ายและชัดเจน — เช่น
if (x > 0)ไม่จำเป็นต้องตั้งชื่อเพิ่ม- สาขาที่จะ extract มีแค่ 1-2 บรรทัดและชื่อไม่ได้อธิบายเพิ่มขึ้น
- ยังไม่มี test — extract โดยไม่มี safety net เพิ่มความเสี่ยง