จัดการกับโค้ดเก่าสักที !
สวัสดีครับ กลับมาอีกแล้วหลังจากที่เคยเขียนถึง เรื่องการ Maintain ซอฟต์แวร์ และ สิ่งที่ได้จากโค้ดเก่า ๆ ในมุมของ First Jobbers ไปแล้ว วันนี้ผมอยากจะเขียนถึงสิ่งที่จะทำให้เราพัฒนาตัวเองมากขึ้นไปอีกขั้นสำหรับคนที่จบใหม่ และผมได้โอกาสแตะโค้ดเก่า ๆ จากงานที่ได้รับมอบหมายเลยคิดถึงเรื่องนี้ขึ้น ที่น่าสนใจคือ ในเรื่องนี้เรามักจะไม่ค่อยได้ยินหรือได้ยินผ่าน ๆ ในช่วงเวลา 4 ปี ที่เราเรียนมา ซึ่งความเข้าใจของแต่ละคนอาจจะไม่เหมือนกัน วันนี้ผมจะมาเขียนในมุมมองของผมที่ได้เรียนมาจาก Course Online และประสบการณ์ต่าง ๆ ให้อ่านกัน โดยเป้าหมายคืออยากให้เปิด keywords นี้สำหรับคนจบใหม่ให้ชัดเจนมากขึ้นครับ
Refactoring คืออะไร
ก่อนอื่นเรามาปรับความเข้าใจกันก่อน โดยคำว่า Refactoring ในบล็อคนี้มีความหมาย 2 รูปแบบคือ
Refactoring (noun) หมายถึงการเปลี่ยนโครงสร้างภายในซอฟต์แวร์ที่ทำให้ง่ายต่อการทำความเข้าใจและง่ายต่อการแก้ไขปรับเปลี่ยน โดยไม่เปลี่ยนพฤติกรรมเดิมจากที่เคยทำได้
Refactoring (verb) เป็นการจัดโครงสร้างของซอฟต์แวร์ใหม่ โดยทำการ Refactoring (noun) อย่างน้อย 1 วิธีมาจัดการ
แปลง่าย ๆ คือเป็นแตกต่างเหมือนกัน เพราะเป็นการแก้ไขโค้ดของเราโดยยังคงการทำงานเหมือนเดิม โดยอาจจะแก้มากกว่า 1 ส่วนประกอบก็ได้
เช่น เราอาจเขียนโปรแกรม Lift ขนของขึ้นมา 1 ชิ้น และใช้วิธี If — else ในการเช็คว่า Lift ตัวนี้อยู่ชั้นอะไร ซึ่งเราได้เขียนชุดทดสอบไว้ แต่เราอาจทำการ Refactoring โดยใช้ state design pattern เนื่องจาก Lift มี State ชัดเจนในการใช้งานเป็นต้น ซึ่ง Lift ยังคงขึ้นลงได้เหมือนเดิม แต่โครงสร้างภายในเปลี่ยนโดยใช้ Design Pattern ครับ
แล้วทำไมเราต้องทำล่ะ ?
ผมเคยพูดถึงเรื่องความสามารถในการ Maintainability ไว้ใน เรื่องการ Maintain ซอฟต์แวร์ ครับ ทั้งหมดเป็นเหตุผลที่เราควรจะ Refactoring โค้ดที่เป็น Product ของเราเพราะหากเรายิ่งพัฒนาซอฟต์แวร์มากขึ้น จำนวนโค้ดของเรายิ่งเพิ่มตาม ซึ่งหากเราไม่ดูแลให้ง่ายต่อการเข้าใจ จะทำให้คุณภาพแย่ลงตามความซับซ้อนที่มากขึ้น ทำให้สุดท้าย การแก้ไขจะยากมากขึ้นและทำให้ต้องทำใหม่ทั้งหมด ซึ่งสามารถอ่านเพิ่มเติมได้ในลิ้งที่ใส่ไว้ครับ
ลอง Role Play เล่น ๆ แล้วนึกภาพตามนะครับ
นึกภาพว่าเราเป็นเชฟร้านอาหารแห่งหนึ่ง เราเปิดร้านมาได้ 8 เดือน และมีลูกค้าอยู่ประมาณหนึ่ง
แต่ร้านเราเริ่มสกปรกและในฐานะเชฟ เราเห็นว่าเราควรจะปิดร้านประมาณ 1 อาทิตย์เพื่อทำความสะอาดให้ร้านมีคุณภาพดี มีบรรยากาศน่าเข้ามาทานอาหาร และดูสะอาด แต่เราจะบอกผู้จัดการยังไงหากเราต้องปิดร้านถึง 1 อาทิตย์เพื่อทำความสะอาดโดยขาดรายได้ ? แถมส่งผลให้ลูกค้าผิดหวังหากต้องการจะมาทานที่ร้านเรา โดยอาจจะคิดว่า เฮ้ย กระบวนการของร้านห่วยถึงต้องปิดร้านตั้ง 1 อาทิตย์เพื่อทำความสะอาดเลยหรอ ?
ในเมื่อความเป็นจริงเราทำแบบนี้ไม่ได้ แสดงว่าคำว่าทำไปก่อนเดี๋ยวค่อยมา Refactoring ให้ดีทีหลังโดยขอเวลาประมาณ 2 Sprint มันแทบจะเป็นไปไม่ได้เลย
แต่ก็อาจจะเกิดขึ้นได้ในบางกรณี
แล้วเมื่อไหร่ที่ควรจะ Refactor
- คำที่ดังมากๆ ในช่วงนี้นั่นคือ Test Driven Development (TDD) ครับ เนื่องจากตัว TDD มีกระบวนการ 3 แบบอยู่นั่นคือ Red — Green — Refactor ซึ่งถ้าเราเขียนด้วยหลัก TDD เราจะมีช่วงเวลาให้ทำโดยปกติอยู่แล้วครับ นั่นคือเขียน Test ให้ Fail จากนั้นเขียนโค้ดให้ผ่าน Test และทำการแก้ไขให้ดีทั้ง โค้ดที่เป็น Logic และ โค้ดสำหรับ Test
- อีกคำที่ผมไม่คุ้นหูคือ Pain Driven Development (PDD) หลักการคร่าว ๆ คือเราจะ Refactor เมื่อ Design ปัจจุบันไม่ตอบโจทย์เราอีกต่อไป เช่น เมื่อเราต้องทำระบบ User Management โดยเริ่มต้นเราอาจจะทำแค่ Login ด้วย Email และ Password แต่วันหนึ่งมีความต้องการเข้าสู่ระบบด้วย Facebook ทำให้เราอาจจะต้อง Refactoring ระบบ User เก่าให้รองรับข้อมูลที่มาจาก Facebook ด้วย
- เมื่อเราได้รับงานแก้บัค แล้วเราเห็นว่าโค้ดจุดนี้มันไม่ดี อ่านยากและเข้าใจยาก นั่นแปลว่าถึงเวลาที่คุณจะต้องจัดการครับ เมื่อเราเห็นปัญหาไม่ควรจะนิ่งเฉย เราต้องทำอะไรกับมัน หากไม่เคยมี Test อาจเริ่มต้นด้วยการเขียน Test จาก Behavior เดิม และค่อย ๆ Refactoring ไปครับ แต่ถ้ามี Test อยู่แล้ว เราก็เพียงเพิ่ม Testcase ที่เป็นบัคของเรา และไล่ Refactoring โค้ดเดิมให้ดูแลง่ายขึ้นครับ
- ช่วงเวลาแห่งการรีวิวโค้ด ไม่ว่าเราจะเป็นผู้รีวิวแล้วเห็นปัญหา เราอาจจะฝากให้คนที่ทำอยู่แก้ไขไปได้เพื่อให้โค้ดคุณภาพดีมากขึ้นครับ หรือถ้าเราเป็นคนที่ทำอยู่ เราก็รับคำแนะนำและทำการแก้ไขให้ดี หรือเราอาจจะแก้เองและให้คนที่เคยทำช่วยดูก็ได้ครับว่าผิดพลาดตรงไหนหรือไม่
Boy Scout Rule
เป็นกฏเล็ก ๆ น้อย ๆ ที่ควรจำไว้ในการแก้โค้ดเก่าหรือเมื่อ Refactoring ครับ สรุปคร่าว ๆ คือ เราต้องทำให้โค้ดสะอาดกว่าตอนที่เราเจอมันครั้งแรก ให้เหมือนกับลูกเสือที่รักษาพื้นที่ตั้งแคมป์ให้สะอาดกว่าเดิม หลังจากตั้งแคมป์ไปแล้ว
ไม่ใช่ทุกเวลาที่เราควรจะ Refactoring
แน่นอนครับ เมื่อมีเวลาที่เหมาะ ก็ย่อมมีเวลาที่ไม่เหมาะเช่นกัน เบื้องต้นมีดังนี้ครับ
- เมื่อโค้ดปัจจุบันยังทำงานไม่ได้ พูดง่าย ๆ ก็คือของที่กำลังทำอยู่ยังใช้ไม่ได้เลย ยิ่งแก้ไปเรื่อย ๆ จะยิ่งพังครับ ฉะนั้นเราควรทำให้โค้ดใช้ได้อย่างน้อยก็ขอแค่หนึ่งส่วนก่อนก็ได้ครับ แล้วหลังจากนั้นค่อย ๆ ทำให้ฟังก์ชันเก่าใช้งานได้เหมือนเดิม และค่อย Refactoring ครับ — Made it work, Made it right, Made it good
- เมื่อมี Technical Debt เยอะเกินไป อาจจะเหมาะกับการ Rewrite มากกว่า Refactoring ครับ ยิ่งถ้าโค้ดเดิมไม่สามารถเขียน Test ได้หรือซับซ้อนเกินกว่าจะแก้ เราก็ไม่ควรเสียเวลาไป Refactoring ครับ เพราะถ้าทำใหม่อาจจะใช้เวลาที่เร็วกว่า แต่สิ่งที่ทำใหม่ก็ควรจะออกแบบให้ดีครับ เจ็บแล้วต้องจำ และสามารถอ่านเรื่อง Technical Debt ได้ในบล็อคสิ่งที่ได้จากโค้ดเก่าครับ
- เมื่อใกล้ Deadline ยิ่งไม่ควรอย่างยิ่ง จากที่บอกไปคือ Made it work ให้ได้ก่อนครับ อาจจะย้อนแย้งจากที่บอกไปคือไม่ควรทำๆ ไปก่อนแล้วค่อยมา Refactoring แต่งานต้องเสร็จครับ และเราไม่มีเวลาแล้ว ซึ่งต้องอย่าลืมว่าคุณค่าของการ Refactoring มีค่ากับ Developer เพียงแต่กระทบต่อความเร็วของการพัฒนาในอนาคต กรณีนี้ให้นึกถึง Technical Debt ที่เราออกแบบแล้วแต่ยอมรับกับสิ่งที่ทำลงไปว่ามันไม่ใช่สิ่งที่ดีที่สุดและต้องหาเวลากลับมาแก้ต่อไปครับ
“Other than when you are very close to a deadline… you should not put off refactoring because you haven’t got time.”
Martin Fowler, Refactoring
กระบวนการ Refactoring
สำคัญมากก่อนจะ Refactoring อยากแนะนำว่าเราควรใช้ Version Control นะครับ เพราะหากเราทำผิดพลาด เราสามารถย้อนเวอร์ชั่นไป 1 Step เพื่อดึงโค้ดที่ใช้งานได้กลับมา ไม่งั้นงานเราอาจจะหายทั้งหมด !
Step มีดังนี้ครับ
- Commit โค้ดของเราไปเก็บไว้สักที่ อาจใช้ Version Control ตามบริษัทตัวเองอยู่แล้วหรือใช้ยี่ห้อดัง ๆ เช่น Github, Gitlab หรืออื่น ๆ แล้วแต่สะดวก อาจจะเริ่มทำโดยการแตก Branch แยกออกมาก็ได้หากทำงานร่วมกันหลายคน
- Verify Existing ให้ตรวจสอบว่าการทำงานเดิมยังใช้ได้อยู่หรือไม่ โดยอาจจะทำ Manual Test หรือ Automated tests ก็ได้ครับเพื่อความรวดเร็ว เนื่องจากสิ่งสำคัญคือของเดิมจะต้องใช้งานได้ ทำยังไงก็ได้ให้มั่นใจว่าของเดิมใช้งานได้
- Apply Refactoring เมื่อเราตรวจสอบแล้ว เราจึง Apply ให้สิ่งที่เรา Refactoring ไปสู่ Code ชุดหลัก โดยอาจใช้การ Merge Branch ในฟีเจอร์ของ Version Control ช่วยครับ
- Confirm คือการนำสิ่งที่ Apply ไปแล้วมาทดสอบอีกรอบเพื่อความมั่นใจว่าสิ่งที่เราทำไปจะต้องใช้งานได้เหมือนเดิมโดยผ่านการ Run Tests อีกรอบครับ หากไม่มีปัญหาก็ถือว่าเสร็จกระบวนการ
หากเริ่มทำใหม่ เราอาจจะแบ่งแบบง่าย ๆ ก็ได้ครับคือให้มี 3 version ประกอบไปด้วย
- First Draft คือเวอร์ชั่นที่ทดลองทำแบบมั่ว ๆ โค้ดแย่ ๆ ทดลอง solution
- First Revision คือโค้ดที่เริ่มปรับแก้ให้ดีแล้ว มีการ Apply Refactoring
- Final Version เป็นเวอร์ชั่นที่ใช้งานได้จริงผ่านการ Refactoring แล้ว
โดยแต่ละ Version เราจะรันกระบวนการซ้ำ ๆ ข้างต้นไปเรื่อย ๆ ร่วมกันด้วยก็ได้จนถึง Final Version ที่เรามั่นใจจากการ Run Tests ครับ โดยที่สำคัญคือต้องมี Test โดย Test จะเน้นที่ Behavior ของระบบครับ
เราจะเริ่มจัดการโค้ดเก่ากันได้หรือยัง ?
จะเห็นว่าสิ่งสำคัญคือเราต้องมีชุดทดสอบ ส่วนตัวผมพยายามเขียนเทสเข้าไว้ เพื่อให้มั่นใจว่าเมื่อเราแก้อะไรไป มันจะต้องทำงานได้อย่างที่คาดหวังไว้ และทำงานให้เสร็จไวๆ เพื่อให้มีเวลา Refactoring โค้ดที่ทำไว้ เพื่อให้อนาคต คนที่มารับช่วงต่อจะมีความสุขเวลาอ่านโค้ดของเรา และยังส่งผลต่อองค์กรด้วยเนื่องจากเราเขียนโค้ดที่ดีมีคุณภาพที่ทำให้พัฒนาซอฟต์แวร์ได้อย่างรวดเร็วและดูแลต่อได้ครับ
งั้นวันนี้ ทุกคนลองเริ่มต้นเขียน Test และลอง Refactoring โค้ดอย่างมั่นใจกันนะครับ
ถึงเวลาจัดการโค้ดเก่ากันสักที !
ขอบคุณ
- ขอบคุณข้อมูลความรู้จาก Course ใน Pluralsight
ชื่อว่า Microsoft Azure Developer: Refactoring Code
สามารถตามไปเรียนเพิ่มเติมกันได้ครับ - ขอบคุณทาง depa และ คณะเทคโนโลยีสารสนเทศ มจธ.
ที่มอบทุนการศึกษาเพชรพระจอมเกล้าเพื่อพัฒนาเทคโนโลยีและนวัตกรรมดิจิทัล และทำให้ได้ความรู้เพิ่มสำหรับใช้ทำงานจริงครับ
เรื่องนี้อาจจะเป็นเรื่องสุดท้ายเกี่ยวกับการดูแล Software แล้ว
เรื่องต่อไปจะเป็นเรื่องอะไร ขอให้ติดตามตอนต่อไป ขอบคุณครับ 🙏