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

Shiny Toy

เชื่อ​ว่า​ทุก​ปัญหา​แก้​ได้​ด้วย​เครื่อง​มือใหม่​ล่าสุด​เสมอ

Shiny Toy คือ​การ คิด​เสมอ​ว่า​ปัญหา​วัน​นี้​แก้​ได้​ด้วย​เครื่องมือ เทคนิค หรือ library ใหม่​ล่าสุด (bleeding-edge) โดย​ไม่​ประเมิน​ความ​เสี่ยง​หรือ​ความ​สุกงอม​ของ​มัน​ก่อน

แม้​วงการ​ซอฟต์แวร์​จะ​พัฒนา​ตลอด​เวลา และ​ของ​ใหม่​มัก​แก้​ปัญหา​ที่​ของ​เก่า​เคย​มี แต่​ของ​ใหม่​ก็​มัก​มา​พร้อม “ปัญหา​ใหม่” ที่​ยัง​ไม่มี​ใคร​เจอ​หรือ​แก้​มา​ก่อน — เอกสาร​ไม่​ครบ ชุมชน​เล็ก เครื่องมือ debug ยัง​ไม่​โต และ​บาง​ครั้ง​ตัว library เอง​ก็​ยัง​ไม่​เสถียร​พอ​สำหรับ production จริง

ขึ้น​กับ​ความ​เสี่ยง​ที่ application และ​องค์กร​รับ​ได้ อาจ​สม​เหตุ​สม​ผลอย่าง​ยิ่ง​ที่​จะ รอ ให้ library หรือ framework ใหม่​พิสูจน์​ตัวเอง​ใน production ระยะ​หนึ่ง​ก่อน​นำ​มา​ใช้ — แนวคิด​นี้​สัมพันธ์​กับ Pain Driven Development คือ​รับ​ของ​ใหม่​เข้า​มา​ก็​ต่อ​เมื่อ “เจ็บ​ปวด” กับ​ข้อ​จำกัด​ของ​ของ​เดิม​จริง ๆ ไม่ใช่​เพราะ​มัน​ดู​น่า​ตื่นเต้น

  • สัญญา​ว่า​จะ​เร็ว​กว่า ง่าย​กว่า — บทความ, block, และ conference talk มัก​โปรโมต​ของ​ใหม่​ด้วย demo ที่​สวยงาม​และ​เรียบ​ง่าย​เกิน​จริง​เมื่อ​เทียบ​กับ​ปัญหา​จริง​ใน​ระบบ​ที่​มี​อยู่​แล้ว
  • ความ​อยาก​รู้​อยาก​เห็น​และ​ความ​สนุก​ของ​ผู้​พัฒนา — โปรแกรม merge ำนวนมา​กรัก​ของ​ใหม่ เพราะ​มัน​ท้าทาย​และ​น่า​ตื่นเต้น​กว่า​การ​ดูแล code เดิม
  • แรง​กดดัน​ด้าน​เรซู​เม่ — งาน​วิจัย​เชิง​ประจักษ์​เรื่อง Résumé-Driven Development (RDD) พบ​ว่า 82% ของ​นัก​พัฒนา​เชื่อ​ว่าการ​ใช้​เทคโนโลยี​ที่​กำลัง​ฮิต​ทำให้​ตัวเอง​น่า​จ้าง​มาก​ขึ้น และ 60% ของ​ผู้​จ้าง​งาน​ยอมรับ​ว่า​เทรนด์​มี​ผล​ต่อ​การ​เปิด​รับสมัคร​งาน แรง​จูงใจ​นี้​ผลักดัน​การ​เลือก​เทคโนโลยี​โดย​ไม่​สัมพันธ์​กับ​ความ​ต้องการ​ทาง​เทคนิค​จริง
  • กลัว​ตก​ยุค (FOMO) — รู้สึก​ว่า​ถ้า​ไม่​ใช้​ของ​ใหม่ ทีม​หรือ​องค์กร​จะ​ล้าหลัง​คู่แข่ง
  • สับสน​ระหว่าง “ใหม่” กับ “ดี​กว่า” — ของ​ใหม่​แก้​ปัญหา​บาง​อย่าง​จริง แต่​ก็​มัก​แลก​ด้วย​ข้อ​เสีย​ใหม่​ที่​ยัง​มอง​ไม่​เห็น​ใน​วัน​ที่​ตัดสิน​ใจ​เลือก
  • ความ​เสี่ยง​ด้าน production ที่​ยัง​ไม่​ถูก​พิสูจน์ — library ใหม่​อาจ​มี breaking changes บ่อย, bug ที่​ยัง​ไม่​ถูก​เจอ, หรือ​ช่อง​โหว่​ด้าน​ความ​ปลอดภัย​ที่​ยัง​ไม่มี​ใคร patch
  • ต้นทุน​การ​เรียนรู้​ซ้ำซ้อน — ทุก​เครื่อง​มือใหม่​หมาย​ถึง learning curve ใหม่​สำหรับ​ทั้ง​ทีม ซึ่ง​ดึง​เวลา​ออก​จาก​การ​ส่ง​มอบ​คุณค่า​ทาง​ธุรกิจ
  • หนี้​ทาง​เทคนิค​จาก​การ​ทดลอง​ที่​ค้าง​อยู่ — “ลอง​ดู​ก่อน” มัก​ไม่​เคย “ทำ​เสร็จ​จริง” กลาย​เป็น code ผสม​สอง​สไตล์​ที่​ดูแล​ยาก​กว่า​เดิม
  • ระบบ​นิเวศ​และ​ชุมชน​ที่​ยัง​เล็ก — เมื่อ​เจอ​ปัญหา อาจ​ไม่มี​คำ​ตอบ​ใน Stack Overflow, ไม่มี Stack Overflow เลย, หรือ​ทีม maintainer มี​คน​เดียว
  • ของ​ใหม่​กลาย​เป็น Golden Hammer ตัว​ใหม่ — เมื่อ​ทีม​ตื่นเต้น​กับ​เครื่อง​มือใหม่ มัก​เผลอ​เอา​มัน​ไป​ใช้​แก้​ทุก​ปัญหา ไม่​ว่า​จะ​เหมาะสม​หรือ​ไม่ ซึ่ง​กลาย​เป็น Golden Hammer ใน​คราบ​ของ​ใหม่
  • ขัด​กับ​หลัก YAGNI — การนำ​เทคโนโลยี​ที่​ซับซ้อน​เข้า​มา​เพื่อ “เผื่อ​อนาคต” หรือ​เพราะ​มัน​ดู​ล้ำ โดย​ไม่มี​ความ​จำเป็น​ตอน​นี้ ก็​คือ​การ​ทำงาน​ที่​ยัง​ไม่​จำเป็น​ต้อง​ทำ

