มีคำถามหนึ่งที่น่าสนใจจากการไปร่วมงาน meetup ของกลุ่ม Thailand PHP User Group
คือ เราจะเขียน unit test สำหรับทดสอบ Legacy code อย่างไรดี ?
โดยสิ่งที่ผมตอบไปคือ วิธีการ Golden master และ ข้อมูลตัวอย่าง (Sampling data)
มาดูกันว่ามันเป็นอย่างไร

เมื่อใดก็ตามที่ต้องมาทำการแก้ไข code เดิม
ส่วนใหญ่รวมทั้งผมก็ต้องคิดว่า งานเข้าล่ะ !!

เนื่องจาก code ส่วนใหญ่มักถูกเรียกว่า Legacy code
มันคือ code ที่ดูแย่ เข้าใจยาก ทั้งอ่านยาก และ มีความซับซ้อนสูง
ทำให้เราไม่เข้าใจทำงานจริงๆ มัน

คำว่า Legacy code ถูกให้คำจำกัดความไว้หลายความหมาย

Michel Feathers
Legacy code คือ code ที่ไม่ unit test

หรือบางคนอาจจะบอกว่า
Legacy code คือ code ที่มันมีคุณค่าต่ององค์กร และคุณกลัวมัน

ซึ่งมันเป็นเรื่องจริงทั้งหมดเลย ว่าไหม ?

ถ้า code ชุดนั้นมีคุณค่าหรือทำรายได้ให้กับองค์กร
เมื่อคุณเข้าไปแก้ไข คุณจะกลัวมัน เพราะว่า ความเสี่ยงสูง

แต่กับ Legacy code ที่ไม่ได้มีคุณค่าอะไรต่อองค์กรมาก
คุณกลับไม่กลัวเมื่อต้องไปแก้ไข

ดังนั้นถ้า code ชุดใดก็ตามที่มันมีคุณค่า
เราในฐานะนักพัฒนาต้องเคารพมันด้วยเช่นกัน
เราต้องไม่ทำให้ code ชุดนั้นมันน่ากลัว และยากต่อการแก้ไข

แต่ว่า Legacy code นั้นมักน่ากลัวและยากต่อการแก้ไขเสมอ
แล้วเราจะทำอย่างไรดีล่ะ ถึงจะอยู่กับมันได้อย่างมีความสุข ?

สิ่งแรกก็คือ คุณต้องหาจุดที่ปลอดภัยที่สุด …

ทำอย่างไรดีล่ะ ?

ก็เริ่มด้วยการเขียน unit test ในส่วนที่เราต้องการแก้ไข หรือทำการ refactoring ไงล่ะ
เพื่อทำให้เรามั่นใจมากขึ้น และ รู้สึกปลอดภัย

แต่อีกใจหนึ่งเราก็บอกว่า unit test นั่นพอเขียนได้นะ
แต่ในระดับ integration test ล่ะ เพราะว่ามันน่ากลัวมากเลยนะ
เพราะว่า unit test มันทดสอบแต่ส่วนเล็กๆ เท่านั้น
เมื่อไปถึงระดับ integration แล้วระบบมักจะพังตลอด

แล้วเราจะทำอย่างไรกันดี ?

ลองกลับมาดูกันหน่อยว่า ทำไมเราถึงกลัว integration test กันนักหนา ?

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

ข้อต่อมา
คุณมีการทดสอบระบบ ซึ่งสนใจ test coverage หรือไม่
เพื่อเพิ่มความเชื่อมั่นว่า code มีคุณภาพ

ก่อนอื่นเราต้องเข้าใจว่าเราสร้าง unit test ขึ้นมาเพื่อตรวจสอบว่า

  • ถ้าเปลี่ยนแปลง code แล้วมันจะกระทบส่วนไหนบ้าง
  • เพื่อทำให้แก้ไขปัญหาได้อย่างรวดเร็ว
  • เพื่อทำให้รู้ว่าค่าใช้จ่ายและเวลาในการแก้ไขเท่าไร
  • เพื่อเป็นการจัดการเรื่องความเสี่ยงต่างๆ

จากทั้งสองข้อที่ผ่านมา ถ้าเอามาใช้ใน Legacy code แล้ว คิดว่า มันจะดีหรือไม่ ?

ถ้ามองในแง่การทำงานถ้าทำได้ ก็น่าจะดีนะ…
เพราะว่ามีการทดสอบต่างๆ ในระบบ และมี test coverage ที่ดี
สามารถช่วยเราเรารู้ว่า แต่ละการเปลี่ยนแปลงมันกระทบอะไรบ้าง

แต่ในโลกการทำงานจริงๆ นั้นคงเป็นเรื่องที่ยากมากๆ
และคิดว่ามันไม่น่าจะเหมาะกับเหตุการณ์ที่เราพบเจออยู่…

ดังนั้น เรามาดูว่า มันมีวิธีการใดที่ช่วยทำให้เราจัดการ Legacy code ได้บ้าง

บางคนบอกว่า เราก็อยู่เฉยๆ ไงล่ะ อย่าไปยุ่งกับมันเลย !
ปล่อยให้มันทำงานของมันไปแบบเดิมนั่นแหละ
แต่ในฐานนะนักพัฒนาอย่างเรา ต้องมีแนวคิดที่ดีกว่าไม่ทำอะไรบ้างสินะ !!!

