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

การทำให้เงื่อนไขเรียบง่ายขึ้น

ตระกูล การทำให้เงื่อนไขเรียบง่ายขึ้น (simplifying conditionals) ว่าด้วย logic การตัดสินใจ ได้แก่ branch, guard และ switch ที่ตัดสินว่า โปรแกรมจะทำอะไรต่อไป เงื่อนไขที่เริ่มต้นจากคำถามเดียวที่ชัดเจน ค่อย ๆ งอกเงื่อนไขเพิ่มเข้ามา ซ้อนอยู่ในเงื่อนไขอื่น และแตกสำเนากระจายไปทั่ว code เบส การ refactor เหล่านี้คลายความรกรุงรังนั้นออก เพื่อให้ผู้อ่านมองเห็นได้ในพริบตาว่าเคสไหนพาไปทางไหน

logic เงื่อนไขที่ซับซ้อนเป็นหนึ่งในสัญญาณที่ดังที่สุดว่า function นั้นดูแลรักษายาก สัญญาณเตือนก็คุ้นหน้าคุ้นตา ได้แก่ boolean expression ที่ยาวจนต้องอ่านสองรอบ ลูกศรของบล็อก if ที่ซ้อนกันไล่ออกไปทางขอบขวาของจอ การ switch บน field “type” ที่ทำซ้ำในห้า function และ if (x == null) ที่กระจายอยู่ในทุก method ที่แตะต้อง x แต่ละอย่างมีวิธีแก้ที่มีชื่อเฉพาะ และวิธีแก้แต่ละแบบทำให้ behavior เหมือนเดิมทุกประการ แต่ทำให้เจตนาชัดเจนขึ้น

Code Smellอาการที่บ่งชี้ท่าที่แนะนำ
Complex Conditionalexpressionboolean ยาวที่ต้องอ่านสองรอบเพื่อถอดรหัสDecompose Conditional
Duplicated Conditionalการตรวจสอบหลายอันนำไปสู่ผลลัพธ์เดียวกันConsolidate Conditional Expression
Nested Conditionalif/else ซ้อนลึกจนหา happy path ไม่เจอReplace Nested Conditional with Guard Clauses
Switch Statementtype switch ตัวเดียวกันซ้ำใน function หลายตัวReplace Conditional with Polymorphism
Null Checkif (x == null) เดิมซ้ำในทุก method ที่แตะ xIntroduce Special Case
Control Flagboolean ที่มีไว้เพียงเพื่อบังคับ loopReplace Control Flag with Break/Return
Error Codesentinel 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
การเลือกท่า simplifying-conditionals ตามสิ่งที่ผิดปกติ

การ 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 ระหว่างทุกขั้นตอน

การ refactor ตระกูล "simplifying conditionals" ปรับปรุงอะไรเป็นหลัก?
การ refactor แบบใดทำให้ if/else ที่ซ้อนลึกแบนราบลงด้วยการ return ออกก่อนสำหรับเคสพิเศษ?
เมื่อการตรวจสอบหลายตัวให้ผลลัพธ์เดียวกัน ท่าไหนที่รวมเข้าด้วยกัน?