Parameterize Function
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”มองหา function ตั้งแต่สองตัวขึ้นไปที่ body เหมือนกันหมด ต่างกันแค่ค่า hard-code ค่าเดียว แล้วยุบให้เหลือ function เดียวที่รับค่านั้นเป็น parameter จากนั้น logic ที่เคยซ้ำจะอยู่ที่เดียวเป๊ะ ๆ และความต่างระหว่างเวอร์ชันเดิมก็กลายเป็น argument ที่มองเห็นได้ตรงจุดที่เรียก
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”นี่คือ Duplicated Code อีกรูปแบบหนึ่ง คุณจะเห็น discountFivePercent กับ discountTenPercent นั่งติดกัน เหมือนกันทุกอย่างยกเว้นตัวเลข หรือ tierBronze กับ tierSilver ที่ต่างกันแค่ค่า threshold ทุกครั้งที่ต้องแก้ logic ส่วนที่ใช้ร่วมกัน คุณต้องจำเองว่าต้องไล่แก้ทุก clone แล้ววันหนึ่งก็จะลืมไปสักตัว ในเมื่อค่า hard-code คือสิ่งเดียวที่ต่างกัน ค่านั้นก็ควรเป็น argument ไม่ใช่เหตุผลให้คัดลอกทั้ง function
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”function สองตัวที่คำนวณราคาหลังหักส่วนลด ต่างกันแค่อัตรา หลังจากนั้น function เดียวรับอัตราเป็น parameter
// Beforefunction priceWithFivePercentOff(price: number): number { return price - price * 0.05;}
function priceWithTenPercentOff(price: number): number { return price - price * 0.10;}
const a = priceWithFivePercentOff(200);const b = priceWithTenPercentOff(200);
// Afterfunction discountedPrice(price: number, rate: number): number { return price - price * rate;}
const a = discountedPrice(200, 0.05);const b = discountedPrice(200, 0.10);# Beforedef price_with_five_percent_off(price): return price - price * 0.05
def price_with_ten_percent_off(price): return price - price * 0.10
a = price_with_five_percent_off(200)b = price_with_ten_percent_off(200)
# Afterdef discounted_price(price, rate): return price - price * rate
a = discounted_price(200, 0.05)b = discounted_price(200, 0.10)// Beforefunc PriceWithFivePercentOff(price float64) float64 { return price - price*0.05}
func PriceWithTenPercentOff(price float64) float64 { return price - price*0.10}
a := PriceWithFivePercentOff(200)b := PriceWithTenPercentOff(200)
// Afterfunc DiscountedPrice(price, rate float64) float64 { return price - price*rate}
a := DiscountedPrice(200, 0.05)b := DiscountedPrice(200, 0.10)// Beforefn price_with_five_percent_off(price: f64) -> f64 { price - price * 0.05}
fn price_with_ten_percent_off(price: f64) -> f64 { price - price * 0.10}
let a = price_with_five_percent_off(200.0);let b = price_with_ten_percent_off(200.0);
// Afterfn discounted_price(price: f64, rate: f64) -> f64 { price - price * rate}
let a = discounted_price(200.0, 0.05);let b = discounted_price(200.0, 0.10);กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- เลือก function ที่เกือบเหมือนกันตัวหนึ่งมาเป็นแม่แบบ
- เพิ่ม parameter สำหรับค่าที่แปรเปลี่ยน ตั้งชื่อให้สื่อความหมายอย่าง
rateหรือthresholdไม่ใช่value - ใน body ให้เปลี่ยนค่า hard-code เป็น parameter ตัวใหม่ แล้ว รัน test ถ้าส่งค่าคงที่เดิมเข้าไป behavior ต้องไม่เปลี่ยน
- สำหรับ clone ที่เหลือแต่ละตัว ให้เปลี่ยนเส้นทาง caller มาที่ function ที่ parameterize แล้ว โดยส่ง literal เฉพาะของ clone นั้นเข้าไปเป็น argument
- รัน test หลังเปลี่ยนเส้นทางแต่ละ clone เพื่อให้ความล้มเหลวชี้ไปยังการเรียกใช้จุดเดียว
- เมื่อไม่เหลือใครเรียก clone เดิมแล้ว ก็ลบทิ้งได้
- พิจารณาว่า parameter ใหม่ควรถูกตรวจสอบความถูกต้องหรือไม่ — ตัวอย่างเช่น อัตราส่วนลดที่อยู่นอกช่วงศูนย์ถึงหนึ่งน่าจะเป็นบั๊กที่ควรปฏิเสธ
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”หยิบท่านี้มาใช้เมื่อ function ต่างกันแค่ค่าคงที่ เมื่อคุณกำลังจะ copy-paste function แล้วปรับตัวเลขตัวเดียว หรือเมื่อชุดของตัวแปรที่มีชื่อต่าง ๆ เพิ่มขึ้นเรื่อย ๆ การ parameterize ฆ่าความซ้ำซ้อนและทำให้มิติที่แปรเปลี่ยนชัดเจนและค้นพบได้
ข้อแลกเปลี่ยนคือ literal ที่โผล่ตรงจุดเรียกอาจอธิบายตัวเองได้น้อยกว่าชื่อ function เดิมที่ตั้งมาดี ๆ เพราะ 0.05 โดด ๆ บอกอะไรน้อยกว่า priceWithFivePercentOff ถ้าชุดค่าที่เป็นไปได้มีไม่กี่ตัวและสื่อความหมาย ก็ใช้ค่าคงที่ที่มีชื่อหรือ enum ตรงจุดเรียกเพื่อดึงความชัดเจนกลับมา และอย่า parameterize เพลินเกินไป เพราะ function ที่มีปุ่มปรับแปดปุ่มใช้ยากกว่า clone ที่มาแทนที่เสียอีก
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Parameterize Function เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| function สองตัวต่างกันแค่ literal เดียว | ความแตกต่างคือ behavior ไม่ใช่ data |
| มีตัวแปรใน namespace เพิ่มขึ้นเรื่อย ๆ | parameter ใหม่ทำให้ signature อ่านยากขึ้น |
| จะ copy-paste function แล้วเปลี่ยนตัวเลขเดียว | function ที่ได้มี parameter เกิน 4 ตัว |
⚠️ ไม่ควร Parameterize Function เมื่อ:
- ความแตกต่างระหว่างสอง function คือ behavior — ให้ Remove Flag Argument แทน
- ค่าที่จะเป็น parameter มีชุดจำกัดและมีความหมาย — ใช้ enum หรือแยก function แทน
- เพิ่ม parameter เพราะ “อาจจะต้องการ” ในอนาคต — YAGNI