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

YOLO Software Architecture

design แบบ “ทำ​ไป​เรื่อย” ที่​ไร้​แผน — ทาง​ลัด​สู่ Big Ball of Mud

ใน​โลก​การ​พัฒนา​ซอฟต์แวร์ มี pattern สถาปัตยกรรม​มากมาย​ที่​ช่วย​จัด​ระเบียบ codebase เช่น Clean Architecture และ Vertical Slice Architecture แต่​ไม่ใช่​ทุก​ระบบ​ที่​เดิน​ตาม pattern เหล่า​นี้ บาง​ระบบ​พัฒนา​แบบ​วุ่นวาย​ไร้​โครงสร้าง — นั่น​คือ YOLO Architecture ซึ่ง​เล่นคำ​จาก “You Only Live Once” เพื่อ​สะท้อน​ธรรมชาติ​ที่​สะเพร่า​และ​ทำ​ไป​เรื่อย ๆ (ad-hoc) ของ​ระบบ​เช่น​นี้

YOLO Architecture คือ​สไตล์​การ​พัฒนา​ที่​มี​การ​วางแผน​น้อย​ที่สุด ขาด​การ​จัด​ระเบียบ และ​ไร้​หลักการ​สถาปัตยกรรม​ชัดเจน เป็น​สภาวะ​ที่ code ถูก​เขียน​โดย​แทบ​ไม่​คำนึง​ถึง​ความ​สามารถ​ใน​การ​ดูแล การ scale หรือ​อนาคต มัก​เริ่ม​ใน project เล็กหรือส​ตาร์ตอัป​ที่​ให้​ความ​สำคัญ​กับ​ความเร็ว​และ​ผลลัพธ์​เฉพาะ​หน้า​มากกว่า​คุณภาพ code ระยะ​ยาว มัน​ไม่ใช่ “pattern” ที่​ใคร​ออกแบบ​ไว้​ตั้งใจ แต่​เป็น​สิ่ง​ที่​เกิด​ขึ้น​เอง​เมื่อ​ไม่มี​ใคร​ลงมือ​ฝึก​วินัย​ด้าน​สถาปัตยกรรม​อย่าง​ต่อ​เนื่อง

สิ่ง​ที่​ควร​แยก​ให้​ออก​คือ YOLO มี​สอง​รูปแบบ​ที่​ต่าง​กัน​ใน​เจตนา ตาม Technical Debt Quadrant ของ Martin Fowler: แบบ​แรก​คือ reckless deliberate — ทีม​รู้ตัว​และ​เลือก​ลัด​จงใจ เช่น เดโม​ใน​แฮค​คาธอน​หรือ spike ทดลอง​ไอเดีย ซึ่ง​พอ​รับ​ได้​ถ้า​จำกัด​ขอบเขต แบบ​ที่​สอง​คือ accidental — ไม่มี​ใคร​ตัดสิน​ใจ​เลือก YOLO เลย มัน​คืบ​คลาน​เข้า​มา​เอง​จาก​แรง​กดดัน​เรื่อง deadline ต้นทุน ประสบการณ์​ทีม​ที่​จำกัด requirement ที่​เปลี่ยน​ตลอด และ​ธรรมชาติ​ของ​ซอฟต์แวร์​ที่​โครงสร้าง​ภายใน​มอง​ไม่​เห็น​จาก​ภายนอก (ตาม​ที่ Brian Foote และ Joseph Yoder อธิบาย​ไว้​ใน​เปเปอร์ Big Ball of Mud ต้นฉบับ) แบบ​หลัง​นี้​อันตราย​กว่า​มาก เพราะ​ไม่มี​จุด​ใด​ที่​ทีม​รู้ตัว​ว่า​กำลัง​ก่อ​หนี้​ทาง​เทคนิค​อยู่

  • ขาด modularity: code มัก​เป็น monolith แยก concern น้อย function และ class ถูก​ใส่​ตรง​ไหน​ก็ได้​ที่​สะดวก จน​เกิด tightly coupled
  • เอกสาร​แย่: แทบ​ไม่มี​การ​ทำ​เอกสาร พึ่ง tribal knowledge ทำให้​คน​ใหม่​เข้าใจ​ระบบ​ยาก
  • ตัดสิน​ใจ​แบบ ad-hoc: ตัดสิน​ใจ​สถาปัตยกรรม​เฉพาะ​หน้า​โดย​ไม่​คิดถึง​ผล​ระยะ​ยาว ทาง​ลัด​คือ​เรื่อง​ปกติ
  • technical debt สูง: ทาง​ลัด​สะสม​จน​จัดการ​ยาก​ขึ้น​เรื่อย ๆ
  • พัฒนา​แบบ​ตั้ง​รับ: เปลี่ยน​ตาม​ความ​จำเป็น​เฉพาะ​หน้า ไม่มี roadmap ยิ่ง​ระบบ​ใหญ่ การ​แก้​เล็ก ๆ ยิ่ง​ลาม​เป็น​ปัญหา​ต่อ​เนื่อง productivity ดิ่ง​ลง

