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

Naming Things

ตั้ง​ชื่อ​ให้​สื่อ​เจตนา — งาน​ที่​ยาก​ที่สุด​อย่าง​หนึ่ง​ใน​การ​เขียน​โปรแกรม

Naming Things คือ​วินัย​ใน​การ​เลือก ชื่อ ให้​กับ​องค์​ประกอบ​ทุก​ระดับ​ใน source code — ตัวแปร parameter method class interface namespace ไป​จนถึง​ชื่อ file และ project — ให้​สื่อ​ความหมาย​และ​เจตนา​ได้​ชัดเจน​ที่สุด​เท่า​ที่​จะ​ทำได้ มัน​ไม่ใช่​แค่​เรื่อง​สไตล์​หรือ​รสนิยม​ส่วนตัว แต่​เป็น​เครื่องมือ​สื่อสาร​หลัก​ระหว่าง​นัก​พัฒนา​ด้วย​กัน เพราะ code ถูก “อ่าน” มากกว่า “เขียน” หลาย​เท่าตัว​ตลอด​อายุ​ของ​ระบบ

Phil Karlton เคย​กล่าว​ประโยค​ที่ Martin Fowler นำ​มา​เผยแพร่​ต่อ​จน​โด่งดัง​ไว้​ว่า

“ใน​วิทยาการ​คอมพิวเตอร์​มี​เรื่อง​ยาก​อยู่​แค่​สอง​เรื่อง: การ invalidate cache และ​การ​ตั้ง​ชื่อ​สิ่ง​ต่าง ๆ”

ความ​ยาก​ของ​การ​ตั้ง​ชื่อ​ไม่​ได้​อยู่​ที่​ไวยากรณ์ แต่​อยู่​ที่​การ​ตั้ง​ชื่อ​ที่​ดี​บังคับ​ให้​ผู้​เขียน​ต้อง เข้าใจ domain และ​เจตนา​ของ code อย่าง​ถ่องแท้​เสีย​ก่อน หาก​ยัง​ไม่​เข้าใจ​พอ ชื่อ​ที่​ได้​จะ​กำกวม​หรือ​ทำให้​เข้าใจ​ผิด​เสมอ ด้วย​เหตุ​นี้​ชื่อ​ที่​หาไม่​เจอ หรือ​ชื่อ​ที่​ต้อง​คิด​นาน​ผิด​ปกติ จึง​มัก​เป็น​สัญญาณ​ว่าการ​ออกแบบ​ยัง​ไม่​นิ่ง​หรือ concept นั้น​ยัง​ไม่​ถูก factor ออก​มา​อย่าง​เหมาะสม

หลักการ​ข้อ​แรก​และ​สำคัญ​ที่สุด​ของ​การ​ตั้ง​ชื่อ​คือ ความ​สอดคล้อง​ภายใน​ทีม​หรือ project ไม่​ว่า​ความ​ชอบ​ส่วนตัว​ของ​แต่ละ​คน​จะ​เป็น​อย่างไร code ที่​ทุก​คนใช้​ร่วม​กัน​ต้อง​ใช้​ชื่อ​และ​รูปแบบ​ที่​สอดคล้อง​กัน ไม่มี​หลักการ​อื่น​ใด​ด้าน​ล่าง​นี้​สำคัญ​เหนือ​กว่า​ข้อ​นี้

1. สื่อ​เจตนา ไม่ใช่​แค่​ชนิด​ข้อมูล​หรือ​กลไก — ชื่อ​ควร​ตอบ​คำถาม​ว่า “ทำไม​สิ่ง​นี้​จึง​มี​อยู่” และ “มัน​ทำ​อะไร” มากกว่า​จะ​บอก​แค่​ว่า​มัน​เก็บ​ข้อมูล​ชนิด​ใด หาก​ต้อง​มี​คอมเมนต์​มา​ช่วย​อธิบาย​ความหมาย​ของ​ชื่อ นั่น​คือ​สัญญาณ​ว่า​ชื่อ​นั้น สั้น​หรือ​คลุมเครือ​เกิน​ไป

2. ใส่​หน่วย​และ​บริบท​ไว้​ใน​ชื่อ เมื่อ​มี​ความ​เสี่ยง​ต่อ​ความ​กำกวม เช่น หน่วย​เวลา สกุล​เงิน หรือ​ระบบ​พิกัด ให้​ชื่อ​สื่อ​สิ่ง​นั้น​ออก​มา​ตรง ๆ แทนที่​จะ​พึ่ง​คอมเมนต์

3. ใช้​ภาษา​ของ Ubiquitous Language ชื่อ class method และ​ตัวแปร​ใน code ควร​ตรง​กับ​คำ​ศัพท์​ที่ domain expert ใช้​จริง ไม่ใช่​ศัพท์​เทคนิค​ที่​นัก​พัฒนา​คิด​ขึ้น​เอง — ดู Ubiquitous Language ใน​บริบท​ของ Domain-Driven Design

