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

ทำไมและเมื่อไหร่ควร refactor

Refactoring ไม่ได้เพิ่มฟีเจอร์และไม่ได้ทำให้ผู้ใช้พึงพอใจโดยตรง จึงเป็นธรรมที่จะถามว่าคุณได้อะไรกลับมาจากความพยายามนี้ สามอย่าง ซึ่งทั้งหมดทบต้นขึ้นตลอดอายุของโปรเจกต์

  • อ่านง่าย เวลาส่วนใหญ่ของ dev หมดไปกับการอ่าน code ไม่ใช่การเขียน code ที่จัดรูปทรงจนเจตนาโผล่ออกมาเอง อ่านได้เร็วกว่าและเข้าใจผิดน้อยกว่า ทุกนาทีที่ลงไปกับการทำให้ code ชัดเจน จะได้คืนกลับมาหลายเท่าจากทุกคนที่มาอ่านทีหลัง รวมถึงตัวคุณเองในอีกไม่กี่เดือน
  • บั๊กน้อยลง โครงสร้างที่ชัดเจนเปิดโปงข้อบกพร่องที่ code ยุ่ง ๆ ซ่อนไว้ พอแต่ละ function ทำงานเดียวที่ตั้งชื่อได้ สมมติฐานผิด ๆ ก็ไม่เหลือที่ให้ซุก Refactoring ไม่ได้แก้บั๊กด้วยตัวเอง แต่ทำให้บั๊กมองเห็นได้ และทำให้ตอนแก้จริงปลอดภัยขึ้น
  • เปลี่ยนแปลงถูกลง ผลตอบแทนที่แท้จริง code ที่มีโครงสร้างดีต่อยอดได้ถูก ส่วน code ที่ยุ่งเหยิงทำให้ทุกฟีเจอร์ใหม่กลายเป็นโครงการขุดค้นทางโบราณคดี Refactoring คือการลงทุนที่ทำให้ต้นทุนของการเปลี่ยนแปลงในอนาคตยังคงต่ำ

วิธีคิดที่ใช้ได้ดีกับ code รก ๆ คือมองเป็น technical debt ทุกครั้งที่คุณใช้ทางลัดเพื่อส่งงานให้ทัน เท่ากับกู้ยืมจากอนาคต code ใช้งานได้ตอนนี้ แต่แก้ยากขึ้น และเหมือนหนี้จริง ๆ ตรงที่ คิดดอกเบี้ย คือการแก้ทุกครั้งในบริเวณนั้นจะแพงกว่าที่ควรอยู่นิดหนึ่งเสมอ

หนี้ก้อนเล็กที่ตั้งใจกู้เป็นเรื่องฉลาดได้ เพราะช่วยให้ฟีเจอร์ออกสู่ตลาดทัน อันตรายอยู่ที่การไม่เคยจ่ายคืนเลย ปล่อยไว้นานดอกเบี้ยจะทบต้นจนแก้ code บรรทัดเดียวกินเวลาเป็นสัปดาห์ Refactoring คือวิธีจ่ายเงินต้นและหยุดดอกเบี้ย

flowchart LR
  A["Ship a shortcut"] --> B["Debt accrues<br/>(harder to change)"]
  B --> C{"Pay it down?"}
  C -->|"Refactor"| D["Interest cleared,<br/>code stays cheap"]
  C -->|"Ignore"| E["Interest compounds,<br/>every change slows"]
  E --> B
Technical debt: refactoring ชำระเงินต้นก่อนที่ดอกเบี้ยจะทบต้น

ทิ้ง code ไว้ให้สะอาดกว่าตอนที่คุณเจอเสมอ แม้จะแค่นิดเดียวก็ตาม

คุณไม่จำเป็นต้องมี “refactoring sprint” โดยเฉพาะ การปรับปรุงที่ยั่งยืนที่สุดมาจากการเก็บกวาดเล็ก ๆ ที่สอดแทรกอยู่ในงานปกติ เปลี่ยนชื่อตัวแปรที่สับสนหนึ่งตัว extract บล็อกที่ยุ่งเหยิงหนึ่งบล็อก ลบ branch ที่ตายแล้วหนึ่งอันทุกครั้งที่คุณผ่านมา code เบสจะค่อย ๆ มีแนวโน้มสะอาดขึ้นโดยไม่มีใครต้องจัดตารางเวลาเลย

