Technical Debt คืออะไร แล้วเมื่อไหร่เราควรจะ Refactor มันหละ

Sakul Montha
3 min readMar 27, 2022

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

Technical Debt

Technical Debt แปลเป็นภาษาไทยได้ว่า “หนี้ทางเทคนิค” แล้วหนี้ทางเทคนิค มันเป็นยังไงกันนะ

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

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

ผลกระทบที่ตามมาก็คือ Deliver ช้า, Deliver แล้วมีบัคติดออกมาเรื่อย ๆ, Deliver ไม่ทัน, Burn out, หรือในบางกรณีสะสมเอาไว้จน Application ทนไม่ไหวระเบิดออกมาเป็น โกโก้ครั้ช

Cause of Technical Debt

Business pressure: บางครั้ง Business ก็อาจจะ Force คุณให้ออก Feature ให้ได้ไวที่สุดก่อนที่มันจะเสร็จสมบูรณ

Lack of understanding of the consequences of technical debt: ขาดความเข้าใจในผลลัพธ์ของ “หนี้ทางเทคนิค” บางครั้งหัวหน้าของเรา หรือคนในองกรณ์ อาจจะยังไม่เข้าใจว่า หนี้ทางเทคนิคนั้นมี “ดอกเบี้ย” ที่ต้องจ่าย ตราบใดก็ตามที่มันยังอยู่ มันก็จะยิ่งทำให้การ Develop ช้าลงเรื่อย ๆ

Failing to combat the strict coherence of components: การ Develop Project ที่ไม่ได้มีการแยก Individual Module ทำให้การเปลี่ยนแปลงส่วนใดส่วนหนึ่งของโปรแกรม มีผลกระทบต่อส่วนอื่น ๆ ทำให้การพัฒนาทีมทำได้ยาก เพราะการแยกงานกันของสมาชิกภายในทีมก็ทำได้ยากเช่นกัน

Lack of Tests: เมื่อไม่มีการทำ Test ทำให้เราขาด Immediate feedback ทำให้เกิดความเสี่ยง ในกรณีที่เลวร้ายที่สุดเมื่อเราแก้ไข หรือเพิ่มเติม Features ลงไปใช้จริงโดยไม่มีการ Test ผลที่ตามมาอาจจะเป็นหายนะ เช่นการ Push notification อาจจะส่งไปให้กับลูกค้าหลายล้านราย หรือยิ่งไปกว่านั้น อาจจะไปลบข้อมูลของลูกค้า ทำให้เสียหายมากกว่านั้นก็เป็นได้

Lack of Documentation: หากไม่มีเอกสาร การที่เรามีคนเข้ามาใหม่ในทีม ก็อาจสามารถทำงานได้ช้าลง หรืออาจจะทำให้กินเวลามากกว่าเดิม

Lack of Interaction between Team Members: หากไม่มีการ Share Knowledge ให้คนในองกรณ์ เมื่อมี Process หรือ Project อะไรใหม่ ๆ ขึ้น อีกคนก็อาจจะยังไม่ทราบ เมื่อมีคนใหม่ ๆ เข้ามา คนที่อยู่มาก่อนก็อาจจะสอนสิ่งเก่าเข้าไปให้คนใหม่ ซึ่งอาจจะทำให้คนใหม่เข้าใจในสิ่งที่มันเก่าไปแล้ว อันนี้ยกตัวอย่างเช่น Team A เปลี่ยน Lib แล้ว แต่ไม่มีการ Announce ให้ทีม B, C หรือ D ทราบ เป็นต้น

Long-team simultaneous development in several branches: การ Develop พร้อมกันแบบระยะยาวในหลาย Branches ส่วนนี้เป็นส่วนสำคัญในการนำไปสู่ “หนี้ทางเทคนิค” ซึ่งมันจะเพิ่มขึ้นเมื่อมีการ Merge change ยิ่งมีการแตก Branches ระยะยาวมากเท่าไหร่ Technical debt ก็จะยิ่งมากขึ้นเท่านั้น

Delayed refactoring: ความต้องการทาง Business นั้นเราทราบกันอยู่แล้วว่า มีการเปลี่ยนแปลงอยู่ตลอดเวลา และในบางครั้งเราก็เห็นว่า Code ของเราเริ่มล้าสมัย หรือพันกันเป็นสปาเกตตี้ ทำให้มีความยุ่งยาก และจำเป็นต้องออกแบบใหม่ เพื่อให้เป็นไปตาม Business requirement ใหม่

ในทางกลับกัน เรากำลังเขียนโค้ดใหม่ เพื่อให้มันใช้งานได้กับส่วนที่มันล้าสมัย ดังนั้น ยิ่งมีการ Refactor ช้าเท่าไหร่ โค้ดของเราก็จะยิ่งต้องพึ่งพาอาศัยกันมากขึ้นเท่านั้น (It depend on…)

Lack of compliance monitoring: การขาดการตรวจสอบการปฏิบัติตามข้อกำหนด มันจะเกิดขึ้นเมื่อทุกคนที่ทำงานในโปรเจกต์ที่แล้ว แล้วก็นำมาทำตามแบบเดียวกัน กับโปรเจกต์ใหม่

Incompetence: การที่ Developer ไม่รู้ถึงวิธีการเขียนโค้ดที่เหมาะสม

Refactor

พอเรารู้จัก Technical Debt กันแล้ว… แล้ว เมื่อไหร่หละที่เราจะทำการ Refactor กัน

