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

Frozen Caveman

ทีม​ที่​ปฏิเสธ​เทคโนโลยี​และ​วิธี​ใหม่ ๆ จน​ตก​ยุค

Frozen Caveman คือ antipattern ที่​ทีม​พัฒนา ไม่​ยอม​หรือ​ไม่​สามารถ​รับ​เทคโนโลยี ระเบียบ​วิธี หรือ​แนว​ปฏิบัติ​ใหม่ ๆ การ​ต่อต้าน​การ​เปลี่ยนแปลง​นี้​นำ​ไป​สู่​กระบวนการ​พัฒนา​ที่​ล้าสมัย​และ​ไม่มี​ประสิทธิภาพ ตาม​มาตรฐาน​อุตสาหกรรม​และ​ความ​คาด​หวัง​ของ​ลูกค้า​ไม่ทัน ชื่อ “มนุษย์​ถ้ำ​ที่​ถูก​แช่แข็ง” สื่อ​ถึง​คน​ที่​แช่แข็ง​ความ​รู้​ของ​ตัวเอง​ไว้ ณ จุด​หนึ่ง​ใน​อดีต แล้ว​ละลาย​ออก​มา​ทำงาน​ด้วย​ชุด​ความคิด​เดิม​ราวกับ​เวลา​ไม่​เคย​เดิน​ต่อ