ทีม​มี background job ธรรมดา​ที่​ทำงาน​ได้​ดี​อยู่​แล้ว​ด้วย queue ใน​ฐาน​ข้อมูล​ที่​มี​อยู่ แต่​มี​คน​ได้ยิน​เรื่อง message broker ตัว​ใหม่​ล่าสุด​จาก conference แล้ว​อยาก​เปลี่ยน​ทันที โดย​ไม่มี​ปัญหา​ด้าน throughput หรือ scalability ที่​ระบบ​เดิม​แก้​ไม่​ได้:

// ก่อน: โซลูชันง่าย ๆ ที่ทำงานได้ดีอยู่แล้ว ไม่มีปัญหาด้าน throughput
public class OrderJobQueue
{
private readonly IDbConnection _db;
public OrderJobQueue(IDbConnection db) => _db = db;
public void Enqueue(OrderJob job)
{
_db.Execute(
"INSERT INTO JobQueue (Payload, Status) VALUES (@Payload, 'Pending')",
new { Payload = JsonSerializer.Serialize(job) });
}
}
// หลัง: เปลี่ยนไปใช้ message broker ใหม่ล่าสุดที่เพิ่ง 1.0 ได้ไม่ถึงเดือน
// เพราะ "มันเจ๋ง" ไม่ใช่เพราะระบบเดิมมีปัญหาจริง
public class OrderJobQueue
{
private readonly BleedingEdgeBrokerClient _broker;
public OrderJobQueue(BleedingEdgeBrokerClient broker) => _broker = broker;
public async Task Enqueue(OrderJob job)
{
// API ยังเปลี่ยนบ่อย เอกสารตามไม่ทัน
// ไม่มีใครในทีมมีประสบการณ์ debug broker ตัวนี้ตอนมันล่มกลางดึก
await _broker.PublishAsync("orders.pending", job);
}
}

ผลลัพธ์: ทีม​ต้อง​เรียนรู้ operational model ของ broker ใหม่​ทั้งหมด (deployment, monitoring, retry semantics) แลก​กับ​สิ่ง​ที่​ระบบ​เดิม​ทำได้​อยู่​แล้ว และ​เมื่อ broker version 1.1 ออก​มา​พร้อม breaking API change ทีม​ก็​ต้อง​หยุด​งาน feature เพื่อ​ไล่​แก้

