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

การอ่าน code smell

ตอนนี้คุณรู้ วิธี refactor อย่างปลอดภัยแล้ว ชิ้นส่วนที่ขาดไปคือการรู้ว่า จะ refactor อะไร และ เมื่อไหร่ คำตอบคือ code smell: สัญญาณบนพื้นผิวที่บ่งบอกว่าบางอย่างเบื้องล่างอาจมีโครงสร้างที่ไม่ดี คำว่า smell (กลิ่น) ถูกเลือกใช้อย่างตั้งใจ — กลิ่นคือคำใบ้ที่ชวนให้มองใกล้ ๆ ไม่ใช่กฎที่เรียกร้องให้ลงมือทำ บางครั้งกลิ่นนั้นก็โอเคในบริบท ทักษะคือการเรียนรู้ที่จะสังเกตกลิ่นแล้วจึงตัดสินใจ

flowchart LR
  S["Code smell<br/>(a hint, not a rule)"] --> J{"Worth it<br/>right now?"}
  J -->|yes| R["Pick a matching<br/>refactoring"]
  J -->|no| N["Note it,<br/>move on"]
  R --> T["Small step → test → commit"]
smell คือคำใบ้ — สังเกตให้เห็น ตัดสินว่าคุ้มจะแก้ตอนนี้ไหม แล้วค่อยก้าวทีละนิด

นี่คือ smell ที่คุณจะเจอบ่อยที่สุด แต่ละตัวจับคู่กับ refactoring ที่คอร์สนี้สอนไว้แก้ ใช้ตารางนี้เป็นแผนที่ พอรู้สึกว่ามีอะไรผิดปกติ ให้ตั้งชื่อ smell ให้ได้ก่อน แล้วค่อยเปิดดูท่าที่ใช้แก้ ชื่อ smell ทั้งหมดมาจากแคตตาล็อกใน Refactoring ของ Fowler (พิมพ์ครั้งที่ 2, 2018) บทที่ 3 “Bad Smells in Code” ซึ่งเขียนร่วมกับ Kent Beck

Smellสิ่งที่คุณสังเกตเห็นคอร์สนี้แก้ไว้ที่ไหน
Long Functionfunction ที่ยาวเลื่อนพ้นหน้าจอและทำหลายสิ่งComposing Methods — Extract Function
Duplicated Codeชิ้นส่วนเดียวกันปรากฏในสองที่หรือมากกว่าExtract Function และ Moving Features
Mysterious Nameตัวแปร function หรือ field ที่ชื่อซ่อนจุดประสงค์ไว้Rename Function/Variable, Rename Field, Extract Variable
Large Classclass ที่กักตุน field และความรับผิดชอบไว้มากเกินไปMoving Features
Long Parameter Listการเรียกที่มี argument มากมายจนคุณตามไม่ทันSimplifying APIs
Feature Envymethod ที่สนใจข้อมูลของ object อื่นมากกว่าของตัวเองMoving Features
Primitive Obsessionstring และ number เปล่า ๆ ที่ถูกใช้แทนแนวคิดจริงOrganizing Data
Shotgun Surgeryการเปลี่ยนแปลงเล็ก ๆ หนึ่งครั้งบังคับให้ต้องแก้กระจัดกระจายไปทั่วหลายไฟล์Moving Features
Repeated Switchesswitch หรือการแตกแขนงซ้อนลึกที่ซ้ำกันจนซ่อน logic ไว้Simplifying Conditionals
Refused Bequest / Temporary Fieldsubclass ที่ไม่ยอมรับสิ่งที่ inherit มา หรือสถานะที่ถูกใช้เพียงบางครั้งGeneralization & Inheritance

นี่คือ Primitive Obsession คือใช้ number ดิบ ๆ ส่งไปมาเพื่อแทน “เงิน” ทั้งที่ไม่มีอะไรกันไม่ให้ caller เอาไปปนกับจำนวนชิ้นหรืออุณหภูมิ smell ตรงนี้คือการใช้ primitive เปล่า ๆ แทนแนวคิดในโดเมน วิธีรักษาคือห่อไว้ใน type เล็ก ๆ ซึ่งอยู่ใน Organizing Data แต่คุณควรฝึก ดมกลิ่น ให้ออกตั้งแต่ตอนนี้

// Smell: a bare number means "money" — easy to misuse
function total(price: number, shipping: number): number {
return price + shipping;
}
// Hint of the cure: give money its own type
type Money = { cents: number };
function totalMoney(price: Money, shipping: Money): Money {
return { cents: price.cents + shipping.cents };
}

เวอร์ชันที่ถูกห่อไว้ทำให้เป็นไปไม่ได้ที่จะเผลอบวกเงินเข้ากับจำนวน — ตอนนี้ compiler หรือชนิดข้อมูลแบกความหมายที่ number เปล่า ๆ เคยปล่อยให้ขึ้นอยู่กับโชคชะตา

code smell คืออะไร?
refactoring ใดที่จัดการกับ Long Function ได้ตรงที่สุด?
smell ใดที่แสดงในตัวอย่าง?
ทำไมจึงควรตั้งชื่อ smell ก่อนเลือก refactoring?