Poor Names
ชื่อที่บดบังว่าสิ่งนั้นคืออะไรหรือทำอะไร
กลิ่นนี้คืออะไร
หัวข้อที่มีชื่อว่า “กลิ่นนี้คืออะไร”Poor Names เกิดเมื่อ identifier — ตัวแปร method class parameter field — ถูกตั้งชื่อในแบบที่บดบังว่ามันแทนอะไรหรือทำอะไร ชื่อคือช่องทางหลักที่ code สื่อเจตนาต่อผู้อ่าน (ไม่ใช่ compiler ซึ่งไม่สนใจว่าตัวแปรชื่อ x หรือ customerCreditLimit) ชื่อที่แย่บังคับให้ผู้อ่านทำงานหนักขึ้นเพื่อสร้างแผนที่ในหัวขึ้นมาใหม่ทุกครั้งที่เปิด file หรือแย่กว่านั้นคือพาไปเข้าใจผิดไปเลย
Martin Fowler เรียกกลิ่นตัวนี้ในหนังสือ Refactoring ฉบับพิมพ์ครั้งที่สองว่า Mysterious Name และวางไว้เป็นกลิ่นตัวแรกที่กล่าวถึง เพราะการตั้งชื่อคือหนึ่งในสองสิ่งที่ยากที่สุดในวงการ (อีกสิ่งคือ cache invalidation และ off-by-one error ตามมุกที่เล่าต่อกันมา) Fowler ชี้ว่า “การพยายามอ่าน code ปริศนาสนุกดีถ้าเป็นนิยายสืบสวน แต่ไม่ใช่ตอนอ่าน code” — และที่สำคัญกว่านั้น เวลาที่ตั้งชื่ออะไรไม่ได้สักที มักเป็นสัญญาณว่ามีปัญหาการออกแบบที่ลึกกว่าซ่อนอยู่ ไม่ใช่แค่ปัญหาเรื่องคำศัพท์
วิธีสังเกต
หัวข้อที่มีชื่อว่า “วิธีสังเกต”รูปแบบของ Poor Names ที่พบบ่อย (อ้างอิงอนุกรมวิธานจาก Peter Hilton และแหล่งอื่น):
- ชื่อไร้ความหมาย (meaningless names) —
foo,bar,temp,data,obj,thingที่ไม่ได้บอกอะไรเลยว่าคืออะไร - ชื่อย่อกำกวม (abbreviated names) —
acc(accumulator? accuracy? account?),pos(position? positive?),auth(authentication? authorization?) — ย่อแล้วตีความได้หลายทาง - ชื่อสั้นเกินไป (short/single-letter names) —
a,i,j,x1,x2ที่ไม่มีบริบทช่วยขยายความ (ยกเว้นตัวนับ loop สั้น ๆ ที่ scope แคบมากซึ่งพอยอมรับได้) - คำต่อท้ายเป็นตัวเลข (numeric suffixes) —
employee1,employee2,handler2ที่ไม่บอกว่าสองตัวนี้ต่างกันอย่างไร - คำกว้างเกินไป (vague words) —
Manager,Processor,Data,Info, หรือกริยาทั่วไปอย่างget,handle,processที่ใช้ได้กับแทบทุกอย่างจนไม่สื่ออะไรเฉพาะเจาะจง - Hungarian notation ที่ตกยุค — คำนำหน้าบอกชนิด เช่น
strName,bIsValid,dtCreatedซึ่งซ้ำซ้อนในภาษาที่มี static typing และ IDE ที่บอกชนิดให้อยู่แล้ว - ชื่อผิด (wrong names) — ชื่อที่ฟังดูสมเหตุสมผลแต่บอกความหมายผิด เช่น method
GetTotal()ที่จริง ๆ แล้วคำนวณ + บันทึกลงฐานข้อมูลด้วย (side effect ที่ชื่อไม่ได้บอก) - ชื่อไม่สอดคล้องกัน (inconsistency) — เรียกสิ่งเดียวกันคนละชื่อในหลายที่ (
customer,client,userปนกันสำหรับ concept เดียว) — ดู Inconsistency
วิธีเช็คง่าย ๆ: ถ้าคุณต้องเปิดดู implementation หรือถามเพื่อนร่วมทีมว่า “ตัวแปรนี้/method นี้มันคืออะไรกันแน่” นั่นคือสัญญาณของ Poor Names
ทำไมถึงเป็นปัญหา
หัวข้อที่มีชื่อว่า “ทำไมถึงเป็นปัญหา”- เพิ่มต้นทุนการอ่าน — code ถูกอ่านมากกว่าที่ถูกเขียนหลายเท่า ชื่อแย่ทำให้ทุกคนที่มาอ่านทีหลัง (รวมถึงตัวเราเองในอีกหกเดือน) ต้องเสียเวลาสืบสวนแทนที่จะเข้าใจได้ทันที
- ซ่อน bug — ชื่อที่บอกความหมายผิด (เช่น
GetTotal()ที่มี side effect) ทำให้ผู้เรียกใช้คาดเดาพฤติกรรมผิด นำไปสู่ bug ที่ตามหายาก เพราะ code “ดูเหมือน” จะทำสิ่งหนึ่งแต่จริง ๆ ทำอีกสิ่งหนึ่ง — เกี่ยวโยงกับ Obscured Intent - บังคับให้พึ่งคอมเมนต์แทน code — เมื่อชื่อไม่สื่อความหมาย คนมักแก้ด้วยการเติมคอมเมนต์อธิบาย แต่คอมเมนต์ล้าสมัยได้ง่ายกว่าชื่อในซิกเนเจอร์ของ code ผลคือกลิ่น Comments ซ้อนทับเข้าไปอีกชั้น — Fowler เปรียบคอมเมนต์ที่ชดเชยชื่อแย่ว่าเหมือน “สเปรย์ระงับกลิ่นกาย” ที่กลบกลิ่น code เน่าไว้ชั่วคราวแทนที่จะแก้ที่ต้นตอ
- ทำลาย Ubiquitous Language — ในบริบท DDD ชื่อใน code ควรตรงกับคำที่ทีมและผู้เชี่ยวชาญ domain ใช้พูดคุยกัน ชื่อแย่หรือไม่สอดคล้องกันทำให้ code กับบทสนทนาทางธุรกิจแยกออกจากกัน จน model ใน code ไม่สะท้อนความเข้าใจ domain อีกต่อไป — ดู Ubiquitous Language
- เป็นสัญญาณเตือนปัญหาการออกแบบ — บ่อยครั้งที่ตั้งชื่ออะไรไม่ได้สักที เพราะ class/method นั้นทำหลายหน้าที่ปนกัน (ขัดกับ Single Responsibility Principle) ความยากในการตั้งชื่อคือสัญญาณเตือนที่คุ้มค่าจะฟัง ไม่ใช่แค่ปัญหาคำศัพท์ผิวเผิน
ตัวอย่างและการ refactor
หัวข้อที่มีชื่อว่า “ตัวอย่างและการ refactor”ตัวอย่าง 1 — ชื่อย่อกำกวมและ Hungarian notation
หัวข้อที่มีชื่อว่า “ตัวอย่าง 1 — ชื่อย่อกำกวมและ Hungarian notation”// ก่อน: ชื่อย่อกำกวม + Hungarian notation ที่ตกยุคpublic class OrdProc{ private double d; // discount? deposit? date? private bool bIsVal; // valid? validated? value?
public double Calc(double amt, int qty) { var t = amt * qty; if (bIsVal) { t = t - (t * d); } return t; }}code ข้างต้น compile ผ่านและอาจทำงานถูกต้อง แต่ผู้อ่านต้องเดาความหมายของ d, bIsVal, t, Calc ทั้งหมด — ใช้ Rename Field, Rename Variable และ Change Function Declaration (การ refactor แบบตรงไปตรงมาที่ IDE ส่วนใหญ่รองรับด้วย “Rename” อัตโนมัติ) เพื่อทำให้ชื่อสื่อเจตนา:
// หลัง: ชื่อสื่อความหมายชัดเจน ไม่ต้องเดาpublic class OrderPriceCalculator{ private double discountRate; private bool isDiscountEligible;
public double CalculateTotal(double unitPrice, int quantity) { var subtotal = unitPrice * quantity; if (isDiscountEligible) { subtotal -= subtotal * discountRate; } return subtotal; }}ตัวอย่าง 2 — ชื่อที่บอกความหมายผิด (wrong name ซ่อน side effect)
หัวข้อที่มีชื่อว่า “ตัวอย่าง 2 — ชื่อที่บอกความหมายผิด (wrong name ซ่อน side effect)”// ก่อน: ชื่อบอกว่าแค่ "อ่าน" แต่จริง ๆ มี side effect ที่แก้ state และเขียนฐานข้อมูลpublic class InvoiceService{ public decimal GetTotal(Invoice invoice) { invoice.Status = InvoiceStatus.Reviewed; // side effect ที่ชื่อไม่ได้บอก _repository.Save(invoice); // ยิ่งไม่คาดคิด return invoice.Lines.Sum(l => l.Amount); }}ผู้เรียก GetTotal() คาดหวังว่าเป็นการอ่านค่าล้วน ๆ (query ไม่มีผลข้างเคียง) แต่จริง ๆ แล้วมันเปลี่ยนสถานะและบันทึกลงฐานข้อมูลด้วย — นี่คือ Poor Names ที่อันตราย เพราะไม่ใช่แค่อ่านยาก แต่ โกหก ผู้อ่าน ทางแก้คือแยกความรับผิดชอบ (Command-Query Separation) แล้วตั้งชื่อให้ตรงกับสิ่งที่แต่ละ method ทำจริง:
// หลัง: แยก query ออกจาก command และตั้งชื่อให้ตรงกับพฤติกรรมจริงpublic class InvoiceService{ public decimal CalculateTotal(Invoice invoice) { return invoice.Lines.Sum(l => l.Amount); }
public void MarkAsReviewed(Invoice invoice) { invoice.Status = InvoiceStatus.Reviewed; _repository.Save(invoice); }}ตัวอย่าง 3 — คำกว้างเกินไปที่ทับซ้อนกับ Ubiquitous Language
หัวข้อที่มีชื่อว่า “ตัวอย่าง 3 — คำกว้างเกินไปที่ทับซ้อนกับ Ubiquitous Language”// ก่อน: "Manager" และ "Data" ไม่บอกว่า class นี้ทำอะไรจริง ๆpublic class OrderManager{ public OrderData Process(OrderData data) { /* ... */ }}เทียบกับชื่อที่ยืมมาจากภาษาที่ผู้เชี่ยวชาญ domain ใช้พูดถึงกระบวนการนี้จริง ๆ:
// หลัง: ชื่อยืมมาจาก Ubiquitous Language ของทีม สื่อว่า "ทำอะไร" ไม่ใช่แค่ "เกี่ยวกับอะไร"public class OrderFulfillmentService{ public FulfilledOrder Fulfill(PendingOrder order) { /* ... */ }}flowchart LR
A[Poor Name พบ] --> B{ตั้งชื่อใหม่ได้ทันทีไหม}
B -- ได้ --> C[Rename Variable Field หรือ Change Function Declaration]
B -- ไม่ได้ ตั้งไม่ถูกเลย --> D[สงสัยว่ามีปัญหาการออกแบบลึกกว่า]
D --> E[แยก responsibility หรือ Extract Function ก่อน]
E --> C
C --> F[ชื่อสื่อเจตนา สอดคล้อง Ubiquitous Language]
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”- Naming Things
- Ubiquitous Language
- Obscured Intent
- Comments
- Inconsistency
- Single Responsibility Principle