flowchart TD
    A[มีปัญหาทางธุรกิจ] --> B{ประเมินทางเลือก}
    B --> C[เครื่องมือปัจจุบันแก้ได้จริงไหม]
    C -->|ได้| D[ใช้เครื่องมือเดิม]
    C -->|ไม่ได้ ระบุข้อจำกัดชัดเจน| E[ประเมินความเสี่ยงของของใหม่]
    E --> F[ทดลองในสโคปเล็ก จำกัดความเสียหาย]
    F --> G[พิสูจน์คุณค่าใน production จริงหรือยัง]
    G -->|ยัง| H[รอ รอบสองค่อยตัดสินใจ]
    G -->|พิสูจน์แล้ว| I[นำไปใช้อย่างมีแผนย้ายทีมทั้งหมด]
  1. ตั้ง​คำถาม​ก่อน​เสมอ: ปัญหา​คือ​อะไร ไม่ใช่​เครื่องมือ​คือ​อะไร — เริ่ม​จาก​ข้อ​จำกัด​ของ​ระบบ​ปัจจุบัน​ที่​วัดผล​ได้​จริง (เช่น latency, throughput, DX ที่​แย่​มาก​จน​เสีย​เวลา) แล้ว​ค่อย​หา​ว่า​เครื่องมือ​ไหน​แก้​ปัญหา​นั้น​ได้​ดี​ที่สุด
  2. ใช้ Pain Driven Development เป็น​เกณฑ์​รับ​ของ​ใหม่ — รับ​เทคโนโลยี​ใหม่​เข้า​มา​ต่อ​เมื่อ​ทีม​รู้สึก “เจ็บ” กับ​ข้อ​จำกัด​ของ​เดิม​จริง ๆ ไม่ใช่​เพราะ​กลัว​ตก​เทรนด์
  3. ใช้ model adopt/trial/assess/hold แบบ ThoughtWorks Technology Radar — แยก​ของ​ใหม่​ตาม​ระดับ​ความ​พร้อม: ของ​ที่ “assess” หรือ “trial” เท่านั้น​ที่​เอา​ไป​ทดลองในสโคป​เล็ก​และ​แยก​จาก production หลัก ส่วน​ของ​ที่ “adopt” แล้ว​เท่านั้น​ที่​ใช้​กับ​งาน​สำคัญ
  4. ทดลอง​ใน​พื้นที่​จำกัด​ความ​เสียหาย​ก่อน (spike/proof-of-concept) — เขียน code ทดลอง​แยก​ต่างหาก มี timebox ชัดเจน และ​เกณฑ์​ตัดสิน​ใจ​ว่า​จะ “ไป​ต่อ” หรือ “พอ​แค่​นี้” ก่อน​เอา​เข้า​ระบบ​จริง
  5. ให้​เวลา library พิสูจน์​ตัวเอง​ใน production ของ​คน​อื่น​ก่อน — รอ​ผ่าน version major แรก ๆ ให้ community ใหญ่​พอที่​จะ​มี​คำ​ตอบ​เมื่อ​เจอ​ปัญหา
  6. แยก​การ​ตัดสิน​ใจ​เทคโนโลยี​ออก​จาก​อี​โก้​และ​เรซู​เม่ส่วนตัว — ให้​ทีม​ทั้ง​ทีม​มี​ส่วนร่วม​ตัดสิน​ใจ ไม่ใช่​คน​เดียว​ผลักดัน​เพราะ​อยาก​เรียนรู้​ของ​ใหม่ (พึง​ระวัง Résumé-Driven Development)
// แนวทางที่ดีกว่า: ทดลองแบบมีขอบเขต วัดผลได้ ก่อนเปลี่ยนทั้งระบบ
public class OrderJobQueue
{
private readonly IDbConnection _db;
private readonly IFeatureFlags _flags;
private readonly BleedingEdgeBrokerClient? _experimentalBroker;
public OrderJobQueue(
IDbConnection db,
IFeatureFlags flags,
BleedingEdgeBrokerClient? experimentalBroker = null)
{
_db = db;
_flags = flags;
_experimentalBroker = experimentalBroker;
}
public async Task Enqueue(OrderJob job)
{
// ทดลองใน flag แยก จำกัดสัดส่วน traffic วัดผลก่อนตัดสินใจย้ายทั้งระบบ
if (_flags.IsEnabled("experimental-broker") && _experimentalBroker is not null)
{
await _experimentalBroker.PublishAsync("orders.pending", job);
return;
}
_db.Execute(
"INSERT INTO JobQueue (Payload, Status) VALUES (@Payload, 'Pending')",
new { Payload = JsonSerializer.Serialize(job) });
}
}
  • Pain Driven Development — หลักการ​รับ​ของ​ใหม่​เมื่อ​เจ็บ​ปวด​จริง​เท่านั้น คือ​ทาง​แก้​หลัก​ของ Shiny Toy
  • YAGNI — อย่า​เพิ่ม​ความ​ซับซ้อน​ที่​ยัง​ไม่​จำเป็น​ต้อง​ใช้​ตอน​นี้
  • Golden Hammer — เมื่อ​ของเล่น​ใหม่​กลาย​เป็น​ค้อน​ที่​ใช้​ตี​ทุก​ปัญหา
  • Speculative Generality — กลิ่น code จาก​การ​ออกแบบ​เผื่อ​อนาคต​ที่​ไม่มี​อยู่​จริง
  • Laws of Software Architecture — กรอบ​คิด​เรื่อง trade-off ที่​ช่วย​ประเมิน​การ​ตัดสิน​ใจ​เลือก​เทคโนโลยี