TechnicalDebt
เนื่องจากเห็นว่ามีงานที่น่าสนใจคือ Legacy Code Retreat 
ดังนั้นจึงลองไปดูว่างานนี้มันเป็นอย่างไร
รวมทั้งยังมีเทคนิคต่างๆ ในการจัดการกับ Legacy code อีกด้วย
ดังนั้นมาลองดูกันว่าเราจะเริ่มต้นทำอะไรได้บ้าง

เริ่มต้นต้องรู้จัก Legacy Code ก่อน

คำนี้มีหลายความหมาย แต่ในความคิดผม Legacy Code มันก็คือ

  • Code ที่มันเก่าๆ
  • Code ที่มันมีโครงสร้างน่าเกลียด
  • Code ที่มีความซับซ้อนสูง
  • Code ที่มันพันกันวุ่นวายไปหมด
  • Code ที่ไม่มี test
  • Code ที่ไม่รู้ได้เลยว่าจริงๆ แล้วมันทำอะไรกันแน่
  • Code ที่แก้ไขและเปลี่ยนแปลงยาก

แต่โดยหลักๆ แล้วมันคือ Code ที่แก้ไขและเปลี่ยนแปลงยากนั่นเอง

คำถามต่อมาก็คือ แล้วเราจะทำอย่างไรให้ code มันเปลี่ยนแปลงได้ง่ายๆ ล่ะ ??
คำตอบในมุมมองของผมคือ มันจะเป็นไปไม่ได้เลยถ้าคุณไม่มี code ของ test มาช่วย

ดังนั้นพยายามทำให้ code สามารถทดสอบได้ง่ายและรวดเร็ว
มาดูกันว่า เราสามารถทำได้อย่างไรบ้าง

โดยในแต่ละข้อผมไม่ได้คิดเองนะครับ
แต่มีชาวบ้านเข้าคิดไว้ ผมเอามาแปลตามความเข้าใจและเท่าที่ทำมา
ซึ่งที่มาต่างๆ อยู่ในส่วนของ Reference Website ด้านล่างสุดนะครับ

1.Golden Master

คือวิธีการหนึ่งในการทำความเข้าใจการทำงานของ Legacy code
แต่ก่อนอื่น ต้องให้เราเข้าใจก่อนว่า Legacy Code นั้น
เราไม่รู้เรื่องมันหรอก ทำใจซะเถอะ

แต่จะเข้าใจมันก็ไม่ยาก มีวิธีการดังนี้

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

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

วิธีการนี้เรียกว่า Golden Master Generator

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

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

2. ทำการแก้ไข code ด้วยการค้นหาสิ่งเล็กๆ ที่เรียกว่า Magic Constant และ Magic String

เป็นวิธีการเริ่มต้นที่ง่ายสุดๆ

ตัวอย่างของ Magic String เช่น

if(true case 1) return “XXX”
else if(true case 2) return “YYY”
else if(true case 3) return “ZZZ”
else return “AAA”

สังเกตได้ว่า ค่าที่ return นั้น เราไม่รู้เลยว่ามันหมายถึงอะไรบ้าง
ดังนั้นแก้ไขด้วยการสร้างตัวแปรขึ้นมาเก็บซะ เช่น

String returnForCase1= “XXX”
String returnForCase2= “YYY”
String returnForCase3= “ZZZ”
String defaultValue = “AAA"

if(true case 1) return returnForCase1
else if(true case 2) return returnForCase2
else if(true case 3) return returnForCase3
else return defaultValue

ถ้าตามหลักการของการ Refactoring Code ตามหนังสือ Refactoring: Improving the Design of Existing Code
ของ Martin Folwer กล่าวไว้ว่า มันคือการประกาศตัวแปรเพื่อลด code ที่ซ้ำซ้อน
ในหนังสือเล่มนี้ยังแนะนำวิธีการ Refactoring ไว้อีกเพียบ แนะนำให้อ่านและศึกษาไว้เลยครับ

เมื่อทำการเปลี่ยนแปลง code แล้วก็อย่าลืมทำการทดสอบจาก test ที่เราสร้างไว้แล้ว
ว่ายังสามารถทำงานได้อย่างถูกต้องหรือเหมือนเดิมหรือไม่

ตัวอย่างของพวกค่าคงที่

