ในโลกของการพัฒนาซอฟต์แวร์ การเขียนโค้ดที่ "ทำงานได้" เป็นเพียงจุดเริ่มต้น แต่การเขียนโค้ดที่ "อ่านง่าย บำรุงรักษาง่าย และขยายต่อได้" คือสิ่งที่แยกนักพัฒนามืออาชีพออกจากมือสมัครเล่น Robert C. Martin (Uncle Bob) ผู้เขียนหนังสือ Clean Code กล่าวไว้ว่า "โค้ดที่ดีคือโค้ดที่อ่านแล้วเข้าใจได้ทันที" และ "อัตราส่วนเวลาที่ใช้อ่านโค้ดต่อเขียนโค้ดคือ 10 ต่อ 1" ดังนั้นการทำให้โค้ดอ่านง่ายจึงเป็นการลงทุนที่คุ้มค่าที่สุด
บทความนี้จะพาคุณเรียนรู้หลักการ Clean Code ตั้งแต่พื้นฐานจนถึงเทคนิค Refactoring ขั้นสูง ครอบคลุม SOLID Principles, Code Smells, Refactoring Techniques และ Best Practices ที่นักพัฒนาทุกคนควรรู้ในปี 2026
Clean Code คืออะไร?
Clean Code คือโค้ดที่มีคุณสมบัติดังนี้:
- อ่านง่าย (Readable) — คนอื่นอ่านแล้วเข้าใจได้ทันทีว่าทำอะไร ทำไม ไม่ต้องเดา ไม่ต้องพึ่ง Comment มากมาย
- เรียบง่าย (Simple) — ไม่ซับซ้อนเกินความจำเป็น ทำสิ่งที่ต้องทำด้วยวิธีที่ตรงไปตรงมาที่สุด
- ทดสอบได้ (Testable) — โครงสร้างเอื้อต่อการเขียน Unit Test ไม่มี Dependencies ที่ซ่อนอยู่ หรือ Side effects ที่คาดเดาไม่ได้
- ไม่มีส่วนที่ซ้ำ (No Duplication) — ไม่มีโค้ดที่ทำสิ่งเดียวกันอยู่หลายที่ เมื่อต้องแก้ไข แก้ที่เดียวจบ
- มีความหมาย (Meaningful) — ชื่อตัวแปร ฟังก์ชัน Class บอกความหมายชัดเจน
Meaningful Naming — การตั้งชื่อที่มีความหมาย
การตั้งชื่อเป็นสิ่งสำคัญที่สุดอย่างหนึ่งใน Clean Code ชื่อที่ดีสามารถลดความจำเป็นในการเขียน Comment ได้อย่างมาก และทำให้โค้ดอ่านเหมือนภาษาธรรมชาติ
# BAD — ชื่อไม่มีความหมาย
def calc(a, b, c):
return a * b * (1 - c)
d = calc(100, 5, 0.1)
lst = [x for x in data if x > 0]
temp = get_data()
# GOOD — ชื่อบอกความหมายชัดเจน
def calculate_discounted_total(price, quantity, discount_rate):
return price * quantity * (1 - discount_rate)
total_amount = calculate_discounted_total(
price=100, quantity=5, discount_rate=0.1
)
positive_values = [value for value in measurements if value > 0]
user_profile = fetch_user_profile(user_id=42)
// BAD — ชื่อคลุมเครือ
class Data {
process() { ... }
handle() { ... }
doStuff() { ... }
}
function check(x) { return x > 0 && x < 100; }
// GOOD — ชื่อชัดเจน
class OrderProcessor {
calculateSubtotal() { ... }
applyDiscount() { ... }
generateInvoice() { ... }
}
function isWithinValidRange(age) { return age > 0 && age < 100; }
หลักการตั้งชื่อที่ดี
- ใช้ชื่อที่เปิดเผยเจตนา (Intention-Revealing Names) — ชื่อต้องตอบได้ว่า ทำไมมีอยู่ ทำอะไร ใช้อย่างไร
- หลีกเลี่ยงตัวย่อที่ไม่ชัดเจน —
usrใช้user,cntใช้count,mgrใช้manager - ใช้ชื่อที่สามารถอ่านออกเสียงได้ —
genymdhmsควรเป็นgenerationTimestamp - ใช้ชื่อที่ค้นหาได้ง่าย — ตัวเลข Magic number ควรเป็น Named constant เช่น
MAX_RETRY_ATTEMPTS = 3 - Class ใช้คำนาม, Method ใช้คำกริยา —
UserAccount,calculateTotal(),isValid()
Functions — ฟังก์ชันที่ดี
ฟังก์ชันเป็นหน่วยพื้นฐานที่สุดของการจัดระเบียบโค้ด ฟังก์ชันที่ดีตามหลัก Clean Code ควรมีคุณสมบัติดังนี้:
1. ฟังก์ชันต้องเล็ก (Small Functions)
# BAD — ฟังก์ชันยาวเกินไป ทำหลายสิ่ง
def process_order(order):
# Validate order
if not order.items:
raise ValueError("Empty order")
if not order.customer:
raise ValueError("No customer")
for item in order.items:
if item.quantity <= 0:
raise ValueError(f"Invalid quantity for {item.name}")
if item.price < 0:
raise ValueError(f"Invalid price for {item.name}")
# Calculate totals
subtotal = 0
for item in order.items:
subtotal += item.price * item.quantity
tax = subtotal * 0.07
shipping = 50 if subtotal < 1000 else 0
total = subtotal + tax + shipping
# Save to database
db.orders.insert({
"customer_id": order.customer.id,
"items": [item.to_dict() for item in order.items],
"subtotal": subtotal,
"tax": tax,
"total": total,
"status": "pending"
})
# Send email
send_email(order.customer.email, "Order Confirmed", f"Total: {total}")
return total
# GOOD — แยกเป็นฟังก์ชันย่อย แต่ละตัวทำสิ่งเดียว
def process_order(order):
validate_order(order)
totals = calculate_order_totals(order)
order_id = save_order(order, totals)
notify_customer(order.customer, totals)
return order_id
def validate_order(order):
if not order.items:
raise ValueError("Empty order")
if not order.customer:
raise ValueError("No customer")
for item in order.items:
validate_order_item(item)
def validate_order_item(item):
if item.quantity <= 0:
raise ValueError(f"Invalid quantity for {item.name}")
if item.price < 0:
raise ValueError(f"Invalid price for {item.name}")
def calculate_order_totals(order):
subtotal = sum(item.price * item.quantity for item in order.items)
tax = subtotal * TAX_RATE
shipping = 0 if subtotal >= FREE_SHIPPING_THRESHOLD else SHIPPING_FEE
return OrderTotals(subtotal=subtotal, tax=tax, shipping=shipping)
2. ฟังก์ชันต้องทำสิ่งเดียว (Single Responsibility)
ฟังก์ชันแต่ละตัวควรทำสิ่งเดียว ทำมันให้ดี และทำแค่สิ่งนั้น ถ้าฟังก์ชันมีคำว่า "and" ในชื่อ มักเป็นสัญญาณว่าทำหลายสิ่งเกินไป
3. จำนวน Arguments ให้น้อย
# BAD — Arguments มากเกินไป
def create_user(name, email, password, age, phone, address,
city, country, zip_code, role, department):
...
# GOOD — ใช้ Object/Dict รวมกลุ่ม
@dataclass
class CreateUserRequest:
name: str
email: str
password: str
age: int
contact: ContactInfo
role: str = "user"
def create_user(request: CreateUserRequest):
...
DRY, KISS, YAGNI — หลักการพื้นฐาน 3 ข้อ
DRY — Don't Repeat Yourself
ทุกความรู้ (Knowledge) ในระบบควรมีการแสดงออกที่เป็นเอกภาพ ไม่ซ้ำซ้อน เมื่อต้องเปลี่ยนแปลง แก้ที่เดียวจบ
# BAD — Code ซ้ำซ้อน
def get_active_users():
users = db.query("SELECT * FROM users WHERE active = 1")
result = []
for user in users:
result.append({
"id": user.id,
"name": user.name,
"email": user.email,
"created": user.created_at.isoformat()
})
return result
def get_admin_users():
users = db.query("SELECT * FROM users WHERE role = 'admin'")
result = []
for user in users:
result.append({
"id": user.id,
"name": user.name,
"email": user.email,
"created": user.created_at.isoformat()
})
return result
# GOOD — DRY: แยก Logic ที่ซ้ำออกมา
def format_user(user):
return {
"id": user.id,
"name": user.name,
"email": user.email,
"created": user.created_at.isoformat()
}
def get_users(condition: str):
users = db.query(f"SELECT * FROM users WHERE {condition}")
return [format_user(u) for u in users]
def get_active_users():
return get_users("active = 1")
def get_admin_users():
return get_users("role = 'admin'")
KISS — Keep It Simple, Stupid
เลือกวิธีที่ง่ายที่สุดที่ทำงานได้ อย่าสร้างความซับซ้อนที่ไม่จำเป็น
// BAD — Over-engineering
class UserValidatorFactory {
static create(type) {
const validators = new Map();
validators.set('email', new EmailValidatorStrategy());
validators.set('phone', new PhoneValidatorStrategy());
return new CompositeValidator(
validators.get(type) || new DefaultValidatorStrategy()
);
}
}
// GOOD — KISS: ง่ายพอสำหรับความต้องการจริง
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validatePhone(phone) {
return /^\d{10}$/.test(phone);
}
YAGNI — You Aren't Gonna Need It
อย่าเขียนโค้ดสำหรับฟีเจอร์ที่ยังไม่ต้องการ เขียนเฉพาะสิ่งที่ต้องใช้ตอนนี้ เมื่อความต้องการเปลี่ยนค่อย Refactor ในอนาคตจะง่ายกว่ามาก ถ้าโค้ดสะอาดตั้งแต่แรก
Code Smells — กลิ่นของโค้ดที่ไม่ดี
Code Smells คือสัญญาณที่บอกว่าโค้ดอาจมีปัญหาและควร Refactor แม้ว่าโค้ดจะยังทำงานได้ถูกต้อง Martin Fowler ได้จำแนก Code Smells ไว้หลายประเภท ต่อไปนี้คือ Smells ที่พบบ่อยที่สุด:
| Code Smell | คำอธิบาย | วิธีแก้ |
|---|---|---|
| Long Method | ฟังก์ชันที่ยาวเกิน 20-30 บรรทัด ทำหลายสิ่ง | Extract Method |
| God Class | Class ที่ใหญ่เกินไป รู้ทุกเรื่อง ทำทุกอย่าง | Extract Class, SRP |
| Feature Envy | Method ที่ใช้ข้อมูลของ Class อื่นมากกว่าของตัวเอง | Move Method |
| Primitive Obsession | ใช้ Primitive types แทน Value Objects | Replace with Value Object |
| Shotgun Surgery | เปลี่ยนฟีเจอร์หนึ่ง ต้องแก้หลายไฟล์ | Move Method, Extract Class |
| Data Clumps | กลุ่มข้อมูลที่ปรากฏด้วยกันเสมอ | Extract Class |
| Switch Statements | Switch/if-else ยาวๆ ที่ซ้ำหลายที่ | Polymorphism |
| Dead Code | โค้ดที่ไม่ถูกเรียกใช้เลย | ลบทิ้ง |
| Comments (บางกรณี) | Comment ที่อธิบายว่าโค้ดทำอะไร แทนที่จะทำไม | Rename, Extract Method |
| Duplicated Code | โค้ดที่ซ้ำกันหลายที่ | Extract Method, Template Method |
ตัวอย่าง God Class และการแก้ไข
# BAD — God Class: ทำทุกอย่าง
class UserManager:
def create_user(self, data): ...
def update_user(self, user_id, data): ...
def delete_user(self, user_id): ...
def validate_email(self, email): ...
def hash_password(self, password): ...
def send_welcome_email(self, user): ...
def send_password_reset(self, user): ...
def generate_report(self, start_date, end_date): ...
def export_to_csv(self, users): ...
def calculate_subscription(self, user): ...
def process_payment(self, user, amount): ...
def log_activity(self, user, action): ...
# GOOD — แยกตาม Responsibility
class UserRepository:
def create(self, data): ...
def update(self, user_id, data): ...
def delete(self, user_id): ...
def find_by_id(self, user_id): ...
class UserValidator:
def validate_email(self, email): ...
def validate_password(self, password): ...
class AuthService:
def hash_password(self, password): ...
def verify_password(self, password, hashed): ...
class EmailService:
def send_welcome_email(self, user): ...
def send_password_reset(self, user): ...
class UserReportService:
def generate_report(self, start_date, end_date): ...
def export_to_csv(self, users): ...
Refactoring Techniques — เทคนิคการปรับปรุงโค้ด
Refactoring คือกระบวนการปรับปรุงโครงสร้างภายในของโค้ด โดยไม่เปลี่ยนแปลง Behavior ภายนอก เป็นเหมือนการทำความสะอาดบ้านโดยไม่เปลี่ยนผังห้อง Martin Fowler ผู้เขียนหนังสือ "Refactoring" ได้รวบรวมเทคนิค Refactoring ไว้มากมาย ต่อไปนี้คือเทคนิคที่สำคัญที่สุด:
Extract Method
แยกโค้ดที่ทำงานเฉพาะทางออกจากฟังก์ชันใหญ่มาเป็นฟังก์ชันย่อย เป็นเทคนิคที่ใช้บ่อยที่สุดในการ Refactor
// BEFORE: โค้ดยาวปนกัน
function printInvoice(invoice) {
console.log("==== INVOICE ====");
console.log(`Date: ${invoice.date}`);
console.log(`Customer: ${invoice.customer.name}`);
let total = 0;
for (const item of invoice.items) {
const itemTotal = item.price * item.quantity;
total += itemTotal;
console.log(` ${item.name}: ${item.quantity} x ${item.price} = ${itemTotal}`);
}
const tax = total * 0.07;
const grandTotal = total + tax;
console.log(`Subtotal: ${total}`);
console.log(`Tax (7%): ${tax}`);
console.log(`Total: ${grandTotal}`);
}
// AFTER: Extract Method
function printInvoice(invoice) {
printHeader(invoice);
const subtotal = printLineItems(invoice.items);
printTotals(subtotal);
}
function printHeader(invoice) {
console.log("==== INVOICE ====");
console.log(`Date: ${invoice.date}`);
console.log(`Customer: ${invoice.customer.name}`);
}
function printLineItems(items) {
let total = 0;
for (const item of items) {
const itemTotal = item.price * item.quantity;
total += itemTotal;
console.log(` ${item.name}: ${item.quantity} x ${item.price} = ${itemTotal}`);
}
return total;
}
function printTotals(subtotal) {
const tax = subtotal * TAX_RATE;
console.log(`Subtotal: ${subtotal}`);
console.log(`Tax (${TAX_RATE * 100}%): ${tax}`);
console.log(`Total: ${subtotal + tax}`);
}
Replace Conditional with Polymorphism
# BEFORE: Switch/if-else ยาว
def calculate_shipping(order):
if order.shipping_type == "standard":
if order.weight <= 5:
return 50
else:
return 50 + (order.weight - 5) * 10
elif order.shipping_type == "express":
if order.weight <= 5:
return 100
else:
return 100 + (order.weight - 5) * 20
elif order.shipping_type == "overnight":
return 300 + order.weight * 30
# AFTER: Polymorphism
from abc import ABC, abstractmethod
class ShippingStrategy(ABC):
@abstractmethod
def calculate(self, order) -> float:
pass
class StandardShipping(ShippingStrategy):
BASE_COST = 50
EXTRA_PER_KG = 10
def calculate(self, order):
extra = max(0, order.weight - 5) * self.EXTRA_PER_KG
return self.BASE_COST + extra
class ExpressShipping(ShippingStrategy):
BASE_COST = 100
EXTRA_PER_KG = 20
def calculate(self, order):
extra = max(0, order.weight - 5) * self.EXTRA_PER_KG
return self.BASE_COST + extra
class OvernightShipping(ShippingStrategy):
BASE_COST = 300
PER_KG = 30
def calculate(self, order):
return self.BASE_COST + order.weight * self.PER_KG
# Usage
SHIPPING_STRATEGIES = {
"standard": StandardShipping(),
"express": ExpressShipping(),
"overnight": OvernightShipping(),
}
def calculate_shipping(order):
strategy = SHIPPING_STRATEGIES[order.shipping_type]
return strategy.calculate(order)
Extract Class
// BEFORE: Class ที่มี Responsibility มากเกินไป
class User {
constructor(name, email, street, city, zipCode, country) {
this.name = name;
this.email = email;
this.street = street;
this.city = city;
this.zipCode = zipCode;
this.country = country;
}
getFullAddress() {
return `${this.street}, ${this.city} ${this.zipCode}, ${this.country}`;
}
isInternational() {
return this.country !== 'Thailand';
}
}
// AFTER: Extract Address class
class Address {
constructor(street, city, zipCode, country) {
this.street = street;
this.city = city;
this.zipCode = zipCode;
this.country = country;
}
getFullAddress() {
return `${this.street}, ${this.city} ${this.zipCode}, ${this.country}`;
}
isInternational() {
return this.country !== 'Thailand';
}
}
class User {
constructor(name, email, address) {
this.name = name;
this.email = email;
this.address = address; // Composition
}
}
SOLID Principles — หลักการ 5 ข้อ
SOLID เป็นหลักการออกแบบ Object-Oriented ที่ Robert C. Martin รวบรวมไว้ เป็นรากฐานสำคัญของ Clean Code และ Clean Architecture:
S — Single Responsibility Principle (SRP)
Class ควรมีเหตุผลเดียวในการเปลี่ยนแปลง หมายความว่าแต่ละ Class ควรรับผิดชอบเพียง Responsibility เดียว ตัวอย่างที่ชัดเจนคือการแยก God Class ออกเป็นหลาย Class ตาม Responsibility ดังที่แสดงในหัวข้อ Code Smells ข้างต้น
O — Open/Closed Principle (OCP)
Class ควรเปิดให้ขยาย (Extension) แต่ปิดไม่ให้แก้ไข (Modification) เมื่อต้องการเพิ่มฟีเจอร์ใหม่ ควรขยายด้วยการสร้าง Class ใหม่ ไม่ใช่แก้ไข Class เดิม
# OCP — เพิ่ม Shipping type ใหม่ได้โดยไม่ต้องแก้โค้ดเดิม
class SameDayShipping(ShippingStrategy):
def calculate(self, order):
return 500 + order.weight * 50
# เพิ่มเข้า Registry ได้เลย ไม่ต้องแก้ if-else
SHIPPING_STRATEGIES["same_day"] = SameDayShipping()
L — Liskov Substitution Principle (LSP)
Subclass ต้องสามารถใช้แทน Parent class ได้โดยไม่ทำลาย Behavior ถ้า Subclass ทำให้โปรแกรมพัง แสดงว่าผิดหลัก LSP
# BAD — ผิด LSP: Square ไม่ควร inherit Rectangle
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
# ปัญหา: set width แล้ว height ไม่เปลี่ยนตาม
# ทำให้ใช้แทน Rectangle ไม่ได้อย่างถูกต้อง
# GOOD — ใช้ Composition หรือ Interface แทน
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Shape):
def __init__(self, side):
self.side = side
def area(self):
return self.side ** 2
I — Interface Segregation Principle (ISP)
Client ไม่ควรถูกบังคับให้ Implement Interface ที่ไม่ใช้ ควรแยก Interface ใหญ่เป็นหลาย Interface เล็กๆ
# BAD — Interface ใหญ่เกินไป
class Worker(ABC):
@abstractmethod
def work(self): pass
@abstractmethod
def eat(self): pass
@abstractmethod
def sleep(self): pass
class Robot(Worker):
def work(self): ...
def eat(self): raise NotImplementedError # Robot ไม่กินข้าว!
def sleep(self): raise NotImplementedError
# GOOD — แยก Interface
class Workable(ABC):
@abstractmethod
def work(self): pass
class Feedable(ABC):
@abstractmethod
def eat(self): pass
class Human(Workable, Feedable):
def work(self): ...
def eat(self): ...
class Robot(Workable):
def work(self): ...
D — Dependency Inversion Principle (DIP)
Module ระดับสูง ไม่ควรขึ้นอยู่กับ Module ระดับต่ำ ทั้งคู่ควรขึ้นอยู่กับ Abstraction
# BAD — High-level module ขึ้นกับ Low-level module
class OrderService:
def __init__(self):
self.db = MySQLDatabase() # ผูกกับ MySQL โดยตรง
self.mailer = GmailSender() # ผูกกับ Gmail โดยตรง
# GOOD — Dependency Inversion
class OrderService:
def __init__(self, db: Database, mailer: EmailSender):
self.db = db
self.mailer = mailer
# Inject dependencies
service = OrderService(
db=PostgresDatabase(),
mailer=SendGridMailer()
)
# เปลี่ยน Implementation ได้โดยไม่ต้องแก้ OrderService
Comments — เมื่อไหร่ควรเขียน Comment
Comment ที่ดีคือ Comment ที่ไม่ต้องเขียน เพราะโค้ดอ่านรู้เรื่องด้วยตัวเอง แต่มีบางกรณีที่ Comment มีประโยชน์:
# BAD — Comment ที่ไม่จำเป็น (อธิบายสิ่งที่เห็นชัดอยู่แล้ว)
# เพิ่ม 1 เข้า counter
counter += 1
# ตรวจสอบว่า user เป็น null หรือไม่
if user is None:
return None
# GOOD — Comment ที่มีประโยชน์ (อธิบาย "ทำไม" ไม่ใช่ "อะไร")
# ต้อง +1 เพราะ API ใช้ 1-based index (ไม่ใช่ 0-based)
page_number = index + 1
# Timeout 30s เพราะ payment gateway ของ Bank X มี SLA 25s
PAYMENT_TIMEOUT = 30
# Workaround สำหรับ bug ใน library v2.3
# ref: https://github.com/lib/issues/1234
result = process(data, legacy_mode=True)
Error Handling — จัดการข้อผิดพลาดอย่างถูกต้อง
# BAD — Error handling ที่ไม่ดี
def get_user(user_id):
try:
user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
data = external_api.get(f"/profiles/{user_id}")
return {**user, **data}
except: # Catch ทุกอย่าง — อันตราย!
return None # ไม่รู้ว่าผิดตรงไหน
# GOOD — Error handling ที่ชัดเจน
def get_user(user_id: int) -> User:
try:
user = user_repository.find_by_id(user_id)
except DatabaseConnectionError as e:
logger.error(f"Database error fetching user {user_id}: {e}")
raise ServiceUnavailableError("Database is unavailable") from e
if user is None:
raise UserNotFoundError(f"User {user_id} not found")
try:
profile = profile_api.get_profile(user_id)
user.profile = profile
except ExternalAPIError as e:
logger.warning(f"Could not fetch profile for user {user_id}: {e}")
# Profile is optional — continue without it
return user
Formatting and Consistency — ความสม่ำเสมอของโค้ด
โค้ดที่อ่านง่ายต้องมี Format ที่สม่ำเสมอทั้งโปรเจกต์ ใช้เครื่องมือ Auto-formatter เพื่อให้ทั้งทีมใช้รูปแบบเดียวกัน:
# เครื่องมือ Formatting ยอดนิยม
# Python: Black + isort + flake8
pip install black isort flake8
black . # Format โค้ด
isort . # จัดเรียง imports
flake8 . # ตรวจ style
# JavaScript/TypeScript: Prettier + ESLint
npx prettier --write .
npx eslint --fix .
# Go: gofmt (built-in)
gofmt -w .
# Rust: rustfmt (built-in)
cargo fmt
# ตั้งค่าใน CI/CD ให้ตรวจ Format ทุก PR
# .github/workflows/lint.yml
# - run: black --check .
# - run: npx prettier --check .
Code Review Best Practices
Code Review เป็นกระบวนการสำคัญในการรักษาคุณภาพโค้ดของทีม ต่อไปนี้คือ Best practices:
- Review ขนาดเล็ก — PR ที่มีไม่เกิน 200-400 บรรทัดจะ Review ได้ดีที่สุด PR ที่ใหญ่มากมักจะถูก Approve โดยไม่ได้อ่านจริง
- ตรวจ Logic ไม่ใช่ Style — ปล่อยให้ Linter/Formatter จัดการ Style ผู้ Review ควรเน้นที่ Logic, Architecture, Edge cases
- ให้ Feedback ที่สร้างสรรค์ — อธิบายว่า "ทำไม" ต้องแก้ ไม่ใช่แค่บอกว่า "ผิด" เสนอทางเลือกที่ดีกว่า
- ใช้ Checklist — มี Checklist สำหรับ Review เช่น มี Error handling? มี Tests? มี Documentation? Performance OK?
- อย่ายึดความถูกต้องส่วนตัว — ถ้าโค้ดทำงานได้ถูกต้องและอ่านง่าย แม้ Style จะต่างจากที่คุณชอบ ก็ควร Approve
Refactoring Legacy Code อย่างปลอดภัย
Legacy Code คือโค้ดที่ไม่มี Tests ทำให้การแก้ไขเสี่ยงต่อการทำลาย Functionality ที่ใช้งานอยู่ Michael Feathers ผู้เขียน "Working Effectively with Legacy Code" แนะนำขั้นตอนดังนี้:
- ระบุจุดที่ต้อง Refactor — เลือกส่วนที่เปลี่ยนบ่อยหรือมีบั๊กบ่อย ไม่ต้อง Refactor ทุกอย่างพร้อมกัน
- เขียน Characterization Tests — เทสต์ที่บันทึก Behavior ปัจจุบัน แม้จะไม่แน่ใจว่า Behavior นั้นถูกต้อง เพื่อให้มั่นใจว่า Refactoring ไม่เปลี่ยน Behavior
- Refactor ทีละเล็ก — เปลี่ยนทีละน้อย รันเทสต์บ่อยๆ ไม่ทำ Big Bang Refactoring
- ใช้ IDE Refactoring Tools — เครื่องมือ Refactoring ใน IDE ปลอดภัยกว่าการแก้ด้วยมือ เช่น Rename, Extract Method, Move Class
# ขั้นตอนที่ 1: เขียน Characterization Test
def test_legacy_calculate_price_current_behavior():
"""บันทึก Behavior ปัจจุบัน (ยังไม่รู้ว่าถูกหรือผิด)"""
result = legacy_calculate_price(100, "premium", True)
assert result == 85.0 # บันทึกค่าที่ได้จริง
result = legacy_calculate_price(100, "standard", False)
assert result == 100.0
# ขั้นตอนที่ 2: Refactor ทีละน้อย (เทสต์ยังผ่าน)
# ขั้นตอนที่ 3: เขียนเทสต์ใหม่สำหรับ Behavior ที่ถูกต้อง
Technical Debt Management — จัดการหนี้ทางเทคนิค
Technical Debt คือ "ต้นทุนในอนาคต" ที่เกิดจากการตัดสินใจทางเทคนิคที่เร่งรีบในปัจจุบัน เหมือนการกู้เงิน ยิ่งปล่อยนานดอกเบี้ยยิ่งสูง การจัดการ Technical Debt ที่ดีคือ:
- ทำให้เห็นได้ (Make it visible) — บันทึก Tech Debt ใน Issue tracker ไม่ใช่แค่ TODO ใน Code
- จัดลำดับความสำคัญ — แก้ Tech Debt ที่อยู่ในส่วนที่เปลี่ยนบ่อยก่อน ไม่ต้องแก้ทุกอย่าง
- Boy Scout Rule — "ทิ้งแคมป์ให้สะอาดกว่าตอนที่มาถึง" ทุกครั้งที่แก้ไขไฟล์ ปรับปรุงเล็กน้อย
- จัดสรรเวลา — กันเวลา 15-20% ของ Sprint สำหรับ Refactoring ไม่ใช่ทำแค่ฟีเจอร์ใหม่
Refactoring Tools — เครื่องมือช่วย Refactor
IDE สมัยใหม่มีเครื่องมือ Refactoring ที่ทรงพลัง ช่วยให้ Refactor ได้ปลอดภัยและรวดเร็ว ควรเรียนรู้ Keyboard shortcuts เพื่อใช้งานได้คล่อง:
| เครื่องมือ | ฟีเจอร์ | Keyboard Shortcut (VS Code) |
|---|---|---|
| Rename Symbol | เปลี่ยนชื่อตัวแปร/ฟังก์ชันทั้งโปรเจกต์ | F2 |
| Extract Method | แยกโค้ดที่เลือกเป็นฟังก์ชันใหม่ | Ctrl+Shift+R |
| Extract Variable | แยก Expression เป็นตัวแปร | Ctrl+Shift+R |
| Inline Variable | แทนที่ตัวแปรด้วยค่า | Ctrl+Shift+R |
| Move to File | ย้าย Class/Function ไปไฟล์อื่น | Right-click menu |
| Auto Import | เพิ่ม Import อัตโนมัติ | Ctrl+. |
# เครื่องมือ Static Analysis ที่ช่วยตรวจ Code Quality
# Python
pip install pylint mypy ruff
pylint app/ # ตรวจ Code quality
mypy app/ # ตรวจ Type errors
ruff check app/ # ตรวจ Linting (เร็วมาก)
# JavaScript/TypeScript
npx eslint . --ext .ts,.tsx
npx tsc --noEmit # Type check
# Go
go vet ./... # ตรวจ Code issues
golangci-lint run # ตรวจหลาย Linters พร้อมกัน
# Rust
cargo clippy # Linter ที่ดีที่สุดของ Rust
Clean Architecture Overview
Clean Architecture ที่ Robert C. Martin เสนอ แบ่งระบบเป็นชั้นวงแหวน โดยชั้นในไม่ขึ้นอยู่กับชั้นนอก:
- Entities (ชั้นในสุด) — Business rules พื้นฐาน ไม่ขึ้นอยู่กับอะไรเลย
- Use Cases — Application-specific business rules
- Interface Adapters — แปลงข้อมูลระหว่าง Use Cases กับ Framework
- Frameworks & Drivers (ชั้นนอกสุด) — Database, Web framework, UI ชั้นนี้เปลี่ยนได้ง่ายที่สุด
# ตัวอย่างโครงสร้างโฟลเดอร์ตาม Clean Architecture
project/
├── domain/ # Entities + Use Cases (ชั้นใน)
│ ├── entities/
│ │ ├── user.py
│ │ └── order.py
│ ├── usecases/
│ │ ├── create_order.py
│ │ └── get_user.py
│ └── repositories/ # Interface (Abstract)
│ ├── user_repo.py
│ └── order_repo.py
├── infrastructure/ # Frameworks & Drivers (ชั้นนอก)
│ ├── database/
│ │ ├── postgres_user_repo.py # Implementation
│ │ └── postgres_order_repo.py
│ ├── api/
│ │ └── fastapi_app.py
│ └── email/
│ └── sendgrid_service.py
└── tests/
สรุป — Checklist สำหรับ Clean Code
การเขียน Clean Code ไม่ใช่สิ่งที่ทำครั้งเดียวจบ แต่เป็นวินัยที่ต้องฝึกฝนทุกวัน ต่อไปนี้คือ Checklist ที่สามารถใช้ตรวจสอบโค้ดของตัวเองได้:
- ชื่อตัวแปร ฟังก์ชัน Class บอกความหมายชัดเจนหรือไม่?
- ฟังก์ชันแต่ละตัวทำสิ่งเดียวหรือไม่?
- ฟังก์ชันสั้นพอที่จะอ่านเข้าใจได้เร็วหรือไม่?
- มีโค้ดซ้ำซ้อนที่สามารถแยกออกมาได้หรือไม่?
- Error handling ชัดเจนและครอบคลุมหรือไม่?
- มี Magic numbers ที่ควรเป็น Named constants หรือไม่?
- Comment อธิบาย "ทำไม" ไม่ใช่ "อะไร" ใช่หรือไม่?
- โค้ดทดสอบได้ง่ายหรือไม่? Dependencies ถูก Inject หรือไม่?
- Format สม่ำเสมอกับส่วนอื่นของโปรเจกต์หรือไม่?
- ไม่มี Dead code หรือ Commented-out code ใช่หรือไม่?
จำไว้ว่า Clean Code ไม่ได้หมายความว่าต้องสมบูรณ์แบบตั้งแต่แรก แต่หมายความว่าต้องดีขึ้นเรื่อยๆ ทุกครั้งที่แตะโค้ดนั้น ปฏิบัติตาม Boy Scout Rule อย่างสม่ำเสมอ แล้วโค้ดของคุณจะสะอาดขึ้นเรื่อยๆ เริ่มวันนี้ด้วยการ Refactor ฟังก์ชันยาวๆ สักตัวหนึ่ง แล้วคุณจะเห็นว่า Clean Code เปลี่ยนชีวิตการเขียนโปรแกรมได้จริง
