Substitute Algorithm
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”function บางตัวอ่านยากไม่ใช่เพราะยาว แต่เพราะ วิธีการ ยุ่งเหยิงเกินจำเป็น เช่น เขียน loop เองทั้งที่ lookup ก็จบ หรือต่อเงื่อนไขเป็นสายทั้งที่ standard library มีให้อยู่แล้ว Substitute Algorithm สลับ body ทั้งก้อนเป็นแบบที่เรียบง่ายกว่าและให้ผลลัพธ์เหมือนกัน ต่างจากท่า composing ตัวเล็ก ๆ ตรงที่ท่านี้เปลี่ยน กลยุทธ์ ไม่ใช่แค่ถ้อยคำ จึงต้องพึ่ง test หนักมากเพื่อพิสูจน์ว่าทั้งสองวิธีให้ผลตรงกัน
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”function ทำงานได้อยู่ แต่พออ่านแล้วคุณต้องรันในหัวทีละขั้นเพื่อให้มั่นใจว่าถูก logic หนักกว่าที่ปัญหาควรจะเป็น เช่น ไล่ค้นใน list ด้วยมือทั้งที่เช็คสมาชิกใน set ก็ตอบได้ หรือต่อ if เป็นทอด ๆ ทั้งที่สรุปได้ว่า “ค่านี้อยู่ในไม่กี่ค่านี้หรือเปล่า” เมื่อคุณอธิบายงานของ function ได้ด้วยประโยคเดียว แต่ code กินไปทั้งย่อหน้า แปลว่ามี algorithm ที่ชัดกว่ารออยู่แน่นอน
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”function ตัดสินใจว่ารหัสประเทศได้รับการรองรับหรือไม่ด้วยการวน loop และพลิก flag ตัวแทนใช้การตรวจสอบสมาชิกโดยตรงกับ set ที่กำหนดไว้ตายตัว — คำตอบเดียวกัน อ่านน้อยกว่ามาก
// Beforefunction isSupported(code: string): boolean { const supported = ['US', 'CA', 'GB', 'DE']; let found = false; for (const c of supported) { if (c === code) { found = true; } } return found;}
// Afterconst SUPPORTED = new Set(['US', 'CA', 'GB', 'DE']);
function isSupported(code: string): boolean { return SUPPORTED.has(code);}# Beforedef is_supported(code): supported = ["US", "CA", "GB", "DE"] found = False for c in supported: if c == code: found = True return found
# AfterSUPPORTED = {"US", "CA", "GB", "DE"}
def is_supported(code): return code in SUPPORTED// Beforefunc IsSupported(code string) bool { supported := []string{"US", "CA", "GB", "DE"} found := false for _, c := range supported { if c == code { found = true } } return found}
// Aftervar supported = map[string]bool{"US": true, "CA": true, "GB": true, "DE": true}
func IsSupported(code string) bool { return supported[code]}// Beforefn is_supported(code: &str) -> bool { let supported = ["US", "CA", "GB", "DE"]; let mut found = false; for c in supported { if c == code { found = true; } } found}
// Afterfn is_supported(code: &str) -> bool { matches!(code, "US" | "CA" | "GB" | "DE")}กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- ทำให้แน่ใจว่า function ที่คุณกำลังจะแทนที่ถูกครอบคลุมด้วย test อย่างเต็มที่ รวมถึง edge case — input ว่าง, ไม่พบ match, ค่าที่ขอบเขต ถ้าความครอบคลุมยังบาง ให้เพิ่ม test ก่อน และยืนยันว่าผ่านเมื่อทดสอบกับ code ที่มีอยู่
- คิด algorithm ตัวใหม่ให้ครบแล้วเขียนออกมา ทางที่ดีคือเขียนเป็น function แยกวางไว้ข้างตัวเดิม
- เปรียบเทียบ behavior ทั้งสอง สำหรับ function ที่มี input space เล็กและนับได้ ให้รันทั้งสองอันกับทุก input แล้ว assert ว่าให้ผลตรงกัน มิฉะนั้นพึ่งพาชุด test บวกกับ case เฉพาะเจาะจงไม่กี่กรณี
- สลับ body เก่าเป็นอันใหม่
- รัน test ทุก case ต้องยังคงผ่าน
- ลบ algorithm เก่าและ helper ที่มีไว้รองรับทิ้ง เมื่อไม่เหลืออะไรอ้างถึงแล้ว
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”Substitute Algorithm คุ้มค่าเมื่อมีวิธีที่ชัดเจนกว่าหรือเป็นมาตรฐานที่ให้ผลลัพธ์เดียวกัน — แทนที่ loop ที่ทำเองด้วยการเรียก library หรือเงื่อนไขที่แผ่ขยายด้วยการ lookup จากตาราง เวอร์ชันใหม่อ่านง่ายกว่าและมักจะขยายต่อได้ง่ายกว่า
ความเสี่ยงสูงกว่าการเคลื่อนไหวที่เล็กกว่าเพราะคุณกำลังเปลี่ยนกลยุทธ์ทั้งหมด ดังนั้น behavior ที่แยบยลอาจแตกต่างกัน — ลำดับการตัดสินเสมอที่ต่างกัน การจัดการค่าซ้ำหรือ input ว่างที่เปลี่ยนไป จงลองทำเฉพาะเมื่อมี test ที่หนักแน่น และเลือกที่จะแยก function ยักษ์ที่ซับซ้อนยุ่งเหยิงออกเป็นชิ้นเล็ก ๆ ก่อน ที่จะแทนที่ เพื่อให้การแทนที่แต่ละครั้งเล็กและตรวจสอบได้
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Substitute Algorithm เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| algorithm ใหม่ชัดเจน ทดสอบได้ และมี test รองรับ | algorithm เดิมซับซ้อนและยังไม่มี test ครอบคลุม |
| library มี built-in ที่ทำสิ่งเดียวกันได้ดีกว่า | กำลังเปลี่ยนเพราะ “ดูดีกว่า” ไม่ใช่เพราะแก้ปัญหาจริง |
| algorithm เดิม bug-prone และเข้าใจยาก | algorithm ใหม่ยังไม่ถูก benchmark ว่า performance เทียบเท่า |
⚠️ ไม่ควร Substitute Algorithm เมื่อ:
- ยังไม่มี test ที่ครอบคลุม behavior เดิมทั้งหมด
- algorithm ใหม่ยังไม่ได้รับการพิสูจน์ว่าถูกต้องใน edge case ทั้งหมด
- กำลังเปลี่ยนเพื่อ micro-optimization โดยไม่มี profiling ยืนยัน