Code Refactoring
ทุกคนพยายามเขียนโค้ดที่ดี และไม่มีโปรแกรมเมอร์คนไหนที่ตั้งใจเขียนโค้ดที่ไม่ดีซึ่งส่งผลเสียต่อโปรเจกต์โดยไม่จำเป็น แต่บางครั้งเราก็เพิ่มโค้ดที่ไม่ดีเข้าระบบทั้งโดยตั้งใจและไม่ตั้งใจ
คำถาม: เมื่อไหร่เราจะพูดได้ว่าระบบของเราจำเป็นต้องได้รับการ Refactor?
คำตอบ: เมื่อมี Technical Debt ในระบบมากเกินไป
Technical Debt คืออะไร?
Technical Debt เปรียบเหมือนการกู้เงินจากธนาคารที่ทำให้คุณสามารถซื้อของได้เร็วขึ้น แต่ คุณต้องจ่ายดอกเบี้ยทีหลัง
การเขียนโค้ดก็เช่นเดียวกัน คุณสามารถเร่งพัฒนาระบบได้โดยการข้าม Best Practices บางอย่างไปก่อน การทำแบบนั้นทำให้คุณส่งงานได้เร็วขึ้น แต่โค้ดที่ไม่ทำตาม Best Practices ย่อมมี "ดอกเบี้ย" ที่ต้องจ่าย โดย "ดอกเบี้ย" ในที่นี้คือทรัพยากรที่ต้องใช้เพิ่มขึ้นในการทำงานหรือจัดการกับโค้ดที่ไม่เป็นไปตาม Best Practices เหล่านั้น คุณจะเพิ่มฟีเจอร์ใหม่ แก้ไขปัญหา หรือดูแลรักษาระบบได้ช้าลงเรื่อยๆ ผลกระทบทางธุรกิจก็อาจเกิดขึ้น เช่น ความพอใจของลูกค้าลดลง สุดท้ายคุณจะไม่สามารถทนกับการจ่าย "ดอกเบี้ย" ที่สูงขนาดนั้นได้ และต้องยอม "จ่ายหนี้" ด้วยการเขียนระบบใหม่ทั้งหมดหรือทำการ Refactor ครั้งใหญ่
สาเหตุของ Technical Debt
Technical Debt เกิดได้จากหลายสาเหตุ เช่น
- Business Pressure: เมื่อมีเวลาจำกัด คุณอาจเลือกใช้ solution ที่ทำได้เร็วแต่ไม่เป็นไปตาม best practices
- Lack of understanding of technical debt consequences: ความไม่เข้าใจผลกระทบของ technical debt ทำให้ไม่เห็นความสำคัญที่จะจัดสรรเวลาให้ทีมจัดการ tech debt อย่างเหมาะสม
- Failing to combat component coherence: โครงสร้างโค้ดที่เชื่อมโยงกันอย่างแน่นแฟ้นทำให้การเปลี่ยนแปลงในส่วนหนึ่งของโปรเจกต์อาจส่งผลกระทบต่อส่วนอื่นๆ จึงมีการหลีกเลี่ยงที่จะแตะโค้ดส่วนนั้นไปโดยปริยาย
- Lack of tests: หากไม่มีการทดสอบรองรับ การเปลี่ยนแปลงเล็กๆ ก็อาจทำให้เกิดบั๊กใหญ่ในระบบได้ คนเลยหลีกเลี่ยงที่จะแตะโค้ดที่ขาด tests
- Lack of documentation: การขาดเอกสารอธิบาย logic ทำให้การปรับปรุงระบบหรือการนำคนใหม่เข้าทีมยากขึ้น และอาจทำให้การพัฒนาชะงักหากบุคลากรสำคัญออกจากโปรเจกต์
- Lack of interaction between team members: การสื่อสารไม่ดีในทีมทำให้คนในทีมมีข้อมูลที่ไม่ตรงกัน
- Long-term simultaneous development in several branches: ทำให้สะสม technical debt และเพิ่มมากขึ้นเมื่อทำการรวมโค้ด
- Delayed refactoring: ยิ่งเลื่อนการ Refactor ออกไป โค้ดยิ่งล้าสมัยและแก้ไขยากขึ้นเรื่อยๆ
- Incompetence: นักพัฒนาอาจไม่รู้วิธีหรือไม่เชี่ยวชาญในการเขียนโค้ดที่มีคุณภาพ
วิธีการทำ Code Refactoring
หลักการทำ Refactoring ควรทำเป็นการเปลี่ยนแปลงเล็กๆ เรื่อยๆ ซึ่งแต่ละครั้งทำให้โค้ดดีขึ้นเล็กน้อยแต่ยังคงทำงานได้ตามปกติ
โดยขั้นตอนการทำ code refactoring มีดังนี้
1. ตรวจหา Code Smells
มี code smells หลายแบบที่สามารถพบได้บ่อยๆเช่น
1.1 Bloaters: โค้ด เมธอด หรือคลาสที่ใหญ่จนเกินไป จนจัดการได้ยาก
- Long Method: เมธอดที่มีโค้ดมากเกินไป โดยปกติเมธอดที่ยาวเกินกว่า 10 บรรทัดควรถูกตั้งคำถามแล้วว่าควรถูกแยกออกหรือไม่
- Large Class: คลาสที่มีฟิลด์หรือเมธอดมากเกินไป
- Primitive Obsession: การใช้ฟิลด์ primitive แทนการสร้างคลาสที่เหมาะสม
- Long Parameter List: เมธอดที่มีพารามิเตอร์มากกว่า 3-4 ตัว
- Data Clumps: กลุ่มของตัวแปรที่ใช้ร่วมกันในหลายส่วนของโค้ดควรถูกแยกออกมาเป็นคลาส
1.2 Object-Orientation Abusers: การใช้ OOP ไม่ถูกต้อง
- Alternative Classes with Different Interfaces: คลาสสองคลาสที่ทำหน้าที่เหมือนกันแต่ใช้ interface ที่ต่างกัน
- Refused Bequest: Subclass ใช้แค่บางส่วนของเมธอดหรือฟิลด์ที่สืบทอดมาจากคลาสหลัก
- Switch Statements: มีการใช้ switch statement หรือ if statements ที่ซับซ้อนเกินไป
- Temporary Field: ฟิลด์ชั่วคราวที่ใช้งานเฉพาะในบางสถานการณ์ และไม่มีประโยชน์ในสถานการณ์อื่นๆ
1.3 Change Preventers: โค้ดที่ทำให้คุณเมื่อคุณต้องไปแก้ไขในหลายๆ ส่วนพร้อมกันเมื่อต้องทำการเปลี่ยนแปลงจุดเดียว
- Divergent Change: ต้องเปลี่ยนแปลงหลายๆ เมธอดที่ไม่เกี่ยวข้องกันเมื่อทำการเปลี่ยนแปลงคลาส
- Shotgun Surgery: ต้องทำการเปลี่ยนแปลงเล็กๆ ในหลายๆ คลาสพร้อมกัน
- Parallel Inheritance Hierarchies: การสร้าง subclass ใหม่จะต้องสร้าง subclass อื่นๆ เพิ่มด้วย
1.4 Dispensable: สิ่งที่ไม่จำเป็นและควรถูกกำจัดออกไปเพื่อทำให้โค้ดสะอาดขึ้น
- Comments: เมธอดที่เต็มไปด้วยคอมเมนต์ ถ้าเขียนโค้ดให้ชัดเจนเพียงพอ คอมเมนต์ก็ไม่จำเป็น
- Duplicate Code: โค้ดที่เหมือนกันหรือคล้ายกันในหลายส่วน
- Lazy Class: คลาสที่มีขนาดเล็กมากหรือไม่มีประโยชน์
- Dead Code: โค้ดที่ไม่ถูกใช้งาน
- Speculative Generality: คลาส เมธอด ฟิลด์ หรือพารามิเตอร์ที่ไม่ได้ถูกใช้งาน
1.5 Couplers: การเชื่อมโยงระหว่างคลาสมากเกินไป
- Feature Envy: เมธอดที่เข้าถึงข้อมูลจากคลาสอื่นมากกว่าข้อมูลของตัวเอง
- Inappropriate Intimacy: คลาสหนึ่งที่ใช้ฟิลด์ภายในหรือเมธอดของอีกคลาส
- Message Chains: การเรียกเมธอดต่อเนื่องกันหลายขั้นตอน เช่น $a->b()->c()->d()
- Middleman: คลาสที่ทำหน้าที่เพียงแค่ส่งต่อการทำงานไปยังคลาสอื่น
1.6 Other Smells:
- Incomplete Library Class: ไลบรารีที่ไม่สามารถตอบสนองความต้องการได้
2. ทำการ Refactor โดยใช้ Refactoring Techniques
การ Refactor สามารถทำได้หลายวิธี โดยมี 6 ประเภทหลัก ได้แก่:
- การจัดการเมธอด
- การย้ายฟีเจอร์ระหว่างคลาส
- การจัดการข้อมูล
- การทำให้เงื่อนไขง่ายขึ้น
- การทำให้การเรียกเมธอดง่ายขึ้น
- การจัดการ generalization
สามารถดูเทคนิคและตัวอย่างได้ใน Refactoring Cheatsheet คุณเปิ้ลผู้เขียนได้รวบรวมทั้งหมด 66 เทคนิคในการ refactor code ออกมาเป็น cheatsheet ไว้ด้านล่างสุดของบทความ คุณสามารถนำไปใช้ได้เลย