ข้าม​ไป​ยัง​เนื้อหา

Boy Scout Rule

ทิ้ง code ไว้​ให้​ดี​กว่า​ตอน​ที่​คุณ​เจอ​มัน

Boy Scout Rule สรุป​ได้​สั้น ๆ ว่า จง​ทิ้ง code ไว้​ให้​อยู่​ใน​สภาพ​ที่​ดี​กว่า​ตอน​ที่​คุณ​พบ​มัน

ลูกเสือ​มี​กฎ​เกี่ยว​กับ​การ​ตั้ง​แคมป์​ว่า ต้อง​ทิ้ง​พื้นที่​ตั้ง​แคมป์​ไว้​ให้​สะอาด​กว่า​ตอน​ที่มา​ถึง พวก​เขา​ไม่​ได้​รับ​หน้าที่​ต้อง​ทำความ​สะอาด​ทั้ง​แคมป์​ให้​หมดจด แต่​ก็​ไม่ใช่​ว่า​จะ​ทิ้ง​ขยะ​เกลื่อนกลาด​แล้ว​ค่อย​กลับ​มา​ทำความ​สะอาด​ครั้ง​ใหญ่​ปี​ละ​ครั้ง การ​ทำให้​พื้นที่​สะอาด​ขึ้น​เล็กน้อย​เมื่อ​จาก​ไป รับประกัน​ได้​ว่า​อย่าง​น้อย​พวก​เขา​ก็​ไม่​ได้​สร้าง​ความ​เสียหาย​เพิ่ม​ใน​แง่​ความ​สะอาด​ของ​สถานที่ ต้นตอ​ของ​วลี​นี้​ย้อน​ไป​ถึง Robert Baden-Powell ผู้​ก่อตั้ง​ขบวนการ​ลูกเสือ ที่​เคย​กล่าว​ไว้​ทำนอง​ว่า “จง​พยายาม​ทิ้ง​โลก​นี้​ไว้​ให้​ดี​กว่า​ที่​คุณ​พบ​มัน​เล็กน้อย”

Robert C. “Uncle Bob” Martin เป็น​ผู้​ทำให้​หลักการ​นี้​เป็น​ที่​รู้จัก​ใน​วงการ​ซอฟต์แวร์ ผ่าน​หนังสือ Clean Code: A Handbook of Agile Software Craftsmanship (2008) โดย​เสนอ​ว่า​แทนที่​จะ​รอ big-bang rewrite หรือ refactoring sprint ครั้ง​ใหญ่ นัก​พัฒนา​แต่ละ​คน​ควร​ถือ​เป็น​ความ​รับผิดชอบ​ส่วนตัว​ว่า ทุก​ครั้ง​ที่​แตะ code ไม่​ว่า​จะ​เพื่อ​แก้ bug หรือ​เพิ่ม feature ให้​ปรับปรุง​บาง​สิ่ง​ใกล้​เคียง​ให้​ดี​ขึ้น​เล็กน้อย​ไป​พร้อม​กัน อาจ​จะ​เป็น​แค่​การ​ตั้ง​ชื่อ​ตัวแปร​ใหม่​ให้​สื่อ​ความหมาย แยก function ที่​ยาว​เกิน​ไป​ออก​เป็น​ชิ้น​เล็ก​ลง หรือ​กำจัด duplicate code หนึ่ง​จุด

Martin Fowler อธิบาย​แนวคิด​เดียวกัน​นี้​ใน​ชื่อ opportunistic refactoring และ​แบ่ง​ออก​เป็น​สาม​รูปแบบ​ที่​เกิด​ขึ้น​ระหว่าง​ทำงาน​จริง: preparatory refactoring ปรับ​โครงสร้าง code ก่อน​เริ่ม​งาน​ใหม่​เพื่อ​ให้​งาน​นั้น​ทำได้​ง่าย​ขึ้น, comprehension refactoring ปรับ code ให้​ชัดเจน​ขึ้น​ระหว่าง​ที่​กำลัง​ทำความ​เข้าใจ​มัน และ litter-pickup refactoring เก็บกวาด​สิ่ง​เล็ก ๆ น้อย ๆ ที่​พบ​เจอ​ระหว่าง​ทาง​ก่อน​ถือว่า​งาน​เสร็จ ทั้ง​สาม​แบบ​ล้วน​เป็นการ​นำ Boy Scout Rule ไป​ใช้​ใน​บริบท​ต่าง​กัน

