การอ่าน code smell
Smell คือตัวกระตุ้น ไม่ใช่กฎหมาย
หัวข้อที่มีชื่อว่า “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 ที่คุณจะเจอบ่อยที่สุด แต่ละตัวจับคู่กับ refactoring ที่คอร์สนี้สอนไว้แก้ ใช้ตารางนี้เป็นแผนที่ พอรู้สึกว่ามีอะไรผิดปกติ ให้ตั้งชื่อ smell ให้ได้ก่อน แล้วค่อยเปิดดูท่าที่ใช้แก้
| Smell | สิ่งที่คุณสังเกตเห็น | คอร์สนี้แก้ไว้ที่ไหน |
|---|---|---|
| Long Function | function ที่ยาวเลื่อนพ้นหน้าจอและทำหลายสิ่ง | Composing Methods — Extract Function |
| Duplicated Code | ชิ้นส่วนเดียวกันปรากฏในสองที่หรือมากกว่า | Extract Function และ Moving Features |
| Mysterious Name | ตัวแปรหรือ function ที่ชื่อซ่อนจุดประสงค์ไว้ | Extract Variable |
| Large Class | class ที่กักตุน field และความรับผิดชอบไว้มากเกินไป | Moving Features |
| Long Parameter List | การเรียกที่มี argument มากมายจนคุณตามไม่ทัน | Simplifying APIs |
| Feature Envy | method ที่สนใจข้อมูลของ object อื่นมากกว่าของตัวเอง | Moving Features |
| Primitive Obsession | string และ number เปล่า ๆ ที่ถูกใช้แทนแนวคิดจริง | Organizing Data |
| Shotgun Surgery | การเปลี่ยนแปลงเล็ก ๆ หนึ่งครั้งบังคับให้ต้องแก้กระจัดกระจายไปทั่วหลายไฟล์ | Moving Features |
| Tangled Conditionals | การแตกแขนงที่ซ้อนลึกหรือซ้ำกันจนซ่อน logic ไว้ | Simplifying Conditionals |
| Temporary Field / Inheritance Misuse | สถานะที่ถูกใช้เพียงบางครั้ง หรือลำดับชั้นที่ขัดขากับคุณ | Generalization & Inheritance |
ตัวอย่าง smell สั้น ๆ
หัวข้อที่มีชื่อว่า “ตัวอย่าง smell สั้น ๆ”นี่คือ Primitive Obsession คือใช้ number ดิบ ๆ ส่งไปมาเพื่อแทน “เงิน” ทั้งที่ไม่มีอะไรกันไม่ให้ caller เอาไปปนกับจำนวนชิ้นหรืออุณหภูมิ smell ตรงนี้คือการใช้ primitive เปล่า ๆ แทนแนวคิดในโดเมน วิธีรักษาคือห่อไว้ใน type เล็ก ๆ ซึ่งอยู่ใน Organizing Data แต่คุณควรฝึก ดมกลิ่น ให้ออกตั้งแต่ตอนนี้
// Smell: a bare number means "money" — easy to misusefunction total(price: number, shipping: number): number { return price + shipping;}
// Hint of the cure: give money its own typetype Money = { cents: number };
function totalMoney(price: Money, shipping: Money): Money { return { cents: price.cents + shipping.cents };}# Smell: a bare number means "money" — easy to misusedef total(price, shipping): return price + shipping
# Hint of the cure: give money its own typefrom dataclasses import dataclass
@dataclassclass Money: cents: int
def total_money(price: Money, shipping: Money) -> Money: return Money(price.cents + shipping.cents)// Smell: a bare number means "money" — easy to misusefunc Total(price, shipping float64) float64 { return price + shipping}
// Hint of the cure: give money its own typetype Money struct{ Cents int }
func TotalMoney(price, shipping Money) Money { return Money{Cents: price.Cents + shipping.Cents}}// Smell: a bare number means "money" — easy to misusefn total(price: f64, shipping: f64) -> f64 { price + shipping}
// Hint of the cure: give money its own typestruct Money { cents: i64,}
fn total_money(price: Money, shipping: Money) -> Money { Money { cents: price.cents + shipping.cents }}เวอร์ชันที่ถูกห่อไว้ทำให้เป็นไปไม่ได้ที่จะเผลอบวกเงินเข้ากับจำนวน — ตอนนี้ compiler หรือชนิดข้อมูลแบกความหมายที่ number เปล่า ๆ เคยปล่อยให้ขึ้นอยู่กับโชคชะตา
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| Refactor เมื่อ | หลีกเลี่ยงเมื่อ |
|---|---|
| เห็น Code Smell ที่ชัดเจนและตั้งชื่อได้ | ยังไม่มี test รองรับ |
| ต้องการ add feature ในพื้นที่นั้น | deadline กำลังจะมาถึงและ feature ยังไม่พร้อม |
| code ทำให้ review ยากและทีมถามซ้ำ ๆ | code นั้นยังไงก็กำลังจะลบทิ้ง |
⚠️ ไม่ควร Refactor เมื่อ:
- ยังไม่รู้ว่า code นั้นทำอะไรจริง ๆ — อ่านให้เข้าใจก่อน
- ไม่มี test และ refactoring จะเปลี่ยน behavior โดยไม่รู้ตัว
- refactor เพราะความรู้สึก ไม่ใช่เพราะ smell จริง ๆ