ในทางปฏิบัติ refactoring ที่ดีเป็นแบบ ฉวยโอกาส คือแล่นไปพร้อมกับงานที่คุณทำอยู่แล้ว มีสามจังหวะที่เป็นธรรมชาติ

  • Preparatory refactoring“ทำให้การเปลี่ยนแปลงง่ายก่อน แล้วค่อยเปลี่ยนแบบง่าย ๆ นั้น” ก่อนเพิ่มฟีเจอร์ ให้จัดรูปทรง code รอบ ๆ ให้ฟีเจอร์ลงตัวสะอาด ๆ ยังไงคุณก็ต้องอ่าน code ตรงนี้อยู่แล้ว ลงแรงอีกสองสามนาทีเตรียมทางไว้เลย
  • Comprehension refactoring — พอคุณแกะ code ที่งงงวยออกได้สำเร็จ ให้เขียนความเข้าใจนั้นกลับลงไปใน code เลย เปลี่ยนชื่อให้ตรง extract ส่วนที่เพิ่งไขได้ออกมา ผู้อ่านคนถัดไปจะได้ไม่ต้องดิ้นซ้ำรอยคุณ
  • Litter-pickup refactoring — ระหว่างทางคุณเห็นความรกที่ไม่เกี่ยวกับงานตรงหน้า ถ้าเล็กก็เก็บตรงนั้นเลย ถ้าใหญ่ก็จดไว้แล้วค่อยกลับมา อย่าปล่อยให้การเก็บกวาดบานปลายจนกลายเป็น diff ที่รีวิวไม่ไหว

Refactoring ไม่ได้ฟรี และมีบางเวลาที่ควรวางหมวกลง

สถานการณ์ทำไมจึงควรยับยั้งไว้
ไม่มี testหากไม่มีตาข่ายความปลอดภัย คุณก็พิสูจน์ไม่ได้ว่า behavior ถูกรักษาไว้ จงเพิ่ม characterization test ก่อน แล้วจึง refactor
code ใช้แล้วทิ้งสคริปต์ครั้งเดียวจบหรือ spike ที่คุณจะลบทิ้งพรุ่งนี้ ไม่คุ้มกับการลงทุน
ช่วงคับขันก่อนเส้นตายถ้าต้องส่งการแก้ให้ทันในชั่วโมงหน้า ก็ส่งไปก่อน จดหนี้ไว้แล้วค่อยจ่ายคืนอย่างตั้งใจทีหลัง ไม่ใช่ตอนที่โดนกดดัน
code ที่คุณกำลังจะลบจัดโครงสร้าง code ที่ยังไงก็จะถอดออกใน ticket ถัดไป คือลงแรงทิ้งเปล่า

กฎลูกเสือฉบับซื่อสัตย์ย่อมรวมถึงการรู้ว่าเมื่อไหร่ที่ที่ตั้งแคมป์กำลังไฟไหม้และตอนนี้ไม่ใช่ช่วงเวลาที่จะเก็บกวาด

Refactor เมื่อหลีกเลี่ยงเมื่อ
กำลังจะ add feature และ code ที่อยู่ขัดขวางไม่มีแผนแตะ code นั้นในระยะอันใกล้
code review พบว่า code อ่านยากและถามซ้ำrelease freeze — ความเสี่ยง regression สูงกว่าประโยชน์
bug พบในพื้นที่ที่ code ซับซ้อนโดยไม่จำเป็นteam ยังไม่เข้าใจ domain นั้นดีพอ

⚠️ ไม่ควร Refactor เมื่อ:

  • ไม่มี test — ต้องเขียน test ก่อน
  • deadline กดดัน — refactor หลัง release แล้วค่อย ship feature
  • code ที่จะ refactor เป็น “dead code” ที่ยังไงก็กำลังจะลบทิ้ง
อะไรคือผลตอบแทนระยะยาวที่ลึกซึ้งที่สุดของ refactoring?
"technical debt" คิดอะไรเพิ่มขึ้นเรื่อย ๆ ตามเวลาหากไม่เคยชำระคืน?
ข้อใดอธิบาย preparatory refactoring?
เมื่อไหร่ที่ดีที่สุดที่จะยับยั้งการ refactor ไว้ก่อน?