โดยสิ่งที่เราพอจะทำได้ และ ช่วยเพิ่มความมั่นใจในการจัดการกับ Legacy code
รวมทั้งลงแรงไปไม่มากเท่าไร วิธีการนั้นก็คือ Golden Master และ Sampling data

Golden Master
จะช่วยให้เราสามารถดูพฤติกรรมการทำงานของระบบเมื่อเราทำการเปลี่ยนแปลงบางอย่าง
โดยที่เราไม่สามารถที่จะไปเขียน unit test เพื่อตรวจสอบส่วนการทำงานต่างๆ ได้
หรือยากต่อการตรวจสอบผลลัพธ์ เช่นรูปภาพ เสียง เป็นต้น

ดังนั้นถ้าเราต้องการเข้าใจพฤติกรรมการทำงานของระบบ
ต้องมีการเตรียมข้อมูลเข้า (input) ในรูปแบบต่างๆ
ก่อนที่เราจะเตรียมข้อมูลได้ เราต้องรู้ว่าจะทำงานกับส่วนไหนของระบบก่อนนะ
และเมื่อส่งข้อมูลเข้าไปประมวลผลผ่านส่วนงานที่เราต้องการแล้ว
จะได้ผลลัพธ์ (output) ที่เราต้องการ
อาจจะอยู่ในรูปแบบของ

  • รูปภาพ
  • ข้อมูลในฐานข้อมูล
  • text files
  • logging

สิ่งที่เราต้องทำต่อไปก็คือ การเก็บข้อมูลผลลัพธ์จากการทำงานไว้

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

จากนั้นจึงเริ่มแก้ไข code หรือ refactor code แต่ละส่วนไปเรื่อยๆ
โดยจะต้องมี unit test เสมอๆ
และเมื่อทำในแต่ละขั้นตอนเรียบร้อย
ต้องทดสอบด้วยข้อมูลเข้าตัวเดิมทุกๆ ครั้ง
เพื่อตรวจสอบผลการทำงานโดยรวมของระบบว่ายังทำงานเหมือนเดิม

ซึ่งผลลัพธ์ที่เราได้มาตั้งแต่แรกเพื่อนำมาใช้นี่แหละ ที่เรียกว่า Golden Master

แต่ถ้าการทดสอบด้วย Golden Master มันทำงานช้า

สิ่งที่ตามมาก็คือ feedback loop ที่ช้า มันจะทำให้เรากลัว integration test อีกแล้ว
ดังนั้น เราจะแก้ไขให้มันเร็วขึ้นได้อย่างไรล่ะ ?

แนวคิดแรกเลยก็คือ การ refactor code ไงล่ะ
เพื่อแบ่งการทดสอบเป็นส่วนเล็กๆ
แต่ในสถานการณ์ตอนนี้ ไม่น่าจะเหมาะสมเท่าไร
ดังนั้นวิธีการที่น่าจะเป็นไปได้ก็คือ การเลือกข้อมูลตัวแทน (Sampling data)
ที่คิดว่าเป็นชุดข้อมูลที่ดีที่สุด ที่ครอบคลุมกรณีสำคัญต่างๆ ให้มากที่สุด
เพื่อทำให้เวลาการทดสอบน้อยลง และ ยังทำให้เรายังคงสนุกสนานกับมัน

ในการเลือกชุดข้อมูลตัวแทนนั้นสามารถ นำแนวคิดทาง testing มาใช้ได้ เช่น

  • EP (Equivalence Partitioning)
  • BVA (Boundary Value Analysis)

แต่ถ้าในบางครั้ง เราไม่รู้เลยว่ารูปแบบของข้อมูลเข้าเป็นอย่างไร
ก็อาจจะต้องใช้การสุ่มมาช่วยด้วย (Random data)
หรือในบางกรณีอาจจะต้องใช้หลายๆ เทคนิคมาช่วยกันก็เป็นได้เช่นกัน

สิ่งที่แนะนำก็คือ

ขนาดของข้อมูลตัวแทนกลุ่มนั้น จำเป็นต้องดูเวลาของการทำงานด้วย
ว่ามันสอดคล้องกับการพัฒนาหรือไม่ หรือส่งผลต่อการพัฒนาอย่างไร
ส่วนใหญ่แล้วขนาดของข้อมูลจะแปรผันตรงกับความกลัวในส่วนงานนั้นๆ

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

และนี่คือสิ่งที่ผมตอบไปเกี่ยวกับ การจัดการกับ Legacy code ครับ

Reference Website
http://code.tutsplus.com/tutorials/refactoring-legacy-code-part-1-the-golden-master–cms-20331
http://craftedsw.blogspot.com/2012/11/testing-legacy-code-with-golden-master.html
http://chrismelinn.wordpress.com/2013/04/12/using-the-golden-master-technique-to-test-legacy-code/
http://blog.adrianbolboaca.ro/2014/05/legacy-coderetreat-golden-master/
http://blog.thecodewhisperer.com/2014/09/28/surviving-legacy-code-with-golden-master-and-sampling/