ทาง​สู่ Big Ball of Mud: YOLO Architecture มัก​เป็น​จุด​ตั้งต้น​ของ pattern Big Ball of Mud — ระบบ​ที่​โครงสร้างมั่วซั่ว code โต​อย่าง​ควบคุม​ไม่​ได้​จน​ดูแล​แทบ​ไม่​ไหว จาก​ช่วง​แรก​ที่​ดู​จัดการ​ได้ สู่​การ​โต​เร็ว ปัญหา scale และ​สุดท้าย​ถึง​จุด​ที่ refactor แทบ​เป็น​ไป​ไม่​ได้​โดย​ไม่​เสี่ยง​มหาศาล Wikipedia อธิบาย Big Ball of Mud ว่า​เป็น​ระบบ​ที่ “haphazardly structured, sprawling, sloppy” — ไร้​สถาปัตยกรรม​ที่​พอ​สังเกต​เห็น​ได้​เลย

ไม่มี​ใคร​เริ่ม project โดย​ตั้งใจ​ให้​เป็น YOLO Architecture แต่​มัน​เกิด​ขึ้น​ที​ละ​ขั้น​ผ่าน​แรง​กดดัน​ที่​สะสม ตาม​ที่ Foote และ Yoder ระบุ​ไว้​ใน​เปเปอร์​ต้นฉบับ: แรง​กดดัน​เรื่อง​เวลา (deadline ใกล้​เข้า​มา สถาปัตยกรรม​จึง​ถูกลด​ความ​สำคัญ) ต้นทุน​ของ​การ​ออกแบบ​ที่​ดี​ซึ่ง​ไม่​เห็น​ผล​ตอบแทน​ทันที ประสบการณ์​ทีม​ที่​จำกัด​ทั้ง​ด้าน​เทคนิค​และ domain requirement ที่​เปลี่ยน​ไป​จน​แม้​สถาปัตยกรรม​ที่​ดี​ใน​ตอน​แรก​ก็​ค่อย ๆ กร่อน และ​การ​ที่​โครงสร้าง​ซอฟต์แวร์​มอง​ไม่​เห็น​จาก​ภายนอก​ทำให้​ผู้​บริหาร​มอง​ข้าม​ได้​ง่าย

flowchart TD
    A[เริ่ม project เร็ว ไม่วางแผนสถาปัตยกรรม] --> B[เขียน code แบบ ad hoc ตามแรงกดดัน deadline]
    B --> C[ข้ามเอกสารและข้าม code review เพื่อความเร็ว]
    C --> D[ทางลัดสะสมกลายเป็น technical debt]
    D --> E[module พันกันแบบ tightly coupled]
    E --> F[กลายเป็น Big Ball of Mud]
    F --> G[Refactor ทำได้ยากและเสี่ยงสูงมาก]

