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

Replace Magic Literal

มองหาค่าเปลือย ๆ อย่างตัวเลข 9.80665 หรือ 86400 หรือ string อย่าง "ADMIN" ที่แบกความหมายซึ่งผู้อ่านต้องเดาเอง แล้วแทนด้วยค่าคงที่ที่มีชื่อ ชื่อจะอธิบายว่า ค่านี้มีไว้ทำอะไร และนิยามจุดเดียวนั้นก็กลายเป็นจุดเดียวที่ต้องแก้ในวันที่ค่าต้องเปลี่ยน

นี่รักษาอาการ Magic Number (และพี่น้องคือ magic string) สัญญาณบ่งบอกคือค่าคงที่ที่ความหมายไม่ชัดเจนจากบริบท โดยเฉพาะอย่างยิ่งอันที่ปรากฏในมากกว่าหนึ่งที่ เมื่อ 0.07 โผล่ในโมดูลคิดราคา และโผล่อีกครั้งในโมดูลใบแจ้งหนี้ ปัญหาสองอย่างซ่อนอยู่: ผู้อ่านไม่สามารถรู้ได้ว่าทั้งสองหมายถึง “อัตราภาษีขาย” และในวันที่อัตรานั้นเปลี่ยน คุณต้องไล่หาและแก้ไขทุกสำเนา — และคุณ จะ พลาดไปสักอันแน่นอน

การคำนวณค่าจัดส่งที่เต็มไปด้วยตัวเลขที่ไม่มีคำอธิบาย หลังจากนั้น ค่าคงที่แต่ละตัวเป็นค่าคงที่ที่มีชื่อซึ่งชื่อแบกเจตนาเอาไว้

// Before
function shippingCost(weightKg: number): number {
if (weightKg > 30) {
return weightKg * 2.5 + 15;
}
return weightKg * 2.5;
}
// After
const 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;
}
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
ค่าคงที่เปลือย ๆ กลายเป็นค่าคงที่ที่มีชื่อซึ่งใช้ที่จุดเรียกแต่ละแห่ง
  1. ประกาศค่าคงที่แล้วกำหนดค่าดิบเข้าไป ตั้งชื่อที่บอกว่าค่านั้น หมายถึง อะไร ไม่ใช่บอกว่าเป็นเลขอะไร คือ HEAVY_THRESHOLD_KG ไม่ใช่ THIRTY
  2. หาการใช้งานค่าคงที่นั้นหนึ่งจุด ยืนยันว่าหมายถึงสิ่งเดียวกันกับค่าคงที่จริง ๆ — เลข 2 สองตัวที่ไม่เกี่ยวข้องกันไม่ใช่ magic number ตัวเดียวกัน
  3. แทนที่การใช้งานนั้นด้วยค่าคงที่
  4. รัน test
  5. ทำซ้ำกับการใช้งานที่เหลือแต่ละจุดที่มีความหมายร่วมกัน
  6. เมื่อทุกการใช้งานที่ตรงกันถูกแทนที่แล้ว ค่าคงที่นั้นจะอยู่ในจุดเดียวเป๊ะ การเปลี่ยนค่าตอนนี้เป็นการแก้ไขเพียงบรรทัดเดียว

แทนที่ค่าคงที่เมื่อใดก็ตามที่ความหมายไม่ชัดเจนในตัวเอง และโดยเฉพาะอย่างยิ่งเมื่อใดก็ตามที่ค่าที่มีความหมายเดียวกันปรากฏมากกว่าหนึ่งครั้ง ค่าคงที่ที่มีชื่อทำหน้าที่เป็นทั้งเอกสารและจุดเปลี่ยนแปลงเพียงจุดเดียว

มีข้อควรระวังสองข้อ ข้อแรก อย่าไปแทนค่าที่อธิบายตัวเองได้อยู่แล้ว เช่น 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 ทุกครั้ง
Replace Magic Literal แก้ปัญหาอะไร?
ควรตั้งชื่อค่าคงที่ตามอะไร?
ค่าคงที่สองตัวบังเอิญมีค่าเป็น 100 เหมือนกัน แต่หมายถึงสิ่งที่ต่างกัน คุณควรทำอย่างไร?
ค่าคงที่ใดเป็นตัวเลือกที่ดีที่สุดที่จะปล่อยไว้โดยไม่ตั้งชื่อ?