4. เลือก​ระดับ abstraction ให้​เหมาะ​กับ scope ตัวแปร​ที่​มี scope แคบ (เช่น ตัว​นับ​ใน loop สั้น ๆ) อาจ​ใช้​ชื่อ​สั้น​ได้ แต่​ตัวแปร method หรือ class ที่​มี scope กว้าง ควร​ใช้​ชื่อ​ที่​ยาว​และ​อธิบาย​ตัวเอง​มาก​ขึ้น

5. ตั้ง​ชื่อ method ตาม​สิ่ง​ที่​มัน​ทำ​จริง รวม​ถึง side effect ถ้า method ชื่อ GetUser แต่​แอบ​บันทึก log หรือ​แก้ state ภายใน นั่น​คือ​ชื่อ​ที่​โกหก​ผู้​เรียก​ใช้

6. หลีก​เลี่ยง anti-pattern ที่​พบ​บ่อย

  • ใช้​ชื่อ​สุ่ม​ไม่มี​ความหมาย (เช่น foo, Fred) แทน​ตัวแปร​จริง
  • ใช้​ชื่อ​ตัว​อักษร​เดียว โดย​เฉพาะ l เพราะ​สับสน​กับ​เลข 1 ได้​ง่าย (ยกเว้น​ตัว​นับ loop สั้น ๆ ตาม​ธรรมเนียม)
  • ทำให้​ชื่อ​ที่​คล้าย​กัน​ต่าง​กัน​ด้วย​การ​สะกด​ผิด​หรือ​สะกด​สำรอง (color / colour)
  • ตั้ง​ชื่อ class กระเป๋า​รวม​ที่​ทำ​สารพัด​อย่าง เช่น Util, Utils, Manager, Helper
  • ตั้ง​ชื่อ boolean ใน​เชิง​ปฏิเสธ เช่น notReady, isInvalid — ทำให้​เงื่อนไข if (!notReady) อ่าน​ยาก
  • ต่อ​ท้าย​ชื่อ​ที่​ซ้ำ​กัน​ด้วย​ตัวเลข (item1, item2, item3) แทนที่​จะ​แยก concept ให้​ชัด
  • ปน​คำ​ตรง​ข้าม​ไม่​สม่ำเสมอ (ใช้ begin/end ที่​หนึ่ง แล้ว​ใช้ start/finish อีก​ที่)

7. Pair กับ​เพื่อน​ร่วม​ทีม​เวลา​ตั้ง​ชื่อ​ยาก ชื่อ​ที่​สอง​คน​ช่วย​กัน​คิด​จน​เข้าใจ​ตรง​กัน มี​แนวโน้ม​สูง​ที่​คน​อื่น​ใน​ทีม​จะ​เข้าใจ​ตาม​ได้​ง่าย​เช่น​กัน

8. ถ้า​ชื่อ​ยาว​เกิน​ไป​จน​อึดอัด ให้​มอง​เป็น code smell — method หรือ class ที่​ต้อง​ใช้​ชื่อ​ยาว​มาก​เพื่อ​อธิบาย​ให้​ครบ มัก​กำลัง​ทำ​หน้าที่​มาก​เกิน​ไป ควร​แตก​ออก​เป็น​ส่วน​ที่​มี cohesion สูง​ขึ้น​ตาม Single Responsibility

แผนผัง​ด้าน​ล่าง​สรุป​กระบวนการ​ตัดสิน​ใจ​คร่าว ๆ เวลา​ต้อง​ตั้ง​ชื่อ​อะไร​สัก​อย่าง

flowchart TD
    Start[เริ่มตั้งชื่อ] --> Intent{อธิบายเจตนาได้ชัดหรือไม่}
    Intent -- ไม่ชัด --> Understand[ทำความเข้าใจ domain เพิ่ม]
    Understand --> Intent
    Intent -- ชัด --> UL{มีคำใน Ubiquitous Language หรือไม่}
    UL -- มี --> UseUL[ใช้คำนั้นตรงๆ]
    UL -- ไม่มี --> Draft[ร่างชื่อผู้สมัคร]
    Draft --> Pair[Pair กับเพื่อนร่วมทีม]
    UseUL --> Pair
    Pair --> Check{ต้องมีคอมเมนต์อธิบายเพิ่มไหม}
    Check -- ต้องมี --> Draft
    Check -- ไม่ต้อง --> Done[ใช้ชื่อนี้]

ตัวอย่าง​คลาสสิก​จาก Tim Ottinger ใน​หนังสือ Clean Code แสดง​ให้​เห็น​ชัด​ว่า​ชื่อ​สั้น​เกิน​ไป​บังคับ​ให้​ต้อง​พึ่ง​คอมเมนต์:

ชื่อ​ที่​แย่ — ต้อง​พึ่ง​คอมเมนต์​บอก​ความหมาย

int d; // elapsed time in days

ชื่อ​ที่​ดี — ชื่อ​บอก​ความหมาย​และ​หน่วย​ใน​ตัวเอง

int elapsedTimeInDays;
int daysSinceCreation;