นอกจาก DevIQ แล้ว คำ​นี้​ยัง​ถูก​ใช้​ใน​อีก​บริบท​หนึ่ง​ซึ่ง​แคบ​กว่า​แต่​คม​กว่า — Neal Ford ผู้​เขียน Fundamentals of Software Architecture ร่วม​กับ Mark Richards ใช้ “Frozen Caveman Antipattern” อธิบาย สถาปนิก​หรือ​ผู้นำ​ทีม (มัก​เป็น​ผู้​ก่อตั้ง​หรือ​พนักงาน​รุ่น​แรก) ที่​เคย​ถูก “เผา” จาก​การ​ตัดสิน​ใจ​ทาง​เทคนิค​ครั้ง​หนึ่ง​ใน​อดีต แล้ว​ยก​เรื่อง​นั้น​ขึ้น​มา​เป็น pet concern ที่​ต้อง​คัดค้าน​ซ้ำ ๆ ใน​ทุก​สถาปัตยกรรม​ถัด​ไป ไม่​ว่า​บริบท​จะ​เปลี่ยน​ไป​แค่​ไหน​แล้ว​ก็ตาม เช่น สถาปนิก​ที่​เคย​เจอ NoSQL ตัว​หนึ่ง​ทำ​ข้อมูล​พัง เมื่อ 10 ปี​ก่อน แล้ว​ยืนกราน​ปฏิเสธ NoSQL ทุก​ชนิด​ตลอด​ไป​โดย​ไม่​ประเมิน​ใหม่ ทั้ง​สอง​ความหมาย​มี​แก่น​ร่วม​กัน​คือ ความ​รู้​ที่​หยุด​นิ่ง​ถูก​เข้าใจ​ผิด​ว่า​ยัง​ทันสมัย​อยู่ ต่าง​กัน​ที่​ระดับ — DevIQ พูด​ถึง​ทั้ง​ทีม ส่วน Ford/Richards เจาะจง​ที่​การ​ตัดสิน​ใจ​ของ​ปัจเจก​ใน​บทบาท​สถาปนิก

  • เสถียรภาพ​ที่​พิสูจน์​แล้ว — เครื่องมือ/pattern เดิม​ทำงาน​ได้​จริง​มา​หลาย​ปี ทีม​รู้​จุด​อ่อน​จุด​แข็ง​ของ​มัน​หมด​แล้ว ความ​เสี่ยง​จึง​ดู​ต่ำ​กว่า​ของ​ใหม่​ที่​ยัง​ไม่รู้​ผล​ข้าง​เคียง
  • “ถ้า​มัน​ไม่​พัง ก็​อย่า​ไป​แตะ” — หลัก​คิด​แบบ​อนุรักษนิยม​นี้​ฟัง​ดู​มี​เหตุผล​ใน​ระบบ​ที่​ต้องการ​ความ​น่า​เชื่อถือ​สูง (เช่น​ระบบ​การเงิน​หรือ​ระบบ​ที่​มี compliance เข้มงวด)
  • หลีก​เลี่ยง churn จาก​กระแส hype — การ​ไม่​ไล่​ตาม​ทุก framework ใหม่​ช่วย​ป้องกัน​ไม่​ให้​ทีม​เสีย​เวลา​ไป​กับ​ของ​ที่​อาจ​จะ​ตาย​ภายใน​ปี​เดียว ซึ่ง​ดู​มี​วินัย​กว่า​การกระโดด​ตาม Shiny Toy
  • บาดแผล​ใน​อดีต​ให้​ความ​ชอบธรรม — สำหรับ​กรณี​สถาปนิก​ที่​เคย​ถูก​ของ​ใหม่​ทำร้าย​มา​ก่อน การ​ระวัง​ตัว​ดู​สม​เหตุ​สม​ผล เพราะ​เป็น​บทเรียน​จาก​ประสบการณ์​จริง ไม่ใช่​ความ​กลัว​ลอย ๆ
  • ต้นทุน​การ​เรียนรู้​ที่​จับ​ต้อง​ได้​ทันที — การ​เปลี่ยน​เครื่องมือ​มี​ค่า​ใช้​จ่าย​ชัดเจน (เวลา​ฝึก คน​ต้อง​ปรับ​ตัว) ใน​ขณะ​ที่​ผล​ตอบแทน​เป็น​ของ​ใน​อนาคต​ที่​ยัง​พิสูจน์​ไม่​ได้ ทำให้​ผู้​ตัดสิน​ใจ​มัก​เลือก​ทาง​ที่​ปลอดภัย​ไว้​ก่อน
  • ตก​ขบวน​มาตรฐาน​อุตสาหกรรม — เมื่อ​คู่แข่ง​ใช้​เครื่องมือ​ที่​เร็ว​กว่า ปลอดภัย​กว่า และ​มี​คน support มากกว่า ทีม​ที่​แช่แข็ง​จะ​ช้า​ลง​เรื่อย ๆ ทั้ง​ด้าน​ความเร็ว​ใน​การ​ส่ง​มอบ​และ​คุณภาพ
  • หนี้​ทาง​เทคนิค​สะสม​แบบ​ทบ​ต้น — framework เก่า​หมด​อายุ support (end-of-life) ไม่มี patch ความ​ปลอดภัย​ใหม่ ยิ่ง​อยู่​นาน​ยิ่ง migrate ยาก​ขึ้น​เพราะ​ช่องว่าง version ถ่าง​ออก​เรื่อย ๆ
  • สรรหา​และ​รักษา​คน​เก่ง​ไม่​ได้ — นัก​พัฒนา​ที่​ต้องการ​เติบโต​มัก​ไม่​อยาก​ทำงาน​กับ stack ที่​หยุด​พัฒนา ทำให้​ทีม​เสียคน​เก่ง​และ​ดึงดูด​คน​ใหม่​ได้​ยาก​ขึ้น
  • ปิด​กั้น​ทาง​เลือก​ที่​ดี​กว่า​โดย​ไม่​ประเมิน​จริง — ใน​กรณี​ของ​สถาปนิก​ที่​ยึด​ติด pet concern การ​คัดค้าน​แบบ​เหมา​รวม​ทำให้​ทีม​เสีย​โอกาส​ใช้​โซลูชัน​ที่​เหมาะ​กับ​ปัญหา​ปัจจุบัน เพียง​เพราะ​มัน share คำ​เดียว​กับ​สิ่ง​ที่​เคย​พัง​ใน​อดีต
  • ขัด​กับ​หลัก​ความ​คล่องตัว​ทาง​สถาปัตยกรรมArchitectural Agility ต้องการ​ให้​สถาปัตยกรรม​ปรับ​ตัว​ตาม​ความ​ต้องการ​ที่​เปลี่ยน​ไป​ได้ การ​แช่แข็ง​ทำลาย​ความ​สามารถ​นั้น​ตั้งแต่​ต้น

ทีม​ยัง​คง​ใช้ Thread ดิบ​และ WebClient แบบ synchronous ใน​การ​เรียก API ทั้ง​ที่ HttpClient กับ async/await เป็น​มาตรฐาน​ของ .NET มา​นาน​กว่า​ทศวรรษ และ​ยัง​แกะ JSON ด้วย​การ​ตัด string เอง​แทนที่​จะ​ใช้ JSON parser ที่​มี​อยู่​แล้ว​ใน framework

// Frozen Caveman: ยึดติดกับวิธีเรียก HTTP แบบเก่า ทั้งที่มีของใหม่ที่ดีกว่ามานานแล้ว
public class LegacyPriceFetcher
{
public void FetchAllPrices(List<string> productIds)
{
foreach (var id in productIds)
{
var thread = new Thread(() => FetchPrice(id));
thread.Start();
thread.Join(); // block ทีละเส้น ไม่มี concurrency จริง แค่ทำให้ดูขนาน
}
}
private void FetchPrice(string id)
{
using (var client = new WebClient())
{
string json = client.DownloadString("https://api.example.com/price/" + id);
// แกะค่าด้วยการตัด string เอง แทนที่จะใช้ JSON parser ที่มีอยู่แล้ว
int start = json.IndexOf("\"price\":") + 8;
int end = json.IndexOf(",", start);
string price = json.Substring(start, end - start);
Console.WriteLine(id + ": " + price);
}
}
}