เพื่อ​กัน​ไม่​ให้ project กลาย​เป็น Big Ball of Mud ควร​วาง​รากฐาน​สถาปัตยกรรม​ที่​มั่นคง​ตั้งแต่​เนิ่น ๆ (แม้​ไม่​จำเป็น​ต้อง​วัน​แรก):

  1. ลงทุน​กับ​การ​วางแผน​และ​ออกแบบ โดย​คำนึง​ถึง scalability ตั้งแต่​ต้น แต่​ไม่​ต้อง​ถึง​ขั้น Big Design Up Front ที่​วางแผน​ละเอียด​จน​ไม่​ได้​เริ่ม​ลงมือ
  2. จัด​โครงสร้าง code เป็น module/ชั้น​ที่​ชัดเจน ตาม​หลัก SOLID และ Separation of Concerns และ​ส่ง​มอบ feature แบบ Vertical Slices เพื่อ​ให้​แต่ละ feature แยก​จาก​กัน​ได้​จริง
  3. ทำ​เอกสาร รวม​ถึง decision record สั้น ๆ ว่า​ทำไม​ถึง​เลือก​ทาง​นี้ เพื่อ​ไม่​ให้​ความ​รู้​ติด​อยู่​กับ​ตัว​คน​เพียง​คน​เดียว
  4. จัด​กระบวนการ code review ที่​เข้มงวด พร้อม​เครื่องมือ​อัตโนมัติ (static analysis, linting, CI) เพื่อ​จับ​ปัญหา​ก่อน​มัน​ฝัง​ลึก
  5. ยึดวิสัยทัศน์​สถาปัตยกรรม​ร่วม ให้​ทั้ง​ทีม​เข้าใจ​ตรง​กัน​ว่า​ระบบ​ควร​มีหน้าตา​แบบ​ไหน แทนที่​แต่ละ​คน​จะ​ตัดสิน​ใจ​เอง​แยก​กัน
  6. refactor อย่าง​ต่อ​เนื่อง เพื่อ​จัดการ technical debt ก่อน​มัน​สะสม​จน​แก้​ไม่​ไหว

ตัวอย่าง​ต่อ​ไป​นี้​จำลอง​สิ่ง​ที่​มัก​เกิด​ขึ้น​หลัง​ทีม​พัฒนา​แบบ YOLO มา​หลายส​ปริ​นต์ — ทุก​อย่าง​ถูก​ยัด​รวม​ไว้​ใน class เดียว​เพราะ “ตอน​นั้น​แก้​แบบ​นี้​เร็ว​กว่า”

// ผลลัพธ์ทั่วไปของ YOLO Architecture: ทุก concern กองรวมกันในเมทอดเดียว
public class OrderController : ControllerBase
{
private readonly SqlConnection db = new SqlConnection("Server=prod;...");
[HttpPost("checkout")]
public IActionResult Checkout(OrderDto dto)
{
// การเข้าถึงข้อมูล เขียนตรงใน controller ไม่มี repository ไม่มี test
db.Open();
var cmd = new SqlCommand("INSERT INTO Orders (Email, Total) VALUES (@e, @t)", db);
cmd.Parameters.AddWithValue("@e", dto.Email);
cmd.Parameters.AddWithValue("@t", dto.Total);
cmd.ExecuteNonQuery();
// logic ธุรกิจถูกแปะเพิ่มทีหลังแบบรีบ ๆ ไม่มีที่ไปเขียนใหม่
if (dto.Total > 1000) dto.Total *= 0.9m;
// TODO เพิ่มไว้ตอน production พังครั้งที่ 3 ยังไม่มีเวลากลับมาแก้
// ยังไม่ validate สต็อกสินค้าก่อน checkout
new SmtpClient().Send("orders@shop.com", dto.Email, "ยืนยันคำสั่งซื้อ", "ขอบคุณที่สั่งซื้อ");
return Ok();
}
}

สถานการณ์​ทั่วไป: ทีม​เริ่ม​จาก code เดโม​สำหรับ​พรีเซนต์​นัก​ลงทุน แล้ว​ต้อง​รีบ​ขึ้น production ก่อน​ถึง​เวลา จึง​ข้าม code review และ​ไม่มี test คุ้มกัน​ไว้ พอ feature ใหม่​ต้อง​เพิ่ม​เข้า​มา ทุก​คน​ก็​แปะ logic เพิ่ม​ลง​ใน class เดิม​เพราะ “แก้​ตรง​นี้​ง่าย​สุด” หก​เดือน​ผ่าน​ไป ทีม incident postmortem พบ​ว่า​ไม่มี​ขอบเขต​ความ​รับผิดชอบ​ชัดเจน​เลย​สัก​จุด​เดียว