หัวใจ​ของ​หลักการ​นี้​ไม่ใช่​การ​ทำ refactoring ครั้ง​ใหญ่​ใน​ทุก commit แต่​คือการ​สะสมการ​ปรับปรุง​เล็ก ๆ อย่าง​ต่อ​เนื่อง เมื่อ​ทำซ้ำ​หลาย​ครั้ง​โดย​หลาย​คนใน​ทีม คุณภาพ​ของ codebase จะ​ขยับ​ขึ้น​ที​ละ​น้อย​ไป​เรื่อย ๆ ตรง​ข้าม​กับ​แนวโน้ม​ธรรมชาติ​ที่ code มัก​จะ​เสื่อม​คุณภาพ​ลง​เมื่อ​เวลา​ผ่าน​ไป​หาก​ไม่มี​ใคร​ดูแล

ใน​งาน​ซอฟต์แวร์ การ​รักษา code ให้​สะอาด​เป็น​ความ​ท้าทาย​ที่​ไม่มี​วัน​จบ นัก​พัฒนา​และ​ทีม​ต้อง​ตัดสิน​ใจ​ว่า​จะ​ดูแล​ความ​สะอาด​ของ code หรือ​ไม่ เมื่อไร และ​อย่างไร เมื่อใด​จึง​คุ้ม​ค่าที่​จะ​ใช้​เวลา​ปรับปรุง design ของ codebase เมื่อ​เวลา​ผ่าน​ไป คุณภาพ​ของ source code มัก​เสื่อมถอย​ลง สะสม technical debt เพิ่ม​ขึ้น​เรื่อย ๆ การ​ชำระ​หนี้​ก้อน​นี้​ด้วย​การ refactoring จึง​จำเป็น เพื่อ​รักษา code ให้​อยู่​ใน​สภาพ​ที่​ยัง​คุ้ม​ค่า​ต่อ​การ​ต่อยอด​และ​ดูแล​รักษา

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

Boy Scout Rule เสนอ​ทาง​เลือก​อื่น นั่น​คือ เพียง​พยายาม​ให้​แน่ใจ​ว่า​ใน​แต่ละ commit คุณ​ทิ้ง code ไว้​ดี​กว่า​ตอน​ที่​พบ​มัน อาจ​จะ​ดี​ขึ้น​แค่​เล็กน้อย​ก็ได้ เมื่อ​ทำ​ตาม​หลักการ​นี้ ทีม​สามารถ​ยก​ระดับ​คุณภาพ code ขึ้น​เรื่อย ๆ เมื่อ​เวลา​ผ่าน​ไป ใน​ขณะ​ที่​ยัง​ส่ง​มอบ​คุณค่า​ให้​ลูกค้า​และ​ผู้​มี​ส่วน​ได้​ส่วน​เสีย​อย่าง​ต่อ​เนื่อง ข้อดี​อีก​ประการ​คือ​การ​ปรับปรุง​เล็ก ๆ เหล่า​นี้​มัก​เกิด​ขึ้น​ใน​ส่วน​ของ code ที่​ทีม​เข้าไป​แก้ไข​บ่อย​ที่สุด​อยู่​แล้ว ซึ่ง​เป็น​ส่วน​ที่​คุ้ม​ค่าที่สุด​ที่​จะ​ลงทุน​ปรับปรุง เพราะ​มัน​ถูก​อ่าน​และ​แก้ไข​ซ้ำ ๆ

การ​ทิ้ง​ความ​ยุ่งเหยิง​ไว้​ใน code ควร​เป็น​สิ่ง​ที่​สังคม​ยอมรับ​ไม่​ได้​พอ ๆ กับการ​ทิ้ง​ขยะ​เรี่ยราด

— Robert C. “Uncle Bob” Martin

ใน​ทาง​ปฏิบัติ นี่​คือ​กระบวนการ​ง่าย ๆ ที่​นัก​พัฒนา​ใช้​ตัดสิน​ใจ​ระหว่าง​การ​แก้​งาน​จริง

flowchart TD
  A[เจอ code ที่ต้องแก้ไขระหว่างทำงาน] --> B{มีจุดเล็กที่ปรับให้ดีขึ้นได้หรือไม่}
  B -- ใช่ --> C[ปรับปรุงเล็กน้อยระหว่างแก้งานเดิม]
  B -- ไม่ --> D[แก้ไขตามขอบเขตเดิมโดยไม่แตะส่วนอื่น]
  C --> E[Commit การเปลี่ยนแปลงไปพร้อมกัน]
  D --> E
  E --> F[คุณภาพ code ดีขึ้นทีละน้อยอย่างต่อเนื่อง]

ตัวอย่าง​ต่อ​ไป​นี้​จำลอง​สถานการณ์​ทั่วไป คือ​ได้​รับ​มอบหมาย​ให้​แก้ bug หนึ่ง​จุด แต่​ระหว่าง​แก้​ก็​ถือ​โอกาส​ปรับปรุง code รอบ ๆ ไป​ด้วย​เล็กน้อย​ตาม Boy Scout Rule