code นี้​ทำงาน​ได้ แต่​ทุก​จุด​คือ​มรดก​จาก​ยุค​ก่อน Task-based async: จัดการ thread เอง​แทน​ใช้ thread pool ผ่าน async/await, ไม่มี cancellation หรือ timeout ที่​จัดการ​ได้​ง่าย, และ parser แบบ manual ที่​พัง​ทันที​ที่ format JSON เปลี่ยน​นิดเดียว

refactor ด้วย​ของ​ที่​เป็น​มาตรฐาน​ปัจจุบัน — HttpClient แบบ​ฉีด​ผ่าน dependency injection, async/await สำหรับ I/O ที่​รอ​ผล​จาก​ภายนอก, และ System.Text.Json สำหรับ​แปลง JSON เป็น type ที่​รู้จัก:

public class ModernPriceFetcher
{
private readonly HttpClient _client;
public ModernPriceFetcher(HttpClient client) => _client = client;
public async Task FetchAllPricesAsync(IEnumerable<string> productIds)
{
var tasks = productIds.Select(FetchPriceAsync);
await Task.WhenAll(tasks);
}
private async Task FetchPriceAsync(string id)
{
var dto = await _client.GetFromJsonAsync<PriceDto>("price/" + id);
Console.WriteLine(id + ": " + dto?.Price);
}
}
public record PriceDto(string Id, decimal Price);

การ​เปลี่ยนแปลง​เชิง​องค์กร​ที่​ต้อง​ทำ​ควบคู่​กับ​การ refactor code:

  • แยก “ความ​เสี่ยง​จริง” ออก​จาก “pet concern” — ทุก​ครั้ง​ที่​ปฏิเสธ​ของ​ใหม่ ให้​ถาม​ว่า​กำลัง​ประเมิน​บริบท​ปัจจุบัน​จริง ๆ หรือ​แค่​ยก​บาดแผล​เก่า​มา​ใช้​ซ้ำ วิธี​ที่​ใช้ได้​ผล​คือ​กำหนด​เกณฑ์​ประเมิน​ที่​เป็น​ลายลักษณ์​อักษร (เช่น performance, ความ​ปลอดภัย, cost of ownership) แล้ว​ให้​ทุก​ข้อ​เสนอ​ผ่าน​เกณฑ์​เดียวกัน ไม่ใช่​ผ่าน “ความ​รู้สึก​ไม่​ไว้ใจ” ของ​คน​คน​เดียว
  • ทดลอง​แบบ​มี​ขอบเขต (spike/pilot) — ให้​เวลา​จำกัด​ทดลอง​เทคโนโลยี​ใหม่​กับ​งาน​ความ​เสี่ยง​ต่ำ​ก่อน แล้ว​ค่อย​ตัดสิน​ใจ​ขยาย​ผล​ด้วย​ข้อมูล​จริง​แทน​ความ​เชื่อ
  • migrate ที​ละ​ส่วน​แบบ strangler — ไม่​ต้อง​เปลี่ยน​ทั้ง​ระบบ​พร้อม​กัน ค่อย ๆ แทนที่ code เก่า​ที​ละ​จุด​เพื่อ​ลด​ความ​เสี่ยง​และ​กระจาย​ต้นทุน​การ​เรียนรู้
  • ให้​เวลา​เรียนรู้​อย่าง​เป็น​ทางการ — จัดสรร​เวลา/งบ​สำหรับ​สำรวจ​เครื่อง​มือใหม่ เช่น brown-bag session หรือ technology radar ภายใน​ทีม เพื่อ​ไม่​ให้การ​อัปเดต​ความ​รู้​ต้อง​แข่ง​กับ deadline ของ​งาน​ประจำ
  • รักษา​สมดุล อย่า​แกว่ง​ไป​สุด​อีก​ทาง — เป้าหมาย​ไม่ใช่​ไล่​ตาม​ของ​ใหม่​ทุก​ชิ้น​แบบ Shiny Toy แต่​คือ​ประเมิน​ด้วย​วิจารณญาณ​และ​คง ความ​คล่องตัว​ทาง​สถาปัตยกรรม ไว้​ตรง​กลาง
flowchart TD
    A[New tool or practice appears] --> B{Blanket veto from past scar}
    B -->|Yes| C[Reject without re-evaluation]
    C --> D[Team stays on outdated stack]
    B -->|No| E[Evaluate against written criteria]
    E --> F[Run a bounded pilot]
    F --> G[Adopt gradually if it proves value]