แนวทาง​แก้ไข​คือ​แยก concern ออก​จาก​กัน​ตาม​หลัก SRP และ​ย้าย logic ไป​อยู่​ใน​ชั้น​ที่​เหมาะสม เช่น

// แยกความรับผิดชอบ: controller บาง เรียกใช้ service ที่มี logic ธุรกิจ
public class OrderController : ControllerBase
{
private readonly IOrderService orderService;
public OrderController(IOrderService orderService) => this.orderService = orderService;
[HttpPost("checkout")]
public async Task<IActionResult> Checkout(OrderDto dto)
{
var result = await orderService.PlaceOrderAsync(dto);
return result.IsSuccess ? Ok(result.Value) : BadRequest(result.Error);
}
}

เมื่อไหร่​ที่​ยอมรับ​ได้: ความเร็ว​ของ YOLO มี​ที่ทาง​ของ​มัน​จริง ๆ ใน​บาง​บริบท เช่น hackathon การ​ทำ spike เพื่อ​พิสูจน์​ไอเดีย​แบบ throwaway หรือ prototype ที่​จะ​ถูก​ทิ้ง​แน่นอน​หลัง​เดโม ใน​สถานการณ์​เหล่า​นี้​การ​ไม่​วาง​สถาปัตยกรรม​ล่วงหน้า​สอดคล้อง​กับ​หลัก YAGNI — ไม่​สร้าง​สิ่ง​ที่​ยัง​ไม่​จำเป็น​ต้อง​ใช้ ต้นทุน​ตั้งต้น​ต่ำ ทำให้​ทดสอบ​ไอเดีย​ได้​เร็ว​โดย​ไม่​ต้อง​ผ่าน​ขั้นตอน Big Design Up Front ที่​ถ่วง​เวลา

ข้อ​ควร​ระวัง: ปัญหา​คือ prototype แทบ​ทุก​ตัว​มี​แนวโน้ม “รอด” ไป​ขึ้น production จริง​โดย​ไม่มี​ใคร​หยุด​กลับ​มา redesign และ​เมื่อนั้น YOLO Architecture จะ​เริ่ม​เก็บ​ดอกเบี้ย​ทาง​เทคนิค — Big Ball of Mud ที่ refactor เสี่ยง​สูง​และ​แพง onboarding คน​ใหม่​ทำได้​ช้า​เพราะ​ไม่มี​เอกสาร​หรือ​ขอบเขต​ชัดเจน การ​เปลี่ยนแปลง​เล็ก ๆ กลับ​ส่ง​ผลกระทบ​ลาม​ไป​ทั่ว​ระบบ​เพราะ​ทุก​อย่าง tightly coupled และ​เมื่อ​ไม่มี test คุ้มกัน bug ก็​หลุด​เข้า production ง่าย​ขึ้น​เรื่อย ๆ ยิ่ง​ระบบ​มีอายุ​นาน​เท่าไร ต้นทุน​ของ​การ​อยู่​แบบ YOLO ต่อ​ไป​ก็​ยิ่ง​สูง​กว่า​ต้นทุน​ของ​การ​หยุด​มา​จัด​โครงสร้าง​ใหม่

ทาง​ที่​สมดุล​คือ​ยอมรับ​ว่า​ความ​ชุลมุน​ใน​ช่วง​เริ่มต้น​เป็น​เรื่อง​ธรรมชาติ (ตาม​ที่ Foote และ Yoder เอง​ก็​ยอมรับ) แต่​ต้อง “รู้ตัว” ว่า​กำลัง​ก่อ​หนี้​อยู่ และ​มี​วินัย​พากลับ​มา​สู่​โครงสร้าง​ที่​ยั่งยืน​ก่อน​ที่​มัน​จะ​สาย​เกิน​แก้