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

Moving Features

กลุ่ม moving features เป็นเรื่องของ ตำแหน่ง การ refactor แบบ composing-methods จะปรับรูปร่างภายในของ function แต่การย้ายเหล่านี้จะตัดสินว่า function field หรือกลุ่ม behavior ทั้งก้อนควรไปอยู่ใน class หรือโมดูลใด โปรแกรมยังคงทำสิ่งเดิมหลังจากนั้น — แต่งานแต่ละชิ้นจะอยู่ติดกับข้อมูลและผู้ร่วมงานที่ใช้จริง

การตัดสินใจเรื่องซอฟต์แวร์ในวันแรกแทบไม่เคยรอดมาถึงวันที่ร้อย function ที่เคยเป็นของ Order ค่อย ๆ เติบโตจนอ่านข้อมูลส่วนใหญ่จาก Customer field ที่เพิ่มเข้าไปใน record หนึ่งกลับถูกแตะต้องทุกที่ยกเว้น record นั้น class เล็ก ๆ ค่อย ๆ ดูดซับความรับผิดชอบที่สองเข้ามาอย่างเงียบ ๆ จนกลายเป็นสอง class ที่สวมชื่อเดียวกัน เมื่อ behavior กับข้อมูลที่ต้องใช้อยู่ห่างกัน ทุกการแก้ก็แปลว่าต้องกระโดดไปมาระหว่างไฟล์ ท่าย้ายในโมดูลนี้จะดึงทั้งสองอย่างกลับมาอยู่ด้วยกัน

Code Smellอาการที่บ่งชี้ท่าที่แนะนำ
Feature Envyfunction อ้างอิงข้อมูลของ class อื่นมากกว่า class ตัวเองMove Function
Data Clumpsfield ชุดเดิมเดินทางไปด้วยกันในหลาย functionMove Field
Large Classclass ทำสองงานที่ไม่เกี่ยวกัน เปลี่ยนด้านหนึ่งกระทบอีกด้านExtract Class
Inappropriate Intimacycaller เดินตามสาย object เพื่อถึง dependencyHide Delegate
Middle Manclass ส่งต่อทุก method call โดยไม่เพิ่มค่าInline Class
flowchart TD
  A["A feature sits in the wrong place"] --> B{"What is misplaced?"}
  B -->|"A function leans on another context"| C["Move Function"]
  B -->|"A field is used elsewhere more"| D["Move Field"]
  B -->|"One class does two jobs"| E["Extract Class"]
  B -->|"A class no longer earns its keep"| F["Inline Class"]
  B -->|"Callers navigate an object chain"| G["Hide Delegate"]
  C --> H["Each module owns the right work"]
  D --> H
  E --> H
  F --> H
  G --> H
เลือกการย้ายแบบ moving-features ตามสิ่งที่วางผิดที่

การ refactor เก้าแบบ ซึ่งแต่ละแบบย้อนกลับได้:

  • Move Function — เมื่อ function อ้างอิงข้อมูลและ method ของ object อื่นมากกว่าของตัวเอง (Feature Envy) ให้ย้ายไปอยู่ class ที่ function ตัวนั้นอิจฉา
  • Move Field — เมื่อ field ถูกอ่านและเขียนจาก record อื่นมากกว่าบ้านปัจจุบัน ให้ย้ายไปอยู่ที่ที่ใช้งานจริง
  • Extract Class — เมื่อ class หนึ่งเติบโตจนทำสองงานที่ต่างกัน (Large Class) ให้แยกความรับผิดชอบหนึ่งออกไปเป็น class ใหม่
  • Inline Class — ท่ากลับกัน เมื่อ class ไม่คุ้มจะมีอยู่ต่อไป ให้พับกลับเข้า class ที่ใช้งาน
  • Hide Delegate (และท่ากลับกันคือ Remove Middle Man) — ห่อหุ้มการมอบหมาย (delegation) เพื่อให้ caller เลิกเดินไล่ตามสายของ object ตามหลัก Law of Demeter
  • Move Statements into Function — เมื่อ statement เดิมรันติดกับการเรียก function ทุกครั้ง ให้พับเข้าไปใน function เสีย caller จะได้ลืมไม่ได้
  • Move Statements to Callers — สิ่งตรงข้าม: เมื่อ function ทำงานเกินไปหนึ่งอย่างสำหรับ caller บางราย ให้ดันคำสั่งที่แตกต่างกันออกไปยัง caller เหล่านั้น
  • Split Loop — เมื่อ loop เดียวทำสองงานที่ไม่เกี่ยวกัน ให้ซอยเป็นสอง loop ที่ทำงานเดียว เพื่อความชัดเจน
  • Combine Functions into Class — เมื่อ function คอยส่งข้อมูลก้อนเดิมไปมา ให้รวบเข้าเป็น class ที่เก็บข้อมูลนั้นไว้เป็น field

แต่ละบทเรียนแสดง before-and-after เดียวกันใน TypeScript, Python, Go และ Rust จากนั้นพาเดินผ่านกลไก (mechanics) ที่ปลอดภัยและเป็นลำดับขั้น — โดยรัน test ระหว่างทุกขั้นตอน

การ refactor แบบ "moving features" เปลี่ยนสิ่งใดเป็นหลัก?
Move Function แก้ smell ใดได้ตรงที่สุด?
Inline Class เป็นสิ่งตรงข้ามของการ refactor ใดในโมดูลนี้?