// ก่อน: code เดิมที่ทำงานได้ แต่มี bug และมีจุดที่อ่านยาก
// bug: โยน NullReferenceException เมื่อ order ไม่มีคูปอง เพราะไม่ได้เช็ค null
public class OrderProcessor
{
public decimal calc(Order o)
{
decimal total = 0;
foreach (var item in o.Items)
{
total += item.Price * item.Qty;
}
if (o.Coupon.Type == "PERCENT")
{
total = total - (total * o.Coupon.Value / 100);
}
else if (o.Coupon.Type == "FIXED")
{
total = total - o.Coupon.Value;
}
return total;
}
}
// หลัง: แก้ bug ตามที่ได้รับมอบหมาย และทิ้ง code ไว้ดีกว่าเดิมเล็กน้อยไปพร้อมกัน
// 1) แก้สาเหตุของ bug ด้วยการเช็ค null ก่อนใช้ Coupon
// 2) ตั้งชื่อ method ใหม่ให้สื่อความหมาย (calc -> CalculateTotal)
// 3) แทนที่ magic string ด้วย enum เพื่อลดโอกาส typo และให้ compiler ช่วยตรวจ
public enum CouponType { Percent, Fixed }
public class OrderProcessor
{
public decimal CalculateTotal(Order order)
{
decimal total = order.Items.Sum(item => item.Price * item.Qty);
if (order.Coupon is null)
{
return total;
}
return order.Coupon.Type switch
{
CouponType.Percent => total - total * order.Coupon.Value / 100,
CouponType.Fixed => total - order.Coupon.Value,
_ => total,
};
}
}

การ​เปลี่ยนแปลง​นี้​ยัง​อยู่​ใน scope ที่​สม​เหตุ​สม​ผล​สำหรับ​การ​แก้ bug หนึ่ง​ใบ ไม่​ได้​ไป​รื้อ design ทั้ง class หรือ​แตะ code ส่วน​ที่​ไม่​เกี่ยวข้อง แต่​ก็​ทำให้ codebase ดี​ขึ้น​กว่า​ตอน​ที่​พบ​มัน​จริง ๆ นี่​คือ​ขนาด​ของ​การ​ปรับปรุง​ที่ Boy Scout Rule เรียกร้อง คือเล็ก​พอที่​จะ​ปลอดภัย​และ​รวม​อยู่​ใน commit เดียวกัน​ได้ ไม่ใช่​การ refactor ขนาด​ใหญ่​ที่​ต้อง​ขอ approve แยก​ต่างหาก

  • code ถูก​แตะ​แล้ว​ปล่อย​ผ่าน​เหมือน​เดิม​ทุก​ครั้ง ไม่มี​ใคร​แก้​ชื่อ​แปรผัน​แย่ ๆ หรือ​ลบ dead code ที่​เห็น​อยู่​ตรง​หน้า แม้​จะ​รู้​ว่า​ไม่​ดี​ก็ตาม
  • comment ประเภท TODO หรือ HACK สะสม​พอกพูน​เป็น​ปี ๆ โดย​ไม่มี​ใคร​แก้ ทั้ง​ที่​มี​โอกาส​แก้​หลาย​ครั้ง​ระหว่าง​ทาง (ดู Comments code smell)
  • ทีม​ต้อง​หยุด​งาน​สร้าง​คุณค่า​ทั้งหมด​เป็น​ระยะ ๆ เพื่อ​ทำ “refactoring sprint” ใหญ่ ซึ่ง​เป็น​สัญญาณ​ว่า​ไม่มี​การ​ดูแล code อย่าง​สม่ำเสมอ​ระหว่าง​ทาง
  • Pull request review ปฏิเสธ​การ​ปรับปรุง​เล็ก ๆ ที่​เกี่ยวข้อง โดย​อ้าง​ว่า “ไม่​อยู่​ใน scope” อยู่​เสมอ จน​ไม่มี​ใคร​กล้า​ทำความ​สะอาด code ที่​แตะ​อยู่​แล้ว
  • นัก​พัฒนา​เกรงกลัว​ที่​จะ​แตะ code บาง​ส่วน​ของ​ระบบ เพราะ​สภาพ​ย่ำแย่​จน​ไม่มี​ใคร​อยาก​รับผิดชอบ​เพิ่ม เป็น​อาการ​ของ Broken Windows ที่​ลุกลาม​จน​คน​หมด​ใจ​จะ​ซ่อมแซม
  • technical debt backlog มี​แต่​จะ​ยาว​ขึ้น​เรื่อย ๆ โดย​ไม่​เคย​ลด​ลง เพราะ​ไม่มี​การ​ชำระ​หนี้​เล็ก ๆ ระหว่าง​ทาง มี​แต่​รอ “sprint ใหญ่” ที่​ไม่​เคย​มา​ถึง