Extract Variable
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”ดึงชิ้นส่วนหนึ่งของ expression ก้อนใหญ่มาเก็บไว้ในตัวแปร local ที่ตั้งชื่อดี ๆ แล้วใช้ชื่อนั้นแทน sub-expression เดิม อีกชื่อที่คุ้นหูคือ Introduce Explaining Variable
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”นี่คือวิธีรักษา cryptic one-liner คือ expression ที่แน่นจนต้องแกะในหัวใหม่ทุกครั้งที่อ่าน เงื่อนไข boolean ที่ถักจากการเปรียบเทียบสามชั้น หรือสูตรราคาที่เต็มไปด้วยเลขคณิตปริศนา ล้วนซ่อนความหมายไว้หลังเครื่องหมายวรรคตอน พอตั้งชื่อให้แต่ละส่วน บรรทัดนั้นก็กลายเป็นอะไรที่อ่านออกเสียงได้เลย
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”การตรวจเงื่อนไขจัดส่งอัดความหมายทั้งหมดไว้ใน boolean expression บรรทัดเดียว พอตั้งชื่อให้แต่ละส่วน กฎที่ซ่อนอยู่ก็โผล่ออกมา
// Beforefunction isEligibleForFreeShipping(order: Order): boolean { return order.total > 50 && order.country === 'TH' && !order.isGift;}
// Afterfunction isEligibleForFreeShipping(order: Order): boolean { const overThreshold = order.total > 50; const domestic = order.country === 'TH'; const giftExcluded = !order.isGift; return overThreshold && domestic && giftExcluded;}# Beforedef is_eligible_for_free_shipping(order): return order.total > 50 and order.country == "TH" and not order.is_gift
# Afterdef is_eligible_for_free_shipping(order): over_threshold = order.total > 50 domestic = order.country == "TH" gift_excluded = not order.is_gift return over_threshold and domestic and gift_excluded// Beforefunc IsEligibleForFreeShipping(order Order) bool { return order.Total > 50 && order.Country == "TH" && !order.IsGift}
// Afterfunc IsEligibleForFreeShipping(order Order) bool { overThreshold := order.Total > 50 domestic := order.Country == "TH" giftExcluded := !order.IsGift return overThreshold && domestic && giftExcluded}// Beforefn is_eligible_for_free_shipping(order: &Order) -> bool { order.total > 50.0 && order.country == "TH" && !order.is_gift}
// Afterfn is_eligible_for_free_shipping(order: &Order) -> bool { let over_threshold = order.total > 50.0; let domestic = order.country == "TH"; let gift_excluded = !order.is_gift; over_threshold && domestic && gift_excluded}กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- เช็กก่อนว่า sub-expression ไม่มี side effect เพราะการ extract ชิ้นที่ไปแก้ state อาจเปลี่ยน behavior ได้
- ประกาศตัวแปร local แบบ immutable แล้วกำหนดค่า sub-expression ลงไป
- ตั้งชื่อที่บอกว่าค่านั้น หมายถึงอะไร ไม่ใช่บอกว่าคำนวณมายังไง
- แทนที่ sub-expression เดิมด้วยตัวแปรใหม่
- รัน test
- ถ้า sub-expression เดียวกันโผล่หลายครั้งใน scope นั้น ให้แทนที่ให้ครบทุกจุดแล้ว test อีกรอบ
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”ใช้ตอน debug ได้ดี เพราะตัวแปร local ที่มีชื่อจะส่องดูค่าง่าย นอกจากนี้ยังช่วยหั่นสูตรยาว ๆ ออกเป็นชิ้นที่ review ไหว และทำให้เงื่อนไขอธิบายตัวเองได้ ถ้าใช้อยู่ใน function เดียว ตัวแปร local ก็พอแล้ว แต่ถ้าคำอธิบายชุดเดียวกันมีประโยชน์ทั้ง object ให้ไปทาง Replace Temp with Query หรือ extract เป็น method แทน
ฝั่งตรงข้ามคือ Inline Variable ถ้าชื่อตัวแปรไม่ได้เพิ่มอะไรจาก expression ที่เก็บอยู่ ก็เอา expression ใส่กลับเข้าไปแล้วลบตัวแปรทิ้ง
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Extract Variable เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| expression ซับซ้อนจนต้องอ่านหลายรอบ | ชื่อที่คิดได้ไม่ได้อธิบายดีกว่า expression เดิม |
condition มีหลาย operand — a && b && !c | ต้องการ expression นั้นใน function อื่น — Extract Function แทน |
| magic number ที่ไม่รู้ว่าหมายถึงอะไร | variable ใช้แค่ครั้งเดียวและ expression สั้น |
⚠️ ไม่ควร Extract Variable เมื่อ:
- expression ง่ายอยู่แล้ว —
x + 1ไม่ต้องการชื่อ- ต้องการใช้ logic เดิมในหลายที่ — Extract Function ดีกว่า
- กำลังสร้าง variable แบบ
tempหรือresultที่ไม่บอกอะไร