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

Replace Parameter with Query

ถ้า function คำนวณค่าของ parameter ตัวหนึ่งได้เองจากข้อมูลที่รับมาอยู่แล้ว parameter ตัวนั้นก็เป็นน้ำหนักส่วนเกิน caller ทุกตัวต้องคำนวณแล้วส่งเข้ามา และแต่ละตัวก็คำนวณไม่ตรงกันได้ ลบทิ้งไปเลย แล้วให้ function ถามหาค่าเอง — นั่นคือ query — จะได้เหลือ single source of truth และลดจุดที่ call site ทำพลาดไปอีกหนึ่งจุด

parameter ตัวหนึ่งถูกส่งมาแบบเดิมทุกครั้ง โดยคำนวณจาก argument อีกตัวที่ function มีอยู่แล้ว คุณจะเห็น caller รันการคำนวณสั้น ๆ ชุดเดียวกันก่อนเรียกทุกครั้ง แล้วยื่นผลลัพธ์เข้ามา ที่แย่กว่านั้นคือ caller สองตัวคำนวณไม่ตรงกันนิดหน่อย function จึงให้ผลไม่คงเส้นคงวาขึ้นกับว่าใครเป็นคนเรียก สรุปคือ parameter ตัวนี้เสนอความยืดหยุ่นที่ไม่มีใครต้องการจริง ๆ

finalPrice รับทั้ง quantity และ discount แต่ค่า discount ถูกกำหนดโดย quantity ทั้งหมด แล้ว caller ก็ต้องคำนวณเองทุกครั้ง เราจึงลบ parameter ทิ้งแล้วย้ายการคำนวณ discount เข้าไปข้างใน

// Before
function finalPrice(quantity: number, discount: number): number {
const base = quantity * 5;
return base - base * discount;
}
const d = quantity > 100 ? 0.1 : 0;
const price = finalPrice(quantity, d);
// After
function finalPrice(quantity: number): number {
const base = quantity * 5;
return base - base * discountFor(quantity);
}
function discountFor(quantity: number): number {
return quantity > 100 ? 0.1 : 0;
}
const price = finalPrice(quantity);
flowchart LR
  subgraph Before["Before"]
    A["finalPrice(qty, discount)"] --> B["caller computes discount<br/>from qty first"]
  end
  subgraph After["After"]
    C["finalPrice(qty)"] --> D["derives discount<br/>internally"]
  end
  Before -.->|"Replace Parameter with Query"| After
parameter ที่คำนวณได้ถูกลบออกและคำนวณข้างใน
  1. ยืนยันว่า parameter คำนวณได้ครบจากข้อมูลที่ function มีอยู่แล้ว ไม่ว่าจะเป็น parameter ตัวอื่น field หรือ query ที่เข้าถึงได้ ถ้าค่ายังขึ้นกับอะไรภายนอก ให้หยุดก่อน เพราะลบแล้วจะลักลอบพา dependency ที่ซ่อนอยู่เข้ามา
  2. แยกการคำนวณออกมาเป็น query ถ้ายังไม่ได้ทำ
  3. ข้างใน function แทนที่การใช้ parameter แต่ละจุดด้วยการเรียก query นั้น
  4. ลบ parameter ออกจาก signature แล้วลบการคำนวณที่ตอนนี้ตายแล้วออกจาก caller ทุกราย รัน test หลังแต่ละ caller

ใช้สิ่งนี้เมื่อ caller คำนวณ argument ในแบบเดียวกันทั้งหมด, เมื่อค่าถูกกำหนดทั้งหมดโดยข้อมูลที่ function เข้าถึงได้ หรือเมื่อคุณต้องการลบความเสี่ยงที่ caller จะส่งค่าที่ไม่สอดคล้องกัน parameter ที่น้อยลงหมายถึง interface ที่เรียบง่ายและใช้ผิดได้ยากขึ้น

ท่ากลับกันคือ Replace Query with Parameter บางครั้งการคำนวณค่า ข้างใน function ก็สร้าง dependency ที่ซ่อนอยู่ เช่น function เอื้อมไปหา global state นาฬิกา หรือ singleton ซึ่งทำให้ทดสอบยากและตามเหตุผลยาก กรณีนั้นให้ทำกลับด้าน คือยกการคำนวณออกมา รับค่าเป็น parameter แล้วให้ caller เป็นคนจัดหาให้ เท่ากับแลก signature ที่เพรียวกับ dependency ที่ชัดเจนและ inject ได้ เลือกเอาว่าตรงนั้นคุณให้ค่ากับอะไรมากกว่า ระหว่าง interface ที่เล็ก กับ function ที่ไม่มีการเอื้อมแอบ ๆ

ใช้ Replace Parameter with Query เมื่อหลีกเลี่ยงเมื่อ
ทุก caller คำนวณ argument แบบเดียวกันการคำนวณนั้นเข้าถึง global state หรือ clock
ค่านั้นกำหนดได้ทั้งหมดจากข้อมูลที่ function มีอยู่caller แต่ละรายมี logic ต่างกัน
ต้องการลดโอกาสที่ caller จะส่งค่าไม่สอดคล้องต้องการ inject dependency ผ่าน parameter เพื่อ test

⚠️ ไม่ควร Replace Parameter with Query เมื่อ:

  • การคำนวณข้างใน function ซ่อน dependency (global, clock, DB) — ทำให้ test ยาก
  • บางครั้งต้องการ override ค่าที่คำนวณได้ — เช่น test ที่ inject ค่าเฉพาะ
  • parameter นั้นคือ seam ที่ทำให้เขียน unit test ได้ — อย่าลบ
เมื่อไรที่ parameter เป็นตัวเลือกที่ดีสำหรับ Replace Parameter with Query?
การลบ parameter แบบนั้นออกกำจัดความเสี่ยงอะไร?
refactoring ท่ากลับกันคืออะไร และใช้ทำไม?
เมื่อไรที่คุณไม่ควรแทนที่ parameter ด้วย query?