การทำให้เงื่อนไขเรียบง่ายขึ้น
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”ตระกูล การทำให้เงื่อนไขเรียบง่ายขึ้น (simplifying conditionals) ว่าด้วย logic การตัดสินใจ ได้แก่ branch, guard และ switch ที่ตัดสินว่า โปรแกรมจะทำอะไรต่อไป เงื่อนไขที่เริ่มต้นจากคำถามเดียวที่ชัดเจน ค่อย ๆ งอกเงื่อนไขเพิ่มเข้ามา ซ้อนอยู่ในเงื่อนไขอื่น และแตกสำเนากระจายไปทั่ว code เบส การ refactor เหล่านี้คลายความรกรุงรังนั้นออก เพื่อให้ผู้อ่านมองเห็นได้ในพริบตาว่าเคสไหนพาไปทางไหน
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”logic เงื่อนไขที่ซับซ้อนเป็นหนึ่งในสัญญาณที่ดังที่สุดว่า function นั้นดูแลรักษายาก สัญญาณเตือนก็คุ้นหน้าคุ้นตา ได้แก่ boolean expression ที่ยาวจนต้องอ่านสองรอบ ลูกศรของบล็อก if ที่ซ้อนกันไล่ออกไปทางขอบขวาของจอ การ switch บน field “type” ที่ทำซ้ำในห้า function และ if (x == null) ที่กระจายอยู่ในทุก method ที่แตะต้อง x แต่ละอย่างมีวิธีแก้ที่มีชื่อเฉพาะ และวิธีแก้แต่ละแบบทำให้ behavior เหมือนเดิมทุกประการ แต่ทำให้เจตนาชัดเจนขึ้น
| Code Smell | อาการที่บ่งชี้ | ท่าที่แนะนำ |
|---|---|---|
| Complex Conditional | expressionboolean ยาวที่ต้องอ่านสองรอบเพื่อถอดรหัส | Decompose Conditional |
| Duplicated Conditional | การตรวจสอบหลายอันนำไปสู่ผลลัพธ์เดียวกัน | Consolidate Conditional Expression |
| Nested Conditional | if/else ซ้อนลึกจนหา happy path ไม่เจอ | Replace Nested Conditional with Guard Clauses |
| Switch Statement | type switch ตัวเดียวกันซ้ำใน function หลายตัว | Replace Conditional with Polymorphism |
| Null Check | if (x == null) เดิมซ้ำในทุก method ที่แตะ x | Introduce Special Case |
| Control Flag | boolean ที่มีไว้เพียงเพื่อบังคับ loop | Replace Control Flag with Break/Return |
| Error Code | sentinel value สำหรับรายงานความล้มเหลว ถูกมองข้ามได้ง่าย | Replace Error Code with Exception |
flowchart TD
A["Tangled conditional logic"] --> B{"What is wrong?"}
B -->|"A condition is hard to read"| C["Decompose Conditional"]
B -->|"Many checks, one outcome"| D["Consolidate Conditional Expression"]
B -->|"Deep nesting hides the happy path"| E["Replace Nested Conditional with Guard Clauses"]
B -->|"A switch on a type keeps recurring"| F["Replace Conditional with Polymorphism"]
B -->|"The same null check is everywhere"| G["Introduce Special Case"]
C --> H["Decision logic that explains itself"]
D --> H
E --> H
F --> H
G --> H โมดูลนี้ครอบคลุมอะไรบ้าง
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไรบ้าง”การ refactor แปดแบบ แต่ละแบบเล็กและย้อนกลับได้:
- Decompose Conditional — แยกตัวทดสอบ, then-branch และ else-branch ออกเป็น function ที่ตั้งชื่อดี เพื่อให้โครงสร้างอ่านได้เหมือนประโยค: ถ้าเป็นวันพีค ให้คิดเรตพีค ไม่อย่างนั้นคิดเรตปกติ
- Consolidate Conditional Expression — เมื่อการตรวจสอบแยกกันหลายอันนำไปสู่ผลลัพธ์เดียวกัน ให้รวมเข้าเป็นเงื่อนไขเดียวและตั้งชื่อให้ เพื่อให้ผู้อ่านได้รู้ ทำไม แทนที่จะรู้ว่า มีกี่ทาง
- Replace Nested Conditional with Guard Clauses — ทำให้
if/elseที่ซ้อนลึกแบนราบลง ด้วยการ return ออกก่อนสำหรับเคสพิเศษ เหลือเส้นทางหลักไว้แบบไม่ย่อหน้าและเห็นได้ชัด - Replace Conditional with Polymorphism — เมื่อ
switchหรือifที่อิงชนิดข้อมูลเกิดขึ้นซ้ำ ให้ดันแต่ละ branch ลงไปเป็น method ของ subclass เพื่อให้ภาษาทำ dispatch ให้แทน - Introduce Special Case — แทนที่การตรวจ null-หรือพิเศษที่ทำซ้ำ ด้วย object ที่ตอบกลับด้วยค่า default ที่สมเหตุสมผล caller จะได้เลิกถามว่า “ค่านี้หายไปหรือเปล่า”
- Introduce Assertion — ทำให้ข้อสมมติที่ code ส่วนหนึ่งพึ่งพาอยู่เงียบ ๆ ปรากฏชัด เพื่อให้ข้อสมมติที่ถูกละเมิดล้มเหลวอย่างดังที่ต้นตอ แทนที่จะทำให้ข้อมูลปลายน้ำเสียหาย
- Replace Control Flag with Break/Return — ลบ boolean ที่มีอยู่เพียงเพื่อบังคับทิศทาง loop แล้วปล่อยให้
break,returnหรือcontinueแสดงจุดออกตรงที่เกิดขึ้นจริง - Replace Error Code with Exception — เลิกรายงานความล้มเหลวด้วยค่า sentinel ที่ถูกมองข้ามได้ ให้โยน exception แทน หรือใน Go และ Rust ให้ return
error/Resultอย่างชัดเจน เพื่อไม่ให้ความล้มเหลวถูกเข้าใจผิดว่าเป็นความสำเร็จ
แต่ละบทเรียนแสดงตัวอย่างก่อนและหลังแบบเดียวกันใน TypeScript, Python, Go และ Rust จากนั้นพาเดินผ่านกลไกที่ปลอดภัยและเป็นลำดับขั้น — โดยรัน test ระหว่างทุกขั้นตอน