Replace Magic Literal
จุดประสงค์
หัวข้อที่มีชื่อว่า “จุดประสงค์”มองหาค่าเปลือย ๆ อย่างตัวเลข 9.80665 หรือ 86400 หรือ string อย่าง "ADMIN" ที่แบกความหมายซึ่งผู้อ่านต้องเดาเอง แล้วแทนด้วยค่าคงที่ที่มีชื่อ ชื่อจะอธิบายว่า ค่านี้มีไว้ทำอะไร และนิยามจุดเดียวนั้นก็กลายเป็นจุดเดียวที่ต้องแก้ในวันที่ค่าต้องเปลี่ยน
Code Smell
หัวข้อที่มีชื่อว่า “Code Smell”นี่รักษาอาการ Magic Number (และพี่น้องคือ magic string) สัญญาณบ่งบอกคือค่าคงที่ที่ความหมายไม่ชัดเจนจากบริบท โดยเฉพาะอย่างยิ่งอันที่ปรากฏในมากกว่าหนึ่งที่ เมื่อ 0.07 โผล่ในโมดูลคิดราคา และโผล่อีกครั้งในโมดูลใบแจ้งหนี้ ปัญหาสองอย่างซ่อนอยู่: ผู้อ่านไม่สามารถรู้ได้ว่าทั้งสองหมายถึง “อัตราภาษีขาย” และในวันที่อัตรานั้นเปลี่ยน คุณต้องไล่หาและแก้ไขทุกสำเนา — และคุณ จะ พลาดไปสักอันแน่นอน
ก่อน → หลัง
หัวข้อที่มีชื่อว่า “ก่อน → หลัง”การคำนวณค่าจัดส่งที่เต็มไปด้วยตัวเลขที่ไม่มีคำอธิบาย หลังจากนั้น ค่าคงที่แต่ละตัวเป็นค่าคงที่ที่มีชื่อซึ่งชื่อแบกเจตนาเอาไว้
// Beforefunction shippingCost(weightKg: number): number { if (weightKg > 30) { return weightKg * 2.5 + 15; } return weightKg * 2.5;}
// Afterconst RATE_PER_KG = 2.5;const HEAVY_THRESHOLD_KG = 30;const HEAVY_SURCHARGE = 15;
function shippingCost(weightKg: number): number { const base = weightKg * RATE_PER_KG; return weightKg > HEAVY_THRESHOLD_KG ? base + HEAVY_SURCHARGE : base;}# Beforedef shipping_cost(weight_kg): if weight_kg > 30: return weight_kg * 2.5 + 15 return weight_kg * 2.5
# AfterRATE_PER_KG = 2.5HEAVY_THRESHOLD_KG = 30HEAVY_SURCHARGE = 15
def shipping_cost(weight_kg): base = weight_kg * RATE_PER_KG return base + HEAVY_SURCHARGE if weight_kg > HEAVY_THRESHOLD_KG else base// Beforefunc ShippingCost(weightKg float64) float64 { if weightKg > 30 { return weightKg*2.5 + 15 } return weightKg * 2.5}
// Afterconst ( ratePerKg = 2.5 heavyThresholdKg = 30.0 heavySurcharge = 15.0)
func ShippingCost(weightKg float64) float64 { base := weightKg * ratePerKg if weightKg > heavyThresholdKg { return base + heavySurcharge } return base}// Beforefn shipping_cost(weight_kg: f64) -> f64 { if weight_kg > 30.0 { weight_kg * 2.5 + 15.0 } else { weight_kg * 2.5 }}
// Afterconst RATE_PER_KG: f64 = 2.5;const HEAVY_THRESHOLD_KG: f64 = 30.0;const HEAVY_SURCHARGE: f64 = 15.0;
fn shipping_cost(weight_kg: f64) -> f64 { let base = weight_kg * RATE_PER_KG; if weight_kg > HEAVY_THRESHOLD_KG { base + HEAVY_SURCHARGE } else { base }}flowchart LR
subgraph Before["Before"]
A["code: weight * 9.80665"]
B["code: if status == 2"]
end
subgraph After["After"]
C["GRAVITY = 9.80665"]
D["STATUS_SHIPPED = 2"]
C --> E["code: weight * GRAVITY"]
D --> F["code: if status == STATUS_SHIPPED"]
end
Before -.->|"Replace Magic Literal"| After กลไกการทำงาน
หัวข้อที่มีชื่อว่า “กลไกการทำงาน”- ประกาศค่าคงที่แล้วกำหนดค่าดิบเข้าไป ตั้งชื่อที่บอกว่าค่านั้น หมายถึง อะไร ไม่ใช่บอกว่าเป็นเลขอะไร คือ
HEAVY_THRESHOLD_KGไม่ใช่THIRTY - หาการใช้งานค่าคงที่นั้นหนึ่งจุด ยืนยันว่าหมายถึงสิ่งเดียวกันกับค่าคงที่จริง ๆ — เลข
2สองตัวที่ไม่เกี่ยวข้องกันไม่ใช่ magic number ตัวเดียวกัน - แทนที่การใช้งานนั้นด้วยค่าคงที่
- รัน test
- ทำซ้ำกับการใช้งานที่เหลือแต่ละจุดที่มีความหมายร่วมกัน
- เมื่อทุกการใช้งานที่ตรงกันถูกแทนที่แล้ว ค่าคงที่นั้นจะอยู่ในจุดเดียวเป๊ะ การเปลี่ยนค่าตอนนี้เป็นการแก้ไขเพียงบรรทัดเดียว
ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ใช้เมื่อไหร่ / ข้อแลกเปลี่ยน”แทนที่ค่าคงที่เมื่อใดก็ตามที่ความหมายไม่ชัดเจนในตัวเอง และโดยเฉพาะอย่างยิ่งเมื่อใดก็ตามที่ค่าที่มีความหมายเดียวกันปรากฏมากกว่าหนึ่งครั้ง ค่าคงที่ที่มีชื่อทำหน้าที่เป็นทั้งเอกสารและจุดเปลี่ยนแปลงเพียงจุดเดียว
มีข้อควรระวังสองข้อ ข้อแรก อย่าไปแทนค่าที่อธิบายตัวเองได้อยู่แล้ว เช่น index + 1 หรือการคูณ 2 เพื่อทำให้เป็นสองเท่า ไม่ต้องมีค่าคงที่ เพราะชื่ออย่าง ONE มีแต่จะรกเปล่า ๆ ข้อสอง ระวัง ความบังเอิญที่ค่าเท่ากัน ถ้าเลข 100 สองตัวหมายถึง “สเกลเปอร์เซ็นต์” กับ “จำนวน retry สูงสุด” ต้องแยกเป็นค่าคงที่สองตัวเด็ดขาด ห้ามใช้ชื่อร่วมกัน ไม่อย่างนั้นวันหนึ่งการแก้ที่จุดหนึ่งจะไปทำลายอีกจุดแบบเงียบ ๆ
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| ใช้ Replace Magic Literal เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| ตัวเลข/string ปรากฏในหลายที่และต้องเปลี่ยนพร้อมกัน | ค่านั้นปรากฏครั้งเดียวและชัดเจนในบริบท (array[0]) |
ความหมายของค่าไม่ชัดโดยไม่มีบริบท (0.07) | constant ชื่อที่ตั้งได้ก็ไม่ได้ชัดกว่าค่าเดิม |
| test hardcode ค่าเดียวกับ production code | ค่านั้นเป็น implementation detail ที่ไม่ควร expose |
⚠️ ไม่ควร Replace Magic Literal เมื่อ:
0,1,-1,"",nullที่มีความหมายในบริบทชัดเจน- constant ที่ชื่อต้องการจะยาวกว่าค่าเดิมและไม่ได้ช่วยอะไร
- สร้าง constant เยอะเกินไปจนต้องไปหาที่ definition ทุกครั้ง