- การรีแฟกเตอร์โค้ดมีบทบาทสำคัญในการรักษาสุขภาพของโค้ดเบส
- อย่างไรก็ตาม การรีแฟกเตอร์ที่ผิดพลาดอาจทำให้โค้ดยิ่งซับซ้อนและดูแลรักษายากขึ้น
- ควรรู้วิธีแยกแยะระหว่างการรีแฟกเตอร์ที่ดีกับที่แย่ และวิธีหลีกเลี่ยงกับดักของการรีแฟกเตอร์ที่ไม่ดี
ข้อดี ข้อเสีย และด้านที่เลวร้ายของการรีแฟกเตอร์
- abstraction อาจเป็นได้ทั้งเรื่องดีและเรื่องแย่ หัวใจสำคัญคือการรู้ว่าควรใช้เมื่อไรและใช้อย่างไร
- อธิบายกับดักที่พบบ่อยบางอย่างและวิธีหลีกเลี่ยง (ละโค้ดต้นฉบับ)
1. เปลี่ยนสไตล์การเขียนโค้ดมากเกินไป
- นักพัฒนามักทำพลาดด้วยการเปลี่ยนสไตล์การเขียนโค้ดทั้งหมดระหว่างการรีแฟกเตอร์
- การนำไลบรารีใหม่เข้ามาหรือใช้สไตล์การเขียนโค้ดที่ต่างไปโดยสิ้นเชิง อาจส่งผลเสียต่อการบำรุงรักษา
- ควรปรับปรุงให้เป็นโค้ดที่เป็นธรรมชาติตามแนวทางของภาษาและอ่านง่ายขึ้น โดยไม่ต้องนำพาราไดม์ใหม่ทั้งหมดหรือ dependency ภายนอกเข้ามา
2. abstraction ที่ไม่จำเป็น
- การเพิ่ม abstraction ใหม่มากเกินไปโดยไม่เข้าใจโค้ดเดิมก่อนเป็นปัญหา
- มันอาจเพิ่มแต่ความซับซ้อนและทำให้เข้าใจยากขึ้น
- ควรแยกตรรกะออกเป็นฟังก์ชันเล็ก ๆ ที่นำกลับมาใช้ซ้ำได้ แต่ไม่ควรเพิ่มความซับซ้อนที่ไม่จำเป็น
3. เพิ่มความไม่สอดคล้องกัน (Inconsistency)
- การอัปเดตเพียงบางส่วนของโค้ดเบสให้ทำงานต่างออกไปโดยสิ้นเชิง อาจก่อให้เกิดความสับสนและความหงุดหงิด
- หากจำเป็นต้องนำแพตเทิร์นใหม่เข้ามา ควรขอความเห็นชอบจากทีมก่อน
- การรักษาแนวทางที่สอดคล้องกันสำหรับการดึงข้อมูลทั่วทั้งแอปพลิเคชันเป็นสิ่งสำคัญ
4. ไม่เข้าใจโค้ดก่อนรีแฟกเตอร์
- การเรียนรู้โค้ดไปพร้อมกับรีแฟกเตอร์ไม่ใช่ความคิดที่ดี
- มันอาจสร้างบั๊ก ทำให้ประสิทธิภาพลดลง และทำให้ฟีเจอร์หายไป
- ก่อนรีแฟกเตอร์ควรทำความเข้าใจโค้ดอย่างลึกซึ้ง และปรับปรุงโดยยังคงพฤติกรรมเดิมไว้
5. เข้าใจบริบทธุรกิจ
- การเลือกเทคโนโลยีโดยไม่เข้าใจธุรกิจอาจกลายเป็นหายนะได้
- สำหรับเว็บไซต์อีคอมเมิร์ซที่พึ่งพา SEO อย่างมาก การเรนเดอร์ฝั่งไคลเอนต์อาจเป็นตัวเลือกที่ไม่ดี
- แนวทาง server-side rendering อย่าง Next.js หรือ Remix อาจเป็นทางเลือกที่ดีกว่าในแง่ SEO และประสิทธิภาพ
6. รวมโค้ดมากเกินไป
- การรวมโค้ดเพื่อให้ดู "สะอาด" โดยต้องแลกกับความยืดหยุ่นไม่ใช่เรื่องที่ดี
- เวลาทำ abstraction ควรคำนึงถึง use case ที่มันรองรับเสมอ
- ควรทำให้ abstraction รองรับความสามารถทั้งหมดที่ implementation เดิมเคยมี
รีแฟกเตอร์อย่างถูกวิธี
- การรีแฟกเตอร์โค้ดเป็นสิ่งจำเป็น แต่ต้องทำให้ถูกวิธี
- โค้ดของเราไม่ได้สมบูรณ์แบบและจำเป็นต้องจัดระเบียบ แต่ก็ต้องรักษาความสอดคล้องกับโค้ดเบสเดิมไว้
- ควรรีแฟกเตอร์หลังจากเข้าใจโค้ดดีแล้ว และควรเลือก abstraction อย่างระมัดระวัง
- เคล็ดลับสำหรับการรีแฟกเตอร์ให้สำเร็จ:
- ทำแบบค่อยเป็นค่อยไป: เปลี่ยนแปลงทีละเล็กทีละน้อยที่จัดการได้ แทนการเขียนใหม่ทั้งระบบ
- ทำความเข้าใจโค้ดอย่างลึกซึ้งก่อนนำการรีแฟกเตอร์สำคัญหรือ abstraction ใหม่เข้ามา
- ให้สอดคล้องกับสไตล์โค้ดเดิม: ความสม่ำเสมอคือหัวใจของการบำรุงรักษา
- หลีกเลี่ยงการเพิ่ม abstraction ใหม่มากเกินไป: รักษาความเรียบง่ายไว้ เว้นแต่ความซับซ้อนนั้นจำเป็นจริง ๆ
- โดยเฉพาะอย่างยิ่ง อย่าเพิ่มไลบรารีใหม่ที่มีสไตล์การเขียนโปรแกรมแตกต่างมากโดยไม่มีความเห็นชอบจากทีม
- เขียนเทสต์ก่อนรีแฟกเตอร์และอัปเดตเทสต์ระหว่างทาง เพื่อยืนยันว่าฟังก์ชันเดิมยังคงอยู่
- ช่วยกันรับผิดชอบให้เพื่อนร่วมทีมยึดตามหลักการเหล่านี้
เครื่องมือและเทคนิคเพื่อการรีแฟกเตอร์ที่ดีขึ้น
- ลองพิจารณาใช้เทคนิคและเครื่องมือต่อไปนี้เพื่อช่วยให้การรีแฟกเตอร์ดีขึ้น:
เครื่องมือ linting
- ใช้เครื่องมือ linting เพื่อบังคับใช้สไตล์โค้ดที่สม่ำเสมอและค้นหาปัญหาที่อาจเกิดขึ้น
- สามารถใช้ Prettier เพื่อฟอร์แมตอัตโนมัติให้ได้สไตล์ที่สอดคล้องกัน
- ใช้ Eslint ร่วมกับปลั๊กอินแบบกำหนดเองเพื่อตรวจสอบความสอดคล้องได้ละเอียดมากขึ้น
การรีวิวโค้ด
- ทำ code review อย่างรอบคอบเพื่อรับฟีดแบ็กจากเพื่อนร่วมงานก่อน merge โค้ดที่รีแฟกเตอร์แล้ว
- ช่วยค้นพบปัญหาที่อาจเกิดขึ้นได้ตั้งแต่เนิ่น ๆ และทำให้มั่นใจว่าโค้ดที่รีแฟกเตอร์สอดคล้องกับมาตรฐานและความคาดหวังของทีม
การทดสอบ
- เขียนและรันเทสต์เพื่อให้แน่ใจว่าโค้ดที่รีแฟกเตอร์จะไม่ทำให้ความสามารถเดิมเสียหาย
- Vitest เป็น test runner ที่เร็ว แข็งแรง และใช้งานง่าย โดยแทบไม่ต้องตั้งค่าเริ่มต้น
- สำหรับ visual testing อาจพิจารณาใช้ Storybook
- React Testing Library เป็นชุดยูทิลิตีที่ดีสำหรับทดสอบคอมโพเนนต์ React (และยังมีเวอร์ชันดัดแปลงสำหรับ Angular เป็นต้น)
เครื่องมือ AI (ที่เหมาะสม)
- สนับสนุนความพยายามในการรีแฟกเตอร์ด้วยเครื่องมือ AI ที่สามารถปรับให้เข้ากับสไตล์และกฎการเขียนโค้ดเดิมได้
- Visual Copilot เป็นเครื่องมือที่มีประโยชน์อย่างยิ่งในการรักษาความสอดคล้องระหว่างการเขียนโค้ดฝั่งฟรอนต์เอนด์
- ช่วยแปลงดีไซน์เป็นโค้ดโดยให้สอดคล้องกับสไตล์การเขียนโค้ดเดิม และใช้คอมโพเนนต์กับโทเคนของ design system ได้อย่างถูกต้อง
บทสรุป
- การรีแฟกเตอร์เป็นส่วนสำคัญของการพัฒนาซอฟต์แวร์ แต่ต้องทำอย่างระมัดระวังโดยคำนึงถึงโค้ดเบสเดิมและพลวัตของทีม
- เป้าหมายของการรีแฟกเตอร์คือการปรับปรุงโครงสร้างภายในของโค้ดโดยไม่เปลี่ยนพฤติกรรมภายนอก
- การรีแฟกเตอร์ที่ดีที่สุดมักมองไม่เห็นจากมุมมองของผู้ใช้ปลายทาง แต่ช่วยให้ชีวิตของนักพัฒนาง่ายขึ้นมาก
- มันช่วยเพิ่มความอ่านง่าย ความสามารถในการบำรุงรักษา และประสิทธิภาพ โดยไม่ทำให้ทั้งระบบสับสนวุ่นวาย
- เมื่อคุณอยากวาง "แผนใหญ่" ให้กับโค้ด ควรถอยออกมาหนึ่งก้าว ทำความเข้าใจโค้ดอย่างถี่ถ้วน พิจารณาผลกระทบของการเปลี่ยนแปลง และค่อย ๆ ปรับปรุงในแบบที่ทีมจะรู้สึกขอบคุณ
- ตัวคุณในอนาคตและเพื่อนร่วมทีมจะขอบคุณแนวทางที่รอบคอบนี้ในการรักษาโค้ดเบสให้สะอาดและดูแลรักษาได้
6 ความคิดเห็น
บางคนก็เริ่มรีแฟกเตอร์กันพรวดพราดทั้งที่ยังไม่มีโค้ดทดสอบด้วยซ้ำ
พอมองไปเรื่อย ๆ ก็เหมือนกำลังไต่เส้นบาง ๆ แบบเสี่ยงอันตรายสุด ๆ เลย ตื่นเต้นดีเหมือนกัน 555
ข้อ 4, "ไม่ทำความเข้าใจโค้ดก่อนรีแฟกเตอร์" เป็นประเด็นที่โดนใจมากจริง ๆ ครับ
เป็นคำพูดที่ชัดเจนมากและเหมือนเป็นพื้นฐานอยู่แล้วนะ 555 แต่การที่สรุปออกมาให้แบบนี้ก็ดูดีมากเลย พอนึกถึงการรีแฟกเตอร์ ผมมองว่าสุดท้ายมันควรจะได้โค้ดที่ดีขึ้น 100% ถ้าไม่ใช่ แบบนั้นก็แปลว่าฝีมือผมถอยหลังลงจากเมื่อวานไม่ใช่เหรอ?
พอเห็นว่ามีการเอาเรื่องที่เหมือนจะเป็นเรื่องปกติมาจัดระเบียบแบบนี้ ก็รู้สึกเหมือนเป็นบทความที่เขียนขึ้นเพราะมีใครสักคนทำไว้เละเทะจนเจ้าตัวหงุดหงิดจริง ๆ เลยนะ 5555
แทนที่จะเป็นวิธีรีแฟกเตอร์ให้เก่งกว่า มันใกล้เคียงกับวิธีเขียนโค้ดให้เก่งในงานจริงมากกว่า..
(เหมือนเป็นโพสต์ที่เขียนด้วยความหัวร้อนเพราะมีจูเนียร์เข้ามาบอกว่าจะรีแฟกเตอร์โปรเจ็กต์เลกาซี แล้วทำให้มันซับซ้อนไปทั่วและสร้างบั๊กขึ้นมา...)
คนที่แยกไม่ออกหรือสับสนระหว่างหน่วยของงานกับหน่วยของโค้ด มักจะทำรีแฟกทอริงแบบนั้นไม่ว่าจะมีประสบการณ์มากหรือน้อยก็ตาม โค้ดที่แก้ไขง่ายและบำรุงรักษาได้ ท้ายที่สุดแล้วต้องทำให้อีกคนที่เข้ามาทีหลังยังสามารถอ่านลำดับการไหลของงานได้ถึงจะถือว่าตอบโจทย์ แต่ก็อดคิดไม่ได้ว่าสิ่งนี้เป็นเรื่องที่ถ้าไม่ได้ผ่านประสบการณ์ทำงานจริงก็อาจไม่มีทางเข้าใจได้จริง ๆ
ผมเคยเจอกรณีหนึ่งที่เป็นสัญญาณของการทำ abstraction มากเกินไปใน React คือทั้งที่ table component ถูก abstract ไว้แล้วโดย UI framework แต่แค่เพราะ schema ของตารางสองตัวดูคล้ายกัน ก็ไปใช้ inversion of control เพื่อสร้างคอมโพเนนต์ใหม่ที่ไม่ได้มีบทบาทอะไรเลย ซึ่งผมคิดว่าควรหลีกเลี่ยงครับ พอเห็นกรณีที่บอกว่าจะใช้ atomic design แล้วสร้างคอมโพเนนต์เปลือก ๆ ยาวสิบกว่าบรรทัดแบบพร่ำเพรื่อ มันทำให้ต้องไล่ดูหลายส่วนเกินไปจนใช้งานลำบากมาก
เดิมทีหลักการ DRY มีเจตนาแค่ว่าอย่าก๊อปปี้โค้ดที่หน้าตาเหมือนกันและทำหน้าที่เดียวกันแบบตรง ๆ แต่ดูเหมือนว่าจะต้องมีคนที่เข้าใจมันผิดอยู่เสมอ หรือว่าคำเตือนที่ว่าอย่าเชื่อ design pattern แบบงมงาย ก็มาจากบริบทแบบนี้หรือเปล่าครับ