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

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

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

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

วิธีคิดที่ใช้ได้ดีกับ code รก ๆ คือมองเป็น technical debt อุปมาที่ Ward Cunningham ตั้งขึ้นในรายงานประสบการณ์ OOPSLA ปี 1992 ทุกครั้งที่คุณใช้ทางลัดเพื่อส่งงานให้ทัน เท่ากับกู้ยืมจากอนาคต 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 ไว้ให้สะอาดกว่าตอนที่คุณเจอเสมอ แม้จะแค่นิดเดียวก็ตาม

Robert C. Martin ทำให้กฎนี้แพร่หลายในชื่อ Boy Scout Rule ผ่านหนังสือ Clean Code (2008) และ 97 Things Every Programmer Should Know (2010)

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

ในทางปฏิบัติ refactoring ที่ดีเป็นแบบ ฉวยโอกาส คือแล่นไปพร้อมกับงานที่คุณทำอยู่แล้ว มีสามจังหวะที่เป็นธรรมชาติ (ตาม Fowler, Refactoring พิมพ์ครั้งที่ 2 บทที่ 2 “When Should We Refactor?”)

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

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

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

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

อะไรคือผลตอบแทนระยะยาวที่ลึกซึ้งที่สุดของ refactoring?
"technical debt" คิดอะไรเพิ่มขึ้นเรื่อย ๆ ตามเวลาหากไม่เคยชำระคืน?
ข้อใดอธิบาย preparatory refactoring?
เมื่อไหร่ที่ดีที่สุดที่จะยับยั้งการ refactor ไว้ก่อน?