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
หัวข้อที่มีชื่อว่า “ทางแก้และการ refactor”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]