Slide Statements
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”code อ่านง่ายที่สุดเมื่อสิ่งที่เกี่ยวข้องกันอยู่ติดกัน Slide Statements ย้าย statement ขึ้นหรือลงเพื่อให้บรรทัดที่เกี่ยวข้องกันมาเป็นเพื่อนบ้าน ตัวแปรประกาศตรงจุดที่ใช้ครั้งแรก บรรทัด setup อยู่ติดกับการเรียกที่เตรียมไว้ให้ ส่วน behavior ไม่เปลี่ยน เปลี่ยนแค่ลำดับ ท่านี้มักเป็นก้าวแรกเงียบ ๆ ก่อน Extract Function คือเลื่อนชิ้นส่วนที่กระจัดกระจายมารวมเป็นบล็อกเดียวต่อเนื่องกัน แล้วยกทั้งบล็อกออกไปในทีเดียวแบบสะอาด ๆ
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”ตัวแปรถูกประกาศไว้ที่ด้านบนของ function แต่ไม่ถูกแตะต้องจนกระทั่งสามสิบบรรทัดถัดมา สองบรรทัดที่ทำงานเป็นคู่กันอย่างชัดเจนถูกแยกออกจากกันด้วย code ที่ไม่เกี่ยวข้อง เมื่อคุณพยายาม extract ชิ้นส่วนหนึ่ง คุณพบว่า object ดิบกระจายอยู่ทั่ว function ดังนั้นการ extract จะต้องใช้ parameter ที่เก้กังหรือจะลากเอาบรรทัดที่ไม่ควรอยู่ติดมาด้วย
วิธีรักษาคือเลื่อน statement ที่เกี่ยวข้องกันให้มาอยู่ด้วยกันก่อน เมื่ออยู่ติดกันแล้ว โครงสร้างของ function จะชัดเจน และ refactoring ขั้นต่อไปจะง่ายเป็นเรื่องเล็ก
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”มีการประกาศสองบรรทัดวางอยู่ด้านบน แต่แต่ละตัวถูกใช้จริงในภายหลังมาก เลื่อนแต่ละบรรทัดลงมาไว้เหนือจุดที่ใช้ครั้งแรก แต่ละค่าจะได้อยู่ติดกับงานที่ต้องใช้
// Beforefunction summarize(order: Order): string { const discount = order.basePrice * 0.1; const tax = order.basePrice * 0.07;
logAccess(order.customerId); const net = order.basePrice - discount;
recordAudit(order.id); const gross = net + tax; return `Net ${net}, Gross ${gross}`;}
// Afterfunction summarize(order: Order): string { logAccess(order.customerId); const discount = order.basePrice * 0.1; const net = order.basePrice - discount;
recordAudit(order.id); const tax = order.basePrice * 0.07; const gross = net + tax; return `Net ${net}, Gross ${gross}`;}# Beforedef summarize(order): discount = order.base_price * 0.1 tax = order.base_price * 0.07
log_access(order.customer_id) net = order.base_price - discount
record_audit(order.id) gross = net + tax return f"Net {net}, Gross {gross}"
# Afterdef summarize(order): log_access(order.customer_id) discount = order.base_price * 0.1 net = order.base_price - discount
record_audit(order.id) tax = order.base_price * 0.07 gross = net + tax return f"Net {net}, Gross {gross}"// Beforefunc Summarize(order Order) string { discount := order.BasePrice * 0.1 tax := order.BasePrice * 0.07
logAccess(order.CustomerID) net := order.BasePrice - discount
recordAudit(order.ID) gross := net + tax return fmt.Sprintf("Net %.2f, Gross %.2f", net, gross)}
// Afterfunc Summarize(order Order) string { logAccess(order.CustomerID) discount := order.BasePrice * 0.1 net := order.BasePrice - discount
recordAudit(order.ID) tax := order.BasePrice * 0.07 gross := net + tax return fmt.Sprintf("Net %.2f, Gross %.2f", net, gross)}// Beforefn summarize(order: &Order) -> String { let discount = order.base_price * 0.1; let tax = order.base_price * 0.07;
log_access(order.customer_id); let net = order.base_price - discount;
record_audit(order.id); let gross = net + tax; format!("Net {:.2}, Gross {:.2}", net, gross)}
// Afterfn summarize(order: &Order) -> String { log_access(order.customer_id); let discount = order.base_price * 0.1; let net = order.base_price - discount;
record_audit(order.id); let tax = order.base_price * 0.07; let gross = net + tax; format!("Net {:.2}, Gross {:.2}", net, gross)}กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- เลือก statement ที่คุณต้องการย้ายและช่องปลายทาง
- ตรวจ code ที่เลื่อนผ่านว่ามีการรบกวนกันหรือไม่ การเลื่อนจะปลอดภัยก็ต่อเมื่อลำดับไม่สำคัญระหว่างกัน: statement ที่ย้ายและทุก statement ที่ผ่านต้องไม่อ่านค่าที่อีกฝ่ายเขียน และทั้งคู่ต้องไม่มี side effect ที่อีกฝ่ายพึ่งพา
- ย้าย statement ไปยังตำแหน่งใหม่
- รัน test ถ้ามีอะไรพัง แปลว่าการเลื่อนนั้นข้าม dependency ของจริง ให้ย้ายกลับที่เดิมแล้วคิดใหม่
- ทำซ้ำสำหรับ statement ถัดไป โดยเลื่อนทีละอัน เพื่อให้ความล้มเหลวชี้ไปยังการย้ายเพียงครั้งเดียว
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”เลื่อน statement เพื่อวางการประกาศไว้ติดกับจุดที่ใช้ครั้งแรก เพื่อรวบชิ้นส่วนที่กำลังจะ extract ให้มาอยู่ด้วยกัน หรือเพียงเพื่อให้คู่ setup-กับ-action อ่านต่อกันได้ ท่านี้เสี่ยงต่ำแต่ให้ความชัดเจนสูง และเป็นการอุ่นเครื่องที่เหมาะมากก่อนลงมือ Extract Function
อันตรายจริงเพียงอย่างเดียวคือการพึ่งพาที่ซ่อนอยู่: บรรทัดที่ดูเหมือนเป็นอิสระแต่แอบพึ่งพา side effect ก่อนหน้า (flag แบบ global, state ที่เปลี่ยนแปลงได้ที่ใช้ร่วมกัน, ลำดับผ่าน I/O) test คือผู้คุ้มกัน เมื่อสงสัย ให้เลื่อนทีละก้าวเล็ก ๆ แล้วรัน test หลังการย้ายแต่ละครั้ง
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Slide Statements เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| ต้องการ Extract Function แต่ข้อมูลที่เกี่ยวข้องกระจายอยู่ | statement มี side effect ที่ขึ้นกับลำดับ |
| declaration ของ variable ห่างจากที่ใช้มาก | ย้ายข้าม statement ที่แก้ไข data เดียวกัน |
| code ที่เกี่ยวข้องกันควรอยู่ใกล้กัน | ทีมไม่มี test — การย้ายอาจเปลี่ยน behavior โดยไม่รู้ตัว |
⚠️ ไม่ควร Slide Statements เมื่อ:
- ไม่มี test ครอบคลุม — side effect ที่ซ่อนอยู่อาจเกิดขึ้น
- statement ที่จะย้ายข้ามมีการ read/write ตัวแปรเดียวกัน
- กำลังย้ายเพราะ “รู้สึก” ว่าดีกว่า โดยไม่มีเป้าหมาย refactoring ชัดเจน