refactoring คืออะไร?
นิยามที่แม่นยำ
หัวข้อที่มีชื่อว่า “นิยามที่แม่นยำ”ผู้คนใช้คำว่า refactoring แบบหลวม ๆ ในความหมายว่า “เก็บกวาด code” แต่วินัยนี้หมายถึงสิ่งที่คมชัดกว่านั้น
refactoring คือการเปลี่ยน code ที่ปรับปรุงโครงสร้างภายในโดยไม่เปลี่ยน behavior ที่สังเกตได้
สองส่วนนี้สำคัญเท่ากัน การเปลี่ยนแปลงต้อง ปรับปรุงโครงสร้าง ไม่อย่างนั้นก็เป็นแค่การขยับไปมาโดยเปล่าประโยชน์ และต้อง รักษา behavior ไว้ คือทุก input ยังให้ output เดิม ทุก side effect ยังเกิดเหมือนเดิม และไม่มี public contract ไหนขยับ การเปลี่ยนแปลงที่แก้สิ่งที่ caller มองเห็น ไม่ใช่ refactoring ต่อให้ผลลัพธ์จะดูเรียบร้อยแค่ไหนก็ตาม
refactoring กับ rewriting กับการเพิ่มฟีเจอร์
หัวข้อที่มีชื่อว่า “refactoring กับ rewriting กับการเพิ่มฟีเจอร์”สามกิจกรรมนี้รู้สึกคล้ายกันเมื่อมองจากเก้าอี้ แต่แบกความเสี่ยงที่ต่างกันมาก
- Refactoring ปรับรูปทรงของ code ที่มีอยู่และทำงานได้แล้ว ด้วยก้าวเล็ก ๆ ที่ย้อนกลับได้ คุณมีโปรแกรมที่รันได้อยู่เสมอ คุณไม่เคยอยู่ห่างจากสถานะสีเขียวล่าสุด
- Rewriting ทิ้ง code เก่าไปและสร้างของแทนที่ขึ้นมาใหม่ คุณสูญเสียการแก้บั๊กที่สะสมมาฝังอยู่ในของเดิม และคุณไม่มีโปรแกรมที่รันได้จนกว่าของใหม่จะเสร็จ บางครั้งจำเป็น แต่ไม่ค่อยถูก
- การเพิ่มฟีเจอร์ นำ behavior ใหม่ เข้ามา โปรแกรมทำสิ่งที่ทำไม่ได้มาก่อน ซึ่งหมายความว่า behavior ที่สังเกตได้เปลี่ยนไปโดยตั้งใจ
Refactoring คือทางสายกลางที่ปลอดภัย คุณเก็บ code ที่ทำงานได้ไว้แล้วค่อย ๆ ปรับให้ดีขึ้น แทนที่จะเสี่ยงเขียนของใหม่มาแทนตั้งแต่ศูนย์
สองหมวก
หัวข้อที่มีชื่อว่า “สองหมวก”เมื่อคุณนั่งลงเพื่อแก้ code คุณกำลังสวมหมวกหนึ่งในสองใบ หมวกฟีเจอร์ (feature hat) ไว้สำหรับเพิ่มความสามารถ คุณเขียน code ใหม่และ test ส่วน หมวก refactor (refactor hat) ไว้สำหรับปรับโครงสร้าง คุณเปลี่ยน รูปทรง ของ code และ test ที่มีอยู่ต้องคงเป็นสีเขียวตลอดเวลา เพราะ behavior ต้องไม่ขยับ
กฎเรียบง่ายและเข้มงวด สวมหมวกได้ครั้งละหนึ่งใบเท่านั้น ถ้าคุณกำลัง extract function อยู่ครึ่งทางแล้วสังเกตเห็นฟีเจอร์ที่ขาดไป จง refactor ให้เสร็จก่อน commit แล้ว จึง สลับหมวก การผสมสองอย่างเข้าด้วยกันคือวิธีที่ “การเก็บกวาดเร็ว ๆ” กลายเป็นการ debug ทั้งบ่าย — เมื่อ test พัง คุณจะไม่สามารถบอกได้อีกต่อไปว่าการปรับโครงสร้างหรือฟีเจอร์ใหม่ของคุณเป็นต้นเหตุ
flowchart TB
Start["I want to work on the code"] --> Q{"Which hat?"}
Q -->|"Adding behavior"| Feature["Feature hat:<br/>write new code,<br/>add tests"]
Q -->|"Cleaning structure"| Refactor["Refactor hat:<br/>keep behavior,<br/>tests stay green"]
Feature --> Switch["Switch hats —<br/>never wear both"]
Refactor --> Switch ตัวอย่าง before → after เล็ก ๆ
หัวข้อที่มีชื่อว่า “ตัวอย่าง before → after เล็ก ๆ”Refactoring ที่เล็กที่สุดมักมีค่ามากที่สุด ตรงนี้เราทำสองอย่าง แทน magic number ด้วยค่าคงที่ที่มีชื่อ และเปลี่ยนชื่อตัวแปรที่อ่านไม่รู้เรื่อง behavior เหมือนกันทั้งก่อนและหลัง — output เท่ากัน — แต่เจตนาตอนนี้อ่านออกได้จากหน้ากระดาษ
// Beforefunction price(q: number): number { return q * 9.99 * 1.07;}
// Afterconst UNIT_PRICE = 9.99;const TAX_RATE = 1.07;
function price(quantity: number): number { return quantity * UNIT_PRICE * TAX_RATE;}# Beforedef price(q): return q * 9.99 * 1.07
# AfterUNIT_PRICE = 9.99TAX_RATE = 1.07
def price(quantity): return quantity * UNIT_PRICE * TAX_RATE// Beforefunc Price(q float64) float64 { return q * 9.99 * 1.07}
// Afterconst ( UnitPrice = 9.99 TaxRate = 1.07)
func Price(quantity float64) float64 { return quantity * UnitPrice * TaxRate}// Beforefn price(q: f64) -> f64 { q * 9.99 * 1.07}
// Afterconst UNIT_PRICE: f64 = 9.99;const TAX_RATE: f64 = 1.07;
fn price(quantity: f64) -> f64 { quantity * UNIT_PRICE * TAX_RATE}ไม่มีอะไรที่ function คืนค่ากลับมาเปลี่ยนไปเลย นั่นแหละคือประเด็นทั้งหมด refactoring ที่คุณรันได้ทั้งก่อนและหลังแล้วได้ผลลัพธ์ที่เหมือนกันชนิดไบต์ต่อไบต์ คือ refactoring ที่คุณไว้ใจได้
เนื้อหาที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “เนื้อหาที่เกี่ยวข้อง”| Refactoring เหมาะเมื่อ | ไม่ใช่ Refactoring เมื่อ |
|---|---|
| เปลี่ยนโครงสร้าง code โดยไม่เปลี่ยน behavior | เพิ่ม feature ใหม่พร้อมกับปรับโครงสร้าง |
| test ยังเขียว ก่อนและหลัง | แก้ bug ไปพร้อมกับ rename/extract |
| motivation คือ readability หรือ maintainability | ทำ optimization ที่เปลี่ยน performance characteristics |
⚠️ ไม่ควรผสม Refactoring กับ:
- การเพิ่ม feature ใหม่ในเวลาเดียวกัน
- การแก้ bug — ต้องทำแยกกันเพื่อให้ track ได้
- optimization — ต้อง profile ก่อนเพื่อไม่ให้ premature