if( random(0,9 ) {
}

สามารถเปลี่ยนแปลงไปเป็น

int minimumValue = 0
int maximumValue = 9
if( random(minimumValue,  maximumValue ) {
}

จะเห็นได้ว่าการลดพวก  Magic ต่างๆ หรือเป็นการเข้ารหัสต่างๆ ไว้
ช่วยทำให้ code เราอ่านและเข้าใจได้ง่ายขึ้นเยอะเลย

แต่จากวิธีการนี้ จะทำให้เกิดพวก duplicate code หรือ code ที่ซ้ำซ้อน
ซึ่งเราจะทำการแก้ไขต่อไป ในตอนนี้เราทำไปแบบ step-by-step ไปก่อน

3. มาดูในส่วนของเงื่อนไขที่มันยืดยาว หรือ ซับซ้อน

ลองไปดูใน code ของเราหน่อยสิว่า มี method ที่มันยาวๆ บ้างไหม ?
มี if-else  ที่มันเยอะๆ บ้างไหม ?
หรือมีชุดคำสั่งไหนที่มันดูยาวๆ เงื่อนไขเยอะๆ บ้างไหม ?

พอหาเจอแล้ว ลองอ่านว่าเข้าใจมันไหม ?
หรือเข้าใจยากไหม ?

ถ้ามันยาก มีวิธีแนะนำง่ายๆ สั้นๆ คือ ทำให้มันสั้นสิ ….

หลายคนบอกว่า งั้นก็รวมไว้บรรทัดเดียวเลยสิ จบทีเดียว
ใช่จบ ซึ่งจบชีวิตแน่นอน  ดังนั้นแนะนำให้แยกการทำงานออกให้ชัด
โดยใช้ยึดแนวคิดของ SRP ( Single Responsibility Principle )

การเขียน code นั้น เหมือนกับการเขียนหนังสือหรือบทความ
ที่ต้องมีการแบ่งเป็นย่อหน้า มีช่องไฟ
แบ่งประโยค เว้นบรรทัด
เพื่อให้สามารถอ่านได้ง่าย

และจงจำไว้ว่าในการเขียนโปรแกรมนั้น
เราใช้เวลาในการอ่านมากกว่าการเขียนนะ
ดังนั้นถ้าจะลดเวลาการอ่านลง รู้นะว่าต้องทำอย่างไร ….

ตัวอย่างของเงื่อนไขที่ซับซ้อน

if( random(minimumValue,  maximumValue) == wrongAnswer ) {
}

เราสามารถทำการ refactoring  2 แนวทางหลัก
แนวทางแรก คือ สร้างตัวแปรขึ้นมา เพื่อรับผลของเงื่อนไขนี้ คือ

boolean isWrongAnswer = random(minimumValue,  maximumValue) == wrongAnswer
if( isWrongAnswer ) {
}

แนวทางที่สอง คือ สร้างเป็น method ใหม่ไปเลย คือ

if( isWrongAnswer( minimumValue,  maximumValue,  wrongAnswer  ) ) {
}

boolean isWrongAnswer( int minimumValue,  int maximumValue, int  wrongAnswer ) {
return random(minimumValue,  maximumValue) == wrongAnswer
}

ตัวอย่างอื่นๆ ก็มีบ้าง เช่น

if( number % 2 != 0 ) {}

สามารถ extract ออกมาเพื่อสร้าง method  isOdd() ได้ดังนี้

boolean isOdd(number) {
return number % 2 != 0
}

if( isOdd( number ) ) {
}

มาถึงตรงนี้ Legacy code ของเราเริ่มดีขึ้นเรื่อยๆ
สามารถอ่านง่ายขึ้น เข้าใจง่ายขึ้นนะ

เมื่อแก้ไขเสร็จ ก็อย่าลืม run test จากที่ทำไว้ในข้อที่ 1 นะครับ

4. เริ่มเขียน Unit Test กันบ้าง

หลังจากที่เราทำความเข้าใจกับ Legacy code มาพอหอมปากหอมคอแล้ว
เรามาเริ่มเขียน unit test ในส่วนต่างๆ กันบ้าง

แล้วจะเริ่มตรงไหนดีล่ะ ?
มันเป็นปัญหา classic สุดๆ …

เริ่มจากส่วนที่ต้องไปเกี่ยวข้องกับส่วนอื่นๆ น้อยที่สุด เช่นพวก unitility, helper ทั้งหลาย
เราสามารถเข้าไป refactoring code ได้เลย
แต่จงจำไว้ว่า refactoring มันคือการเปลี่ยนโครงสร้างของ code
ไม่ใช่เป้นการเปลี่ยนพฤติกรรมของ code นะครับ ไม่งั้นจะซวยเอาได้นะ

เมื่อสร้าง unit test แล้วก็ run มันด้วยล่ะ
ถ้าผ่านแล้ว ให้ทำการ run test จากข้อที่ 1 ด้วยนะ

ต่อจากนั้นเราเริ่มหา code ส่วนอื่นๆ เพื่อเขียน unit test ทดสอบ
แน่นอนว่าจะต้องมี dependency เยอะอย่างแน่นอน
ซึ่งคงหนีไม่พ้นการใช้ Test Double มาช่วย
ถ้ายังไม่รู้ว่า Test Double คืออะไร อ่านเพิ่มเติมได้จาก blog เรื่อง   Little Mock

ในการ refactoring code นั้นให้ทำทั้ง code ของ test และ production นะครับ
ส่วน method ที่เพิ่มเข้ามาใหม่นั้น ต้องมี unit test เพื่อทดสอบเสมอนะ
จะทำให้ method นั้นไม่เป็น Legacy code อย่างที่เคยเป็นมา

5. ใส่แนวคิดต่างๆ เข้าไปมากขึ้น

ตัวอย่างเช่นแนวคิดด้าน OOP เช่น Abstract class, Inheritance เป็นต้น
แนวคิดของ Design pattern ต่างๆ
สามารถนำมาช่วยให้ code อ่านง่ายขึ้น และทดสอบง่ายขึ้น
แต่การเลือกใช้มันต้องมีเหตุผล มีที่มาที่ไปนะครับ
ซึ่งตรงนี้ต้องใช้เวลาในการศึกษาพอสมควร แต่ก็ไม่ได้ยากเกินไปนัก
ส่วนผมก็กำลังศึกษาอยู่เช่นเดียวกัน

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

การจัดการกับ Legacy Code นั้นต้องรักษาแนวคิดที่ว่า
เมื่อเราเจอ code ที่ไม่ดี ให้เราทำการแก้ไขทันที
หรือเมื่อเราออกมาจาก code นั้นต้องมั่นใจว่า code มันจะดีขึ้นกว่าเดิม ไม่มากก็น้อย
และไม่ทิ้งภาระไว้ให้กับลูกหลานนะ …

ฝากไว้ให้ลอง

ตัวอย่าง code ที่ใช้ในงาน Legacy Code Retreat

Reference Website
Refactoring Legacy Code Part 1
Refactoring Legacy Code Part 2
Refactoring Legacy Code Part 3
Refactoring Legacy Code Part 4
Refactoring Legacy Code Part 5