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

Move Statements to Callers

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

คุณสังเกตว่าตัวเองกำลังจะเอื้อมไปหา boolean parameter — render(person, includeFooter) — หรือหวังว่า caller รายหนึ่งจะข้าม function ก้อนหนึ่งได้ ความคันนั้นหมายความว่า function กำลังมัดขั้นตอนที่ไม่ใช่สากลเข้าด้วยกัน ส่วนที่ใช้ร่วมกันและส่วนที่เฉพาะแต่ละ caller ถูกเชื่อมติดกัน นี่คือสิ่งตรงข้ามเป๊ะ ๆ ของ Move Statements into Function: ที่นั่น คำสั่งที่เหมือนกันอยากเข้ามา ที่นี่ คำสั่งที่แตกต่างกันอยากออกไป

renderPerson ที่ปล่อยบล็อกรูปภาพไว้ท้ายเสมอ caller รายใหม่ต้องการ heading แต่ต้องการ footer ที่ ต่างออกไป แทนที่จะเพิ่ม flag เราย้ายบล็อกรูปภาพออกไปยัง caller

// Before — one caller wants a different ending, tempting a flag
function renderPerson(person: Person): string {
return [
`<p>${person.name}</p>`,
emitPhotoData(person.photo),
].join('\n');
}
const listing = people.map(renderPerson).join('\n');
// After — shared core stays, the varying tail moves to callers
function renderPerson(person: Person): string {
return `<p>${person.name}</p>`;
}
const listing = people
.map((p) => [renderPerson(p), emitPhotoData(p.photo)].join('\n'))
.join('\n');
// a different caller can now end differently
const compact = people.map(renderPerson).join('\n');
  1. ระบุคำสั่งที่ควรแปรผันไปตาม caller — มักเป็นส่วนนำหรือส่วนท้ายของ function ไม่ใช่ตรงกลาง
  2. ถ้ามี caller เพียงหนึ่งหรือสองราย ให้ copy คำสั่งเข้าไปยัง caller แต่ละรายโดยตรง ติดกับการเรียก
  3. เมื่อมี caller จำนวนมาก ให้ทำอย่างปลอดภัย: extract ตัว (ที่แชร์กัน) ส่วนที่เหลือ ออกเป็น function ใหม่ ปล่อยให้ function เดิมเรียก function ใหม่นั้นบวกกับคำสั่งที่แปรผัน แล้วชี้ caller ไปยัง function ที่แชร์กันใหม่ทีละราย
  4. รัน test หลังต่อสาย caller แต่ละราย
  5. เมื่อ caller ทุกตัวมีสำเนา statement ที่ย้ายมาเป็นของตัวเองแล้ว ให้ลบออกจาก function เดิม
  6. เปลี่ยนชื่อ function ที่แชร์กันถ้าความรับผิดชอบที่แคบลงสมควรได้ชื่อที่ชัดเจนกว่า

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

ต้นทุนคือความซ้ำบางส่วนที่จุดเรียก เพราะตอนนี้ caller แต่ละตัวเขียน statement ที่ย้ายมาซ้ำกัน ซึ่งรับได้เมื่อ caller ต่างกันจริง ความซ้ำแบบนั้นถือว่าซื่อตรง แต่ถ้าสุดท้าย statement กลับเหมือนกันหมดทุกที่ แปลว่าเดินผิดทาง ให้ใช้ Move Statements into Function พับกลับเข้าไป

ใช้ Move Statements to Callers เมื่อหลีกเลี่ยงเมื่อ
caller บางตัวต้องการ behavior ที่แตกต่างกันทุก caller ต้องการ behavior เดียวกันทุกครั้ง
function กำลังทำมากกว่าหนึ่งหน้าที่การย้ายออกไปจะทำให้ caller ทุกตัวต้องเขียนซ้ำ
ต้องการแยก responsibility เพื่อ Extract Function ต่อstatement นั้นเป็น invariant ที่ function ควรรับผิดชอบ

⚠️ ไม่ควร Move Statements to Callers เมื่อ:

  • จะทำให้ Duplicate Code — caller ทุกตัวต้องเขียนเหมือนกัน
  • statement นั้นซับซ้อนและ caller ไม่ควรรู้รายละเอียด
  • ย้ายออกไปแล้ว function ที่เหลือไม่มีความหมายในตัวเอง
อะไรเป็นสัญญาณว่าคำสั่งควรย้ายไปยัง caller?
เมื่อมี caller จำนวนมาก วิธีที่ปลอดภัยในการย้ายคำสั่งออกคืออะไร?
Move Statements to Callers เป็นสิ่งตรงข้ามของการ refactor ใด?
อะไรคือ trade-off ที่ยอมรับได้ของการย้ายคำสั่งไปยัง caller?