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 ไม่มีคูปอง เพราะไม่ได้เช็ค nullpublic 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 ใหญ่” ที่ไม่เคยมาถึง
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”- Refactoring
- Broken Windows
- Cunningham’s Law
- Architectural Agility
- DRY — Don’t Repeat Yourself
- Continuous Integration