บันทึกการ Refactor
Published: 2022-05-25 15:39:59

ในช่วงเดือนที่ผ่านมา ผมมีโอกาสได้เข้าไปช่วยเหลือให้คำแนะนำเกี่ยวกับการ   Refactor ให้กับทีมที่กำลังทำแอปตัวหนึ่งมา เลยเอามาบันทึกและแบ่งปันไว้ตรงนี้ครับ

โดยโจทย์ที่ได้เจอก็คือ Code ถูกเขียนมาสักระยะแล้ว ประมาณ 3-4 เดือน โดยที่ไม่ได้มีการ Apply Design principles ใดๆเลย เนื่องจาก ต้องเผาให้ทันส่งนั่นเอง

ดังนั้น code smell ที่จะได้เจอแน่ๆ ก็คือ God class หรือ class ที่มันใหญ่มากๆ มีการทำงานหลายๆอย่างอยู่ข้างในนั้น บางที่ก็เรียก Monster class หรือ Kaiju class นะ อันนี้ก็แล้วแต่จะสรรหามาเรียกกัน แต่โดยรวมแล้วไม่ต่างกัน คืือมันร้ายกาจมาก เวลามันโมโหขึ้นมาก็อาจจะทำให้โปรเจคล่มได้เลยนะ

ยกตัวอย่างให้เห็นภาพ ตอนแรกที่เราเริ่มทำแอป เพื่อจะแยกส่วนแสดงผมกับส่วนของการประมวลผลหรือควบคุม UI ออกจากกัน ก็อาจจะมี View กับ ViewModel คู่กันแบบ 1:1 ใช่ไหมครับ และในเวลาต่อมา เราก็จะเริ่มเพิ่มfeature ที่ 2,3,4 เข้าไปเรื่อยๆ ในช่วงแรก มันยังเล็กๆอยู่ เราอาจจะยังไม่เห็นปัญหามากนัก เราจะเอาของใส่ใว้ตรงไหนล่ะ ก็ ViewModel ตัวเดิมนั่นไง ต่อมาเราก็เพิ่ม View ขึ้นมาอีกอันนึง แต่ feature มันก็ใกล้ๆกันนะ เราก็เอา feature ของ view ที่สองเอามาใส่กับ ViewModel ตัวเดิม แล้วเราก็ทำอย่างนี้ไปเรื่อยๆๆ หลังจากนั้น พอมีตรงไหนที่อยากจะ reuse feature ใน viewmodel ก็เอาไปเรียกใช้ได้เลย ทำให้ ViewModel ตัวเดียว อาจจะมีการเรียกใช้จากทุกหน้าในแอปก็เป็นได้

ซึ่งมันก็ดูจะสะดวกสบายดี เอาทุกๆอย่าง รวมไว้ใน class ก้อนเดียว เวลาเรียกใช้ก็ง่ายๆเลย ปัญหามันคืออะไรล่ะ 

คำถามคือ มันทำให้งานของเรายากขึ้นเรื่อยๆหรือเปล่า?

งานของโปรแกรมเมอร์คืออออ
1.อ่านโค้ด
กับ 2.เขียน(แก้)โค้ด

เราทำข้อ 1 เพื่อหาจุดที่เราจะต้องแก้แล้วถึงทำข้อ 2 เพื่อให้ software ของเรามันเป็นไปตาม Requirement ถูกมั้ยครับ

ไอ้ตัว God class เนื่องจากมีการทำงานที่อัดแน่นอยู่ข้างในตัวมัน ดังนั้นสิ่งที่ตามมาก็คือปริมาณของโค้ดที่เราต้องอ่าน ซึ่งมันสามารถทำคอมโบกับ code smell อีกตัวที่ชื่อ long method ได้อีก ก้คือ method ที่ยาววววว มากๆ อาจจะเกิน 10-20 บรรทัดได้ และยังมีโอกาสเจอตัวอื่นๆได้อีกเพียบ

ยิ่งปริมาณโค้ดเยอะ ก็ยิ่งใช้เวลานานในการทำความเข้าใจ และเมื่อเวลาผ่านไป เราจะใช้เวลาอ่านโค้ดนานกว่าเขียนโค้ดมากขึ้นไปเรื่อยๆ และนี่ก็คือสาเหตุหนึ่งที่ทำไม productivity ของเหล่าโปรแกรมเมอร์มันลดลงๆ

ปัญหาต่อมาคือ แก้ไปแล้วรู้ได้ยังไงว่ามันทำงานถูก