Rule of Three

  1. เมื่อคุณทำอะไรครั้งแรก ทำให้มันเสร็จ
  2. เมื่อคุณทำสิ่งที่คล้าย ๆ กันเป็นครั้งที่สอง อาจจะต่างกันบ้างนิด ๆ หน่อย ๆ แต่มันก็เหมือนกันอยู่ดี
  3. เมื่อคุณเริ่มทำบางอย่างซ้ำกันเป็นครั้งที่สาม ตรงนี้แหละให้เริ่ม “Refactoring

Adding a feature

เมื่อเพิ่ม Feature การ Refactor ทำให้คุณเข้าใจ Code ของผู้อื่น หากคุณต้องการขจัด Dirty code ของคนอื่น, ให้ลอง Refactor ก่อน การทำให้ Dirty code เป็น Clean code จะทำให้ง่ายต่อการเข้าใจ การที่คุณ Refactor นั้นไม่ได้มีประโยชน์แค่กับคุณเองเท่านั้น มันยังมีประโยชน์ต่อคนที่มาอ่านโค้ดหลังจากคุณด้วย

การ Refactor ทำให้การเพิ่ม Feature ทำได้ง่ายขึ้น

Fixing a bug

แน่นอนครับขึ้นชื่อว่า Bug เหล่า Developer ทุกคนย่อมไม่ชอบ บางครั้งมันก็ไปแอบอยู่ในซอกหลืบที่ลึกที่สุดในโค้ดของเรา แนะนำให้ Refactor แล้ว Clean your code แล้วคุณจะพบ Bug ที่คุณตามหาในที่สุด

การที่เรา Refactor ไปเลยหลังพบ Bug ยังไงก็เสียเวลาน้อยกว่าที่เราต้องมาตามแก้ภายหลัง

How to refactor

รู้จัก Technical กับ เมื่อไหร่ที่ต้อง Refactor กันแล้ว ที่นี้มาดูกันครับว่า การจะ Refactor ให้สำเร็จนั้นทำอย่างไร เค้าแนะนำให้มี Checklist ดังนี้

The code should become cleaner

แน่นอนว่าถ้า Refactor แล้วได้ Code smell กลับมา มันคือการเสียเวลาชีวิตไปเปล่า ๆ

New functionality shouldn’t be created during refactoring

อย่าผสมผสารระหว่าง New feature กับการ Refactor พยายามแยกเรื่องกันเป็นเรื่อง ๆ แล้ว Commit สิ่งที่เราแก้แต่ละสิ่ง

All existing tests must pass after refactoring

มีอยู่สองกรณี ที่การทดสอบสามารถพังได้หลังจากการ Refactor

ข้อแรก คุณทำผิดพลาดในระหว่างทำการ Refactoring ตรงนี้คุณทำอะไรไม่ได้คุณต้องไปต่อ และแก้ไขข้อผิดพลาด

ข้อสอง เทสของคุณอยู่ในระดับที่เป็น Low-level เกินไป เช่นคุณกำลังเทส Private method ของ Classes

Example code

ในตัวอย่างเล็ก ๆ ของ Article นี้จะมาลองแก้ Duplicate Code กัน

Pull Up Constructor Body

Class Manager extends Employee {
public Manager(String name, String id, int grade) {
this.name = name;
this.id = id;
this.grade = grade;
}
// ...
}

คลาสลูกของคุณ มี Constructors ที่มีโค้ดส่วนใหญ่เหมือนกันหมด ให้ใช้วิธี Pull Up Constructor Body

class Manager extends Employee {
public Manager(String name, String id, int grade) {
super(name, id);
this.grade = grade;
}
// ...
}

มาดูอีกตัวอย่างนึงที่น่าจะพบกันบ่อยในเรื่อง Duplicate Code

String foundPerson(String[] people){
for (int i = 0; i < people.length; i++) {
if (people[i].equals("Pete")){
return "Pete";
}
if (people[i].equals("Mike")){
return "Mike";
}
if (people[i].equals("Musk")){
return "Musk";
}
}
return "";
}

มาลองใช้ Substitute Algorithm

String foundPerson(String[] people){
List candidates =
Arrays.asList(new String[] {"Pete", "Mike", "Musk"});
for (int i=0; i < people.length; i++) {
if (candidates.contains(people[i])) {
return people[i];
}
}
return "";
}

Duplicate code อีกสักตัวอย่าง

double disabilityAmount() {
if (seniority < 2) {
return 0;
}
if (monthsDisabled > 12) {
return 0;
}
if (isPartTime) {
return 0;
}
// Compute the disability amount.
// ...
}

Consolidate Conditional Expression

double disabilityAmount() {
if (isNotEligibleForDisability()) {
return 0;
}
// Compute the disability amount.
// ...
}

Conclusion

ก็จบไปแล้วนะครับกับ Technical debt กับ Refactoring หวังว่าผู้อ่านจะพอได้ Idea ไปใช้กับงานของเรากันบ้าง ไม่มากก็น้อย รอบหน้าถ้าเขียนอีก ก็อาจจะไปเขียนเรื่อง Code smell, Clean code เป็นภาคต่อ เช่นพวก Long method, Large class, Lazy code, Dead code, Primitive Obsession, Middle Man และอื่น ๆ

การเขียนครั้งนี้ ผมเอาความเข้าใจส่วนตัว บวกกับดูจาก Refactoring guru ส่วน Source code ก็มาจากที่เดียวกันนะครับ ตรงไหนไม่ถูกบอกได้นะครับ หากอยากอ่านเต็ม ๆ ลึก ๆ สามารถ Click link ด้านล่างได้เลย

--

--

Sakul Montha

Chief Product Officer, a man who’s falling in love with the galaxy.