ลอง​นึกถึง method Thread.Sleep ของ .NET ที่​รับ​ค่า int — ไม่มี​ความ​กำกวม​เรื่อง​หน่วย​เลย เพราะ parameter ถูก​ตั้ง​ชื่อ​ว่า millisecondsTimeout แทนที่​จะ​เป็น​แค่ timeout เฉย ๆ

ตัวอย่าง​การ​ตั้ง​ชื่อ​ใน​บริบท domain model ที่​สื่อ Ubiquitous Language แทน​ศัพท์​เทคนิค​ทั่วไป:

// ไม่ดี: ใช้ศัพท์ generic ที่ไม่สื่อ domain
public class OrderManager
{
public void Process(Order o, bool flag)
{
if (!flag)
{
// ...
}
}
}
// ดีขึ้น: ชื่อสื่อเจตนาและใช้ภาษาที่ domain expert เข้าใจ
public class OrderFulfillmentService
{
public void ShipOrder(Order order, bool isExpeditedShipping)
{
if (!isExpeditedShipping)
{
ScheduleStandardDelivery(order);
}
else
{
ScheduleExpeditedDelivery(order);
}
}
}

Microsoft Learn ยัง​กำหนด​ข้อ​ตกลง​การ​ตั้ง​ชื่อ​สำหรับ .NET/C# ไว้​ชัดเจน เช่น ใช้ PascalCase กับ type, method และ public member ทั้งหมด ใช้ camelCase กับ local variable และ parameter ขึ้น​ต้น interface ด้วย I และ หลีก​เลี่ยง​คำ​ย่อ เว้น​แต่​เป็น​คำ​ย่อ​ที่​รู้จัก​กัน​ทั่วไป ตัวอย่าง​การ​ตั้ง​ชื่อ field ตาม​ธรรมเนียม​ของ​ทีม .NET Runtime:

public class OrderProcessor : IOrderProcessor
{
private readonly IOrderRepository _orderRepository; // private field: _camelCase
private static int s_processedCount; // private static field: s_prefix
public OrderProcessor(IOrderRepository orderRepository) // parameter: camelCase
{
_orderRepository = orderRepository;
}
public void ProcessOrder(Order order) // public method: PascalCase
{
// ...
}
}

ประโยชน์

  • ลด​ภาระ​ทาง​ปัญญา (cognitive load) ของ​ผู้​อ่าน code เพราะ​ไม่​ต้อง​เดา​ความหมาย​หรือ​ไล่​ตาม​คอมเมนต์
  • ทำให้ code self-documenting ลด​การ​พึ่งพา​คอมเมนต์​ที่​มัก​จะ “ล้าสมัย” ไม่​ตรง​กับ code จริง​เมื่อ​เวลา​ผ่าน​ไป
  • เร่ง onboarding สมาชิก​ใหม่​ใน​ทีม เพราะ​ชื่อ​ที่​สื่อ​ความหมาย​ทำ​หน้าที่​เป็น​เอกสาร​ใน​ตัวเอง
  • ช่วย​ให้การ​ค้นหา (searchability) และ refactor ทำได้​แม่นยำ​ขึ้น เพราะ​ชื่อ​ไม่​กำกวม​และ​ไม่​ซ้ำ​โดย​ไม่​ตั้งใจ
  • เผย​ให้​เห็น design smell ได้​เร็ว — ถ้า​ตั้ง​ชื่อ​ยาก​ผิด​ปกติ มัก​แปล​ว่า​ความ​รับผิดชอบ​ของ element นั้น​ยัง​ไม่​ชัดเจน

ข้อ​ควร​ระวัง

  • การ​ตั้ง​ชื่อ​ใหม่​ทั้งหมด​ทันที​อาจ​กลาย​เป็น bikeshedding เสีย​เวลา​ไป​กับ​การ​ถกเถียง​เรื่อง​ผิว ๆ แทนที่​จะ​โฟกัส​ที่​ตรรกะ ควร​ทำ​ผ่าน Code Review หรือ pairing แบบ​พอเหมาะ
  • การ​เปลี่ยน​ชื่อ public API ที่​มี​ผู้​ใช้​ภายนอก​พึ่งพา​อยู่ (breaking change) ต้อง​พิจารณา​ผลกระทบ​และ versioning ก่อน​ทำ
  • ความ​สอดคล้อง (consistency) สำคัญ​กว่า​ความ​สมบูรณ์​แบบ​ของ​ชื่อ​เดี่ยว ๆ — การ​มี​สอง​รูปแบบ​ปน​กัน​ใน codebase เดียว​สร้าง​ความ​สับสน​มากกว่า​ชื่อ​ที่​ไม่​สมบูรณ์​แบบ​แต่​สอดคล้อง​กัน
  • ชื่อ​ที่​ดี​ไม่​ทดแทน​การ​ออกแบบ​ที่​ดี — ถ้า​โครงสร้าง code สับสน​อยู่​แล้ว การ​ตั้ง​ชื่อ​ใหม่​เพียง​อย่าง​เดียว​แก้​ปัญหา​ที่​ต้นเหตุ​ไม่​ได้ ต้อง refactor โครงสร้าง​ควบคู่​ไป​ด้วย