Replace Temp with Query
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”เอาตัวแปร local ที่รับค่าจากการคำนวณครั้งเดียวแล้วอ่านอย่างเดียว มาแทนด้วย function ที่คำนวณค่านั้นให้ (เรียกกันว่า “query”) ทุกจุดที่เคยใช้ temp ก็เปลี่ยนไปเรียก query แทน
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”นี่คือวิธีรักษาอาการ temp ตรึง logic ไว้กับที่ temporary variable ล็อกการคำนวณไว้ใน function เดียว คุณเอาผลลัพธ์ไปใช้ซ้ำที่อื่นไม่ได้ และจะ Extract Function ครอบ code ที่พึ่ง temp นั้นก็ไม่ได้ ถ้าไม่ลาก temp ตามไปเป็น parameter การเปลี่ยน temp ให้เป็น query ปลดล็อกการคำนวณนั้นออกมา ท่านี้มีค่าที่สุดในฐานะขั้น เตรียมพร้อม พอค่ากลายเป็น method แล้ว การ extract ก้อนใหญ่กว่าก็หลุดออกมาได้สะอาด
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”basePrice เป็น temp ที่คำนวณครั้งเดียวแล้วอ่านสองครั้ง เลื่อนขั้นขึ้นเป็น query method บน order ไปเลย
// Beforeclass Order { constructor(private quantity: number, private itemPrice: number) {}
finalPrice(): number { const basePrice = this.quantity * this.itemPrice; const discount = Math.max(this.quantity - 100, 0) * this.itemPrice * 0.1; return basePrice - discount; }}
// Afterclass Order { constructor(private quantity: number, private itemPrice: number) {}
private basePrice(): number { return this.quantity * this.itemPrice; }
private discount(): number { return Math.max(this.quantity - 100, 0) * this.itemPrice * 0.1; }
finalPrice(): number { return this.basePrice() - this.discount(); }}# Beforeclass Order: def __init__(self, quantity, item_price): self.quantity = quantity self.item_price = item_price
def final_price(self): base_price = self.quantity * self.item_price discount = max(self.quantity - 100, 0) * self.item_price * 0.1 return base_price - discount
# Afterclass Order: def __init__(self, quantity, item_price): self.quantity = quantity self.item_price = item_price
def base_price(self): return self.quantity * self.item_price
def discount(self): return max(self.quantity - 100, 0) * self.item_price * 0.1
def final_price(self): return self.base_price() - self.discount()// Beforetype Order struct { Quantity int ItemPrice float64}
func (o Order) FinalPrice() float64 { basePrice := float64(o.Quantity) * o.ItemPrice discount := math.Max(float64(o.Quantity-100), 0) * o.ItemPrice * 0.1 return basePrice - discount}
// Aftertype Order struct { Quantity int ItemPrice float64}
func (o Order) basePrice() float64 { return float64(o.Quantity) * o.ItemPrice}
func (o Order) discount() float64 { return math.Max(float64(o.Quantity-100), 0) * o.ItemPrice * 0.1}
func (o Order) FinalPrice() float64 { return o.basePrice() - o.discount()}// Beforestruct Order { quantity: i32, item_price: f64,}
impl Order { fn final_price(&self) -> f64 { let base_price = self.quantity as f64 * self.item_price; let discount = (self.quantity - 100).max(0) as f64 * self.item_price * 0.1; base_price - discount }}
// Afterstruct Order { quantity: i32, item_price: f64,}
impl Order { fn base_price(&self) -> f64 { self.quantity as f64 * self.item_price }
fn discount(&self) -> f64 { (self.quantity - 100).max(0) as f64 * self.item_price * 0.1 }
fn final_price(&self) -> f64 { self.base_price() - self.discount() }}กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- เช็คว่า temp กำหนดค่าครั้งเดียวและไม่โดน mutate หลังจากนั้น ถ้ามีการกำหนดค่าใหม่ ให้แยกตัวแปรก่อน แต่ละค่าจะได้กำหนดที่เดียว
- ยืนยันว่าการคำนวณฝั่งขวาไม่มี side effect
- extract การคำนวณออกมาเป็น query function (ถ้าข้อมูลอยู่บน object ก็ทำเป็น method)
- แทนทุกจุดที่อ่าน temp ด้วยการเรียก query
- รัน test หลังแทนแต่ละจุด
- ลบบรรทัดประกาศ temp ที่ไม่มีใครใช้แล้วทิ้ง
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”ใช้ท่านี้เมื่อ temp ขวาง Extract Function ที่คุณอยากทำ เมื่อการคำนวณเดียวกันมีประโยชน์กับหลาย method หรือแค่อยากลด local state ของ method ยาว ๆ ข้อแลกเปลี่ยนหลักคือการคำนวณซ้ำ เพราะ query อาจคำนวณใหม่ทุกครั้งที่เรียก สำหรับ business logic ทั่วไปแทบไม่มีผล และความชัดเจนที่ได้ก็คุ้ม ส่วน hot path จริง ๆ ให้วัดก่อนตัดสินใจ
ทิศทางกลับกันคือ cache ค่าไว้ใน local อีกครั้ง เมื่อ profiling พิสูจน์แล้วว่า query เป็นคอขวดจริง แต่หยิบทางนี้มาใช้ก็ต่อเมื่อมีหลักฐานเท่านั้น
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Replace Temp with Query เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| temporary variable ใช้หลายครั้งใน function | query แพง — เรียกซ้ำหลายครั้งแล้วช้า |
| ต้องการ extract function ที่ใช้ค่านั้น | temporary variable คือ accumulator ที่ค่าเปลี่ยนใน loop |
| ค่านั้นต้องการใน subclass | function ที่ได้จะมีชื่อที่อธิบายแย่กว่า variable เดิม |
⚠️ ไม่ควร Replace Temp with Query เมื่อ:
- calculation มี side effect — เรียกซ้ำจะทำงานหลายครั้ง
- ค่านั้นซับซ้อนและเรียกบ่อย — cache ไว้ใน variable ดีกว่า
- context object ไม่มีข้อมูลที่จำเป็นสำหรับ query