Not Invented Here
ปฏิเสธทุกสิ่งที่ไม่ได้สร้างเอง จนเสียทรัพยากรไปกับของซ้ำ
ปัญหาคืออะไร
หัวข้อที่มีชื่อว่า “ปัญหาคืออะไร”Not Invented Here (NIH) คือแนวโน้มขององค์กรหรือทีมที่ ปฏิเสธจะเชื่อถือ ใช้ หรือแม้แต่พิจารณา library เครื่องมือ มาตรฐาน หรือแนวคิดใด ๆ ที่ไม่ได้ถูกสร้างขึ้นภายในทีมของตนเอง แม้จะมีทางเลือกที่ดีกว่า ถูกกว่า หรือผ่านการพิสูจน์มาแล้วในตลาดอยู่ก็ตาม อาการทั่วไปคือทีมพูดว่า “เดี๋ยวเราเขียนเอง” ทันทีที่มีคนเสนอ library หรือบริการภายนอก โดยไม่ทันได้ประเมินต้นทุน-ผลประโยชน์อย่างจริงจัง
ผลลัพธ์คือทรัพยากรวิศวกรรมที่มีค่า — เวลา สมอง และงบประมาณ — ถูกทุ่มไปกับการสร้างของที่ตลาดมีอยู่แล้วซ้ำแล้วซ้ำเล่า (เช่น ORM ของตัวเอง, message queue ของตัวเอง, authentication framework ของตัวเอง) แทนที่จะเอาไปใช้กับ feature หลักที่สร้างความแตกต่างให้ผลิตภัณฑ์จริง ๆ
NIH เป็นอีกด้านหนึ่งของเหรียญเดียวกันกับ Reinventing the Wheel — Reinventing the Wheel อธิบาย “การกระทำ” ของการสร้างของซ้ำ ส่วน NIH อธิบาย “ทัศนคติ” หรือวัฒนธรรมที่นำไปสู่การกระทำนั้น
ทำไมถึงดูน่าใช้
หัวข้อที่มีชื่อว่า “ทำไมถึงดูน่าใช้”- ควบคุมได้เต็มที่ — code ที่เขียนเองแก้ไขได้ตามใจ ไม่ต้องรอ vendor หรือ maintainer ของ open-source แก้ bug ให้
- เข้าใจทุกบรรทัด — ทีมรู้สึกมั่นใจว่าเข้าใจระบบทั้งหมด ไม่มี “กล่องดำ” ที่ไม่รู้ว่าข้างในทำงานอย่างไร
- ไม่มีความเสี่ยงด้านสัญญาอนุญาตหรือความปลอดภัยจากภายนอก — ไม่ต้องกังวลเรื่อง license ที่ไม่เข้ากัน หรือช่องโหว่ใน code ของคนอื่น
- ความภาคภูมิใจทางวิศวกรรม — ทีมที่มีทักษะสูงมักเชื่อว่าตนเองสร้างของที่ “ดีกว่า” หรือ “เข้ากับ use case ของเรา” ได้มากกว่าของสำเร็จรูปทั่วไป ซึ่งบางครั้งก็จริง โดยเฉพาะกับตรรกะทางธุรกิจที่เป็นแก่นของผลิตภัณฑ์
- ประสบการณ์แย่ในอดีต — เคยเจอ library ภายนอกที่ bug เยอะ ดูแลไม่ดี หรือถูก deprecate กลางคัน จึงสรุปเหมารวมว่าของภายนอกเชื่อถือไม่ได้เลย
เหตุผลเหล่านี้ทำให้ NIH ฟังดูเหมือนวินัยทางวิศวกรรมที่ดี ไม่ใช่ปัญหา — นั่นคือกับดักของมัน
ทำไมถึงเป็นปัญหา
หัวข้อที่มีชื่อว่า “ทำไมถึงเป็นปัญหา”- เสียเวลาสร้างของสามัญ (commodity) ที่ไม่สร้างความแตกต่างทางธุรกิจ — เวลาที่ทีมใช้เขียน ORM หรือ logging framework ของตัวเอง คือเวลาที่ไม่ได้ใช้กับ feature ที่ลูกค้าจ่ายเงินซื้อ
- ภาระบำรุงรักษาระยะยาวที่มองไม่เห็นตอนเริ่ม — โซลูชันสำเร็จรูปมีชุมชนคอยแก้ bug อัปเดตความปลอดภัย และเขียนเอกสารให้ฟรี ส่วนของที่เขียนเองต้องมีทีมภายในดูแลตลอดอายุของระบบ ต้นทุนสะสมตลอดวงจรชีวิตซอฟต์แวร์ (total cost of ownership) มักสูงกว่าที่ประเมินไว้มาก
- คุณภาพและความลึกของ feature สู้ของสำเร็จรูปที่โตมาหลายปีไม่ได้ — library open-source ที่ใช้กันแพร่หลายผ่านการทดสอบจาก edge case นับพันจากผู้ใช้ทั่วโลก ขณะที่ของที่เขียนเองมักถูกทดสอบแค่กับ use case ที่ทีมนึกออกตอนนั้น
- แยกตัวจากองค์ความรู้ของ ecosystem — ทีมที่ทำทุกอย่างเองพลาดโอกาสเรียนรู้จากแนวปฏิบัติที่ดีที่สุดของอุตสาหกรรม และวิศวกรใหม่ที่เข้าทีมต้องเรียนรู้ระบบภายในที่ไม่มีใครนอกทีมรู้จัก แทนที่จะใช้ทักษะมาตรฐานที่ติดตัวมา
- เอกสารและ onboarding แย่ — ระบบภายในมักไม่มีเอกสารดีเท่า project ที่มีชุมชนขนาดใหญ่ ทำให้ความรู้กระจุกอยู่กับคนไม่กี่คน (ดู Lois Lane และปัญหา bus factor)
- ล่าช้ากว่าคู่แข่งเข้าตลาด — เวลาที่หมดไปกับการประดิษฐ์ล้อใหม่ คือเวลาที่คู่แข่งใช้สร้าง feature ที่ทำให้พวกเขาชนะตลาด
ตัวอย่าง
หัวข้อที่มีชื่อว่า “ตัวอย่าง”ทีมต้องการ retry policy พร้อม exponential backoff และ circuit breaker สำหรับเรียก API ภายนอก แทนที่จะใช้ library ที่ผ่านการพิสูจน์แล้วอย่าง Polly ทีมตัดสินใจเขียนกลไก retry ของตัวเองตั้งแต่ศูนย์:
// ตัวอย่างที่เป็นปัญหา — เขียน retry/circuit-breaker เองทั้งหมด// เพราะ "ไม่อยากพึ่งพา library ภายนอก"public class HomeGrownRetryClient{ private readonly HttpClient _http; private int _consecutiveFailures; private DateTime _circuitOpenUntil = DateTime.MinValue;
public HomeGrownRetryClient(HttpClient http) => _http = http;
public async Task<string> GetWithRetryAsync(string url) { if (DateTime.UtcNow < _circuitOpenUntil) throw new InvalidOperationException("circuit ยังเปิดอยู่ ลองใหม่ทีหลัง");
var delayMs = 200; for (var attempt = 1; attempt <= 5; attempt++) { try { var response = await _http.GetAsync(url); response.EnsureSuccessStatusCode(); _consecutiveFailures = 0; return await response.Content.ReadAsStringAsync(); } catch (Exception) when (attempt < 5) { // ยังไม่ครอบกรณี jitter, timeout ที่เหมาะสม, // หรือ thread-safety เวลามีหลาย request พร้อมกัน await Task.Delay(delayMs); delayMs *= 2; _consecutiveFailures++; if (_consecutiveFailures >= 5) _circuitOpenUntil = DateTime.UtcNow.AddSeconds(30); } } throw new InvalidOperationException("เรียก API ไม่สำเร็จหลังพยายามครบจำนวนครั้ง"); }}code นี้ดูใช้งานได้ในตอนแรก แต่ขาดสิ่งที่ library ที่โตแล้วอย่าง Polly มีให้ครบ เช่น jitter เพื่อลด thundering herd, การรองรับ cancellation token อย่างถูกต้อง, thread-safety ของ state ร่วม, และการแยก policy ออกจาก business logic ทุกครั้งที่ทีมเจอ edge case ใหม่ (เช่น timeout ต้องต่างกันตาม endpoint) ก็ต้องแก้ code นี้เอง แทนที่จะแค่ configure policy ที่มีอยู่แล้ว
ทางแก้และการ refactor
หัวข้อที่มีชื่อว่า “ทางแก้และการ refactor”หลักการแก้คือ แยกให้ชัดว่าอะไรคือ “แก่นที่สร้างความแตกต่าง” ของธุรกิจ กับอะไรคือ “ของสามัญ” (commodity) — ของสามัญควรซื้อหรือใช้ของสำเร็จรูปเสมอ ส่วนแก่นของธุรกิจเท่านั้นที่ควรสร้างเอง
// ทางแก้ — ใช้ library ที่ผ่านการพิสูจน์แล้ว (Polly)// แล้วโฟกัสพลังงานทีมไปที่ policy ที่เหมาะกับ domain ของเราแทนvar retryPolicy = Policy .Handle<HttpRequestException>() .WaitAndRetryAsync( retryCount: 5, sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)) + TimeSpan.FromMilliseconds(Random.Shared.Next(0, 200))); // jitter
var circuitBreakerPolicy = Policy .Handle<HttpRequestException>() .CircuitBreakerAsync( exceptionsAllowedBeforeBreaking: 5, durationOfBreak: TimeSpan.FromSeconds(30));
var resiliencePolicy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);
public class ResilientApiClient{ private readonly HttpClient _http; private readonly IAsyncPolicy _policy;
public ResilientApiClient(HttpClient http, IAsyncPolicy policy) { _http = http; _policy = policy; }
public Task<string> GetAsync(string url) => _policy.ExecuteAsync(async () => { var response = await _http.GetAsync(url); response.EnsureSuccessStatusCode(); return await response.Content.ReadAsStringAsync(); });}ขั้นตอนเชิงปฏิบัติสำหรับทีมและองค์กร:
- ทำ build-vs-buy อย่างเป็นระบบ ก่อนเริ่มเขียนของใหม่ทุกครั้ง ให้ถามว่า “นี่คือสิ่งที่สร้างความได้เปรียบทางการแข่งขันให้เราจริงหรือ หรือเป็นแค่ของสามัญที่ตลาดมีคำตอบอยู่แล้ว” — หากเป็นของสามัญ ให้ default ไปทาง buy/use เสมอ
- ห่อของภายนอกด้วย abstraction ของตัวเอง เมื่อจำเป็นต้องพึ่ง library ภายนอก ใช้ Adapter หรือ Facade เพื่อกันไม่ให้ API ของ third-party รั่วไหลเข้าไปทั่ว codebase วันที่ต้องเปลี่ยน library จะเจ็บน้อยลง
- นับต้นทุนตลอดวงจรชีวิต ไม่ใช่แค่ต้นทุนตอนเขียนครั้งแรก รวมค่าบำรุงรักษา อัปเดตความปลอดภัย และ onboarding พนักงานใหม่เข้าไปในการประเมินด้วย
- ให้รางวัลกับผลลัพธ์ ไม่ใช่กับการควบคุมสถาปัตยกรรม วัฒนธรรมที่ยกย่องวิศวกรที่ “ส่ง feature ได้เร็วด้วยของสำเร็จรูป” พอ ๆ กับวิศวกรที่เขียนระบบซับซ้อนเอง จะลดแรงจูงใจของ NIH ลงตามธรรมชาติ
- สำรวจระบบภายในที่มีอยู่แล้ว ถ้าองค์กรมีวัฒนธรรม NIH ฝังลึก ให้ทำ inventory ของเครื่องมือที่เขียนเองทั้งหมด ประเมินว่าตัวไหน low-value ควรย้ายไปใช้มาตรฐานอุตสาหกรรม แล้วยกตัวอย่างความสำเร็จของการ integrate ของภายนอกให้ทีมเห็นเป็นรูปธรรม
ทั้งนี้ NIH ไม่ได้แปลว่า “ห้ามเขียนเองเด็ดขาด” — งานที่เป็นแก่นของ domain ธุรกิจ (core domain) ที่ไม่มีของสำเร็จรูปตรงกับ use case จริง ๆ ยังคุ้มค่าที่จะสร้างเอง หลักการ “duplication is far cheaper than the wrong abstraction” ยังใช้ได้กับตรรกะทางธุรกิจเฉพาะทาง ประเด็นคือต้องเป็นการตัดสินใจที่ไตร่ตรองแล้ว ไม่ใช่ปฏิกิริยาอัตโนมัติจากอคติต่อของภายนอก
flowchart TD
Need[มีความต้องการ feature ใหม่]
Ask{เป็นแก่นที่สร้าง<br/>ความแตกต่างทางธุรกิจหรือไม่}
Commodity[เป็นของสามัญ เช่น logging<br/>retry queue orm auth]
Core[เป็นแก่น domain ธุรกิจเฉพาะทาง]
Use[ใช้ของสำเร็จรูปที่พิสูจน์แล้ว<br/>ห่อด้วย Adapter หรือ Facade]
Build[พิจารณาสร้างเอง<br/>ด้วยความเข้าใจต้นทุนตลอดวงจรชีวิต]
Need --> Ask
Ask -->|ไม่ใช่| Commodity
Ask -->|ใช่| Core
Commodity --> Use
Core --> Build
ที่เกี่ยวข้อง
หัวข้อที่มีชื่อว่า “ที่เกี่ยวข้อง”- Reinventing the Wheel — การกระทำที่เป็นผลจากทัศนคติแบบ NIH
- Golden Hammer — อคติทางเครื่องมืออีกแบบ ใช้เครื่องมือเดียวกับทุกปัญหาแทนที่จะพิจารณาทางเลือก
- Found on Internet — สุดโต่งฝั่งตรงข้าม เชื่อของที่หามาจากอินเทอร์เน็ตโดยไม่ตรวจสอบ
- Frozen Caveman — ยึดติดกับความเสี่ยงในอดีตจนปฏิเสธแนวทางใหม่ที่เหมาะสมกว่า
- Adapter — pattern ที่ช่วยห่อ library ภายนอกให้ปลอดภัยเมื่อเลือกใช้ของสำเร็จรูป
- Facade — ลดพื้นที่ผิวของ API ภายนอกที่ codebase ต้องรู้จัก