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

Split Phase

เมื่อ code บล็อกหนึ่งทำงานที่ต่างกันจริง ๆ สองอย่างเรียงกัน เช่น parse ข้อมูลเข้าแล้วค่อย คำนวณ ผลลัพธ์ต่อ ให้ซอยออกเป็นสอง phase โดย phase แรกสร้างโครงสร้างข้อมูลกลางที่ชัดเจนออกมา ส่วน phase ที่สองรับเฉพาะโครงสร้างนั้นไปใช้

นี่คือวิธีรักษา tangled responsibilities ภายใน code ช่วงเดียว การ parse และการคำนวณถูกสอดประสานกัน ดังนั้นการเปลี่ยน format ของ input ก็บังคับให้คุณต้องไปแตะคณิตศาสตร์ และการเปลี่ยนคณิตศาสตร์ก็บังคับให้คุณต้องคิดถึง string ทั้งสองความกังวลใช้ตัวแปรร่วมกัน และผู้อ่านไม่สามารถโฟกัสทีละอย่างได้เลย การตั้งชื่อโครงสร้างกลางขีดเส้นที่สะอาดระหว่างทั้งสอง

function ตัวนี้ parse string "name:qty:price" ดิบ ๆ แล้วคำนวณยอดรวมรวดเดียวจบ เราซอยออกเป็นสองขั้น คือ parse ให้เป็น order object ก่อน แล้วค่อยคิดราคาจาก order นั้น

// Before
function orderTotal(raw: string): number {
const parts = raw.split(':');
const qty = Number(parts[1]);
const price = Number(parts[2]);
return qty > 100 ? qty * price * 0.9 : qty * price;
}
// After
interface ParsedOrder {
name: string;
qty: number;
price: number;
}
function parseOrder(raw: string): ParsedOrder {
const [name, qty, price] = raw.split(':');
return { name, qty: Number(qty), price: Number(price) };
}
function priceOrder(order: ParsedOrder): number {
const base = order.qty * order.price;
return order.qty > 100 ? base * 0.9 : base;
}
function orderTotal(raw: string): number {
return priceOrder(parseOrder(raw));
}
flowchart LR
  R["Raw input<br/>(string)"] --> P1["Phase 1: Parse"]
  P1 --> I["Intermediate data<br/>(structured order)"]
  I --> P2["Phase 2: Calculate"]
  P2 --> O["Result<br/>(price)"]
สอง phase เชื่อมกันด้วยโครงสร้างกลางที่ชัดเจน
  1. extract phase ที่สอง (การคำนวณ) ออกมาเป็น function ของตัวเอง รัน test
  2. สร้างโครงสร้างข้อมูลกลางขึ้นมา แล้วส่งเข้าไปเป็น argument ให้ function ของ phase ที่สอง
  3. ตรวจสอบแต่ละค่าที่ phase ที่สองอ่านจาก phase แรก ย้ายแต่ละค่าไปไว้บนโครงสร้างกลางทีละค่า โดยรัน test หลังการย้ายแต่ละครั้ง
  4. extract phase แรก (การ parse) ออกมาเป็น function ของตัวเองที่คืนโครงสร้างกลาง
  5. ยืนยันว่า function ระดับบนสุดตอนนี้เป็นเพียง phase แรกป้อนเข้า phase ที่สอง แล้ว รันชุด test ทั้งหมด

ใช้ Split Phase เมื่อบล็อกหนึ่งจัดการสองขั้นตอนที่แยกกันชัดเจน เช่น parse แล้ว compute, validate แล้ว act หรือ fetch แล้ว format และสองขั้นนั้นแทบไม่มีอะไรร่วมกันนอกจากการส่งค่าต่อ โครงสร้างข้อมูลกลางจะกลายเป็น contract ที่มีชื่อคั่นระหว่างสองขั้น แต่ละ phase จึงเปลี่ยนได้อิสระและ test แยกกันได้

ราคาที่ต้องจ่ายคือ data type พิเศษหนึ่งอันและ indirection เล็ก ๆ ถ้าสอง “phase” พันกันแน่นหรือสั้นแบบไม่สำคัญ รอยต่อก็เป็นของปลอม — ปล่อยให้ code เป็นชิ้นเดียวไว้

ใช้ Split Phase เมื่อหลีกเลี่ยงเมื่อ
function ทำสองขั้นตอนที่แยกกันชัดเจนสองขั้นตอนนั้นพันกันจนแยกไม่ได้ง่าย
data ที่ phase แรกสร้างขึ้น ถูกใช้โดย phase สองแต่ละ phase มีแค่ 2-3 บรรทัด — overhead ไม่คุ้ม
ต้องการ test แต่ละ phase แยกกันกำลัง optimize performance — การแยก phase อาจเพิ่ม pass

⚠️ ไม่ควร Split Phase เมื่อ:

  • function นั้นทำขั้นตอนเดียวที่ชัดเจน — การแยกจะสร้าง complexity ไม่จำเป็น
  • สองส่วนแชร์ state เยอะเกินไปจนต้องส่ง parameter มากมาย
  • ทีมยังไม่คุ้นกับ data-transfer object pattern
อะไรเชื่อมสอง phase เข้าด้วยกันหลังการ refactor แบบ Split Phase?
คู่ใดเป็นตัวเลือกทั่วไปสำหรับ Split Phase?
เมื่อใดที่ Split Phase ไม่คุ้มค่าที่จะนำไปใช้?