การเกิด God class ส่วนหนึ่งเกิดจากการที่เราไม่มี unit test
เพราะถ้าเราเขียน unit test ไว้ตั้งแต่เนิ่นๆ เราจะรู้สึกได้ไวมากๆ ว่า class มันเริ่มมีปัญหา มันชักจะเริ่มเขียน test ยาก ทำให้เราต้องกลับมาดู design ของเราว่ามันมีอะไรผิดไป และเราควรจะเริ่มแตกปัญหาออกมาเป็น class เล็กๆ เพื่อให้สามารถเขียน unit test ได้ง่ายๆ ซึ่ง God class ที่ผมเจอมาคือมักจะไม่มี unit test มันจึงเป็นเรื่องที่ต้องออกแรงเยอะมากถ้าเราจะแก้มัน

อันดับแรก ต้องเริ่มที่ understanding เสมอ

ก็คือทำความเข้าใจของที่อยู่ข้างในนั้นก่อน ซึ่งแน่นอนว่าไม่ง่าย สิ่งที่ผมทำก็คือ ถามว่า ไอ้นี่มันทดสอบยังไง ทั้งแบบ manual และเอามาเขียนเป็น acceptance test กับ integration test ก่อน อาจจะดูง่ายๆ แต่ใช้เวลานานมากนะครับ มันคือการลองผิดลองถูก explore มันไปเรื่อยๆ crosss check กับหลายๆคนเลยว่ามันถูกมั้ย จะให้ดีก็เอา QA มา pair ด้วยเลย ซึ่งไม่ค่อยมีโอกาสเท่าไหร่หรอก 

พอเข้าใจโค้ดระดับหนึ่งแล้ว ผมก็จะมองหาจุดที่จะ improve สำหรับ God class ก็คือเราจะแยกชิ้นส่วนมันออกมาเป็น class เล็กๆ ได้อย่างไร

เทคนิคที่ใช้ก็คือมองหา รอยต่อ(Seams) ภายใน class ยกตัวอย่างแบบง่ายๆเช่น กลุ่มของ method หรือ ตัวแปร ที่ไม่ได้มีความเกี่ยวของกัน อันนี้สามารถจับแยกเป็นคนละ class ได้เลย แต่ก็ไม่ได้ง่ายแบบนั้นเสมอไป ตรงนี้ต้องใช้ความชำนาญสักหน่อยครับ และต้องใช้ design principle หรือ pattern ต่างๆเข้ามาแก้ปัญหา

พอหารอยต่อได้แล้ว ก็จะเอามา design แล้วลงมือเขียน unit test แล้วค่อยๆ refactor ตามขั้นตอนนี้ไปเรื่อยๆ

แล้วจะต้อง refactor ไปถึงไหน? อันนี้ก็แล้วแต่ตกลงกัน ยกตัวอย่างเช่น
- Test coverage ถึงค่าที่กำหนด เช่น 90% (Code เกือบทั้งหมด มี unit test)
- ให้ทีมทำ code review แล้วไม่พบ issue ที่ยอมรับไม่ได้

การ Refactor แบบล้มยักษ์แบบนี้ไม่ควรเกิดขึ้นบ่อยๆ หรือไม่ควรเกิดเลย เพราะมันสิ้นเปลืองเพราะเราก็ต้องใช้เวลาของ developer ในการทำ แล้วในมุมของ business มันก็ถือว่าไม่เกิด progress ใดๆ แทนที่จะได้เอาเวลาไปทำ feature ใหม่ๆ กลับต้องเอาเวลามาใช้หนี้ทางเทคนิคเหล่านี้ แต่มันก็เป็นสิ่งที่ละเลยไม่ได้ถ้าจะทำงานกันไปยาวๆ แต่จากประสบการณ์ของผม ยิ่งโปรเจคเร่งรัดมากเท่าไหร่ ก็มีโอกาสเจอมากเท่านั้นแหละ

ดังนั้น ป้องกันไม่ให้มันเกิดจะดีที่สุดนะครับ

**เพิ่มเติม**
Code smells (Code ที่ไม่ดีเป็นอย่างไร)
https://refactoring.guru/refactoring/smells

Refactoring recipes (วิธีการ Refactor แบบต่างๆ)
https://refactoring.guru/refactoring/techniques

Seams (การหารอยต่อใน code)
https://www.informit.com/articles/article.aspx?p=359417&seqNum=2