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

Reinventing the Wheel

เขียน​เอง​สิ่ง​ที่​มี​ของ​สำเร็จรูป​ดี ๆ อยู่​แล้ว

Reinventing the Wheel คือ​แอนตี้ pattern ที่​ทีม​หรือ​องค์กร สร้าง​โซลูชัน​ขึ้น​เอง​ใหม่​ทั้งหมด สำหรับ​ปัญหา​ที่​มี​คำ​ตอบ​สำเร็จรูป​อยู่​แล้ว​ใน​รูป​ของ library framework บริการ หรือ​เครื่องมือ​ทั้ง open source และ​เชิง​พาณิชย์ ชื่อ​ของ​แอนตี้ pattern นี้​ยืม​มา​จาก​อุปมา​ว่า​ล้อ​เป็น​สิ่ง​ประดิษฐ์​พื้นฐาน​ที่​มนุษย์​คิดค้น​และ​ขัดเกลา​มา​นับ​พันปี การ​ไป​นั่ง​คิดค้น​ล้อ​ใหม่​จึง​เป็น​สัญลักษณ์​ของ​การ​เสีย​เวลา​ไป​กับ​สิ่ง​ที่​ถูก​แก้ไข​ปัญหา​ไป​แล้ว​อย่าง​สมบูรณ์

Wikipedia ชี้​ว่า​ยัง​มี​ตัวแปร​ที่​แย่​กว่า​นั้น​เรียก​ว่า “reinventing the square wheel” — คือ​ไม่​เพียง​สร้าง​ของ​ซ้ำ แต่​สร้าง​ออก​มา​ได้ แย่​กว่า ของ​ที่​มี​อยู่​เดิม​เสีย​อีก เพราะ​ทีม​ไม่มี​เวลา ความ​รู้ หรือ battle-testing เท่ากับ​โซลูชัน​ที่​ผ่าน​การ​ใช้งาน​จริง​มา​นาน

ข้อ​ควร​ระวัง​คือ นี่​ไม่ใช่​การ​บอกว่า “ห้าม​เขียน​อะไร​เอง​เด็ดขาด” DevIQ เอง​ก็​ยกเว้น​กรณี​ที่ feature นั้น​เป็น แกน​หลัก (core) ของ​ผลิตภัณฑ์ — ถ้า​ไม่​ได้​ขาย​เครื่องมือ logging ก็​ไม่​จำเป็น​ต้อง​เขียน logging เอง แต่​ถ้า​ธุรกิจ​คือ​การ​ขาย​เครื่องมือ logging นั่นแหละ​คือ​สิ่ง​เดียว​ที่​ควร​ทุ่ม​พลัง​ลง​ไป​ให้​ดี​ที่สุด ส่วน​ที่​เหลือ​ทั้งหมด​ควร​ใช้​ของ​ที่​มี​อยู่

  • รู้สึก​ควบคุม​ได้​เต็ม​ที่ — code ที่​เขียน​เอง​ไม่มี dependency ภายนอก ไม่​ต้อง​กังวล​เรื่อง breaking change ของ third-party หรือ license ที่​ไม่​แน่ใจ​เงื่อนไข
  • ดูเหมือน​ง่ายกว่าที่​คิด — function เล็ก ๆ เช่น parser, logger, retry policy มัก​ดูเหมือน​เขียน​เอง​ได้​ใน “หนึ่ง​บรรทัด” หรือ​หนึ่ง​บ่าย​วัน โดย​มอง​ไม่​เห็น edge case ที่ library สำเร็จรูป​แก้ไข​มา​แล้ว​นับ​สิบ ๆ ครั้ง​จาก​ผู้​ใช้​จริง
  • ของ​สำเร็จรูป​ไม่​พอดี​เป๊ะ — library ที่​มี​มัก​ออกแบบ​มา​ให้ generic ครอบคลุม​หลาย​เคส ทำให้​ดู​เทอะทะ​เกิน​ความ​จำเป็น​เมื่อ​เทียบ​กับ​เคส​เฉพาะ​ของ​ทีม จึง​ล่อ​ใจ​ให้ “เขียน​แบบ​พอดี​ตัว” เอง
  • เป็น​โอกาส​เรียนรู้​ที่​ดี — Coding Horror ยก​คำ​พูด​ของ James Hart และ Charles Moore ว่าการ​ลงมือ reinvent เพื่อ เรียนรู้​กลไก​เบื้องหลัง เป็น​สิ่ง​มี​ค่า และ 97 Things Every Programmer Should Know ก็​แนะนำ​ให้​โปรแกรมเมอร์ reinvent เป็น​วิธี​ฝึกฝน​ตัวเอง — แต่​นั่น​คือ​บริบท “การ​เรียนรู้​ส่วนตัว” ไม่ใช่ “code production ที่​ทีม​ต้อง​ดูแล​ระยะ​ยาว”
  • ความ​ไม่​ไว้ใจ third-party — บาง​ทีม​กังวล​เรื่อง​ความ​ปลอดภัย supply-chain หรือ​เคย​โดน library ทิ้ง​ร้าง (abandonware) จน​เลือก​เขียน​เอง​เพื่อ​ความ​มั่นใจ ทั้ง​ที่ทาง​เลือก​อื่น​เช่น​การ vendor/fork หรือ​เลือก library ที่ maintain ดี​กว่า​อาจ​ตอบ​โจทย์​ได้​โดย​ไม่​ต้อง​เขียน​ใหม่​ทั้งหมด
  • เสีย​เวลา​และ​งบประมาณ​ไป​กับ​สิ่ง​ที่​ไม่​สร้าง​มูลค่า​ทาง​ธุรกิจ — ทุก​ชั่วโมง​ที่​ใช้​เขียน logger, ORM, หรือ date-time parser เอง​คือ​ชั่วโมง​ที่​ไม่​ได้​ใช้​พัฒนา​สิ่ง​ที่​ทำให้​สินค้า​แตก​ต่าง​จาก​คู่แข่ง
  • คุณภาพ​และ​ความ​น่า​เชื่อถือ​ด้อย​กว่า — Wikipedia สรุป​ตรง ๆ ว่า​เมื่อ reinvent โดย​ไม่รู้ตัว ผลลัพธ์​มัก​จะ อ่าน​ยาก​กว่า เชื่อถือ​ได้​น้อย​กว่า ทดสอบ​น้อย​กว่า และ optimize น้อย​กว่า ของ​ที่​ผ่าน​การ​ใช้งาน​จริง​จาก​ผู้​ใช้​นับ​พัน​นับ​หมื่น​คน​มา​แล้ว
  • bug และ​ช่อง​โหว่​ที่​ควร​ถูก​แก้​ไป​นาน​แล้ว — โซลูชัน​สำเร็จรูป​ที่​โต​แล้ว​ผ่าน​การ​เจอ edge case, race condition, และ​ช่อง​โหว่​ด้าน​ความ​ปลอดภัย​มา​นับ​ไม่​ถ้วน​จน​ถูก patch หมด​แล้ว ส่วน code ที่​เขียน​เอง​ใหม่​ต้อง​ไป​เจอ​ปัญหา​เหล่า​นั้น​ซ้ำ​อีก​ครั้ง​จาก​ศูนย์ — กรณี​ที่​อันตราย​ที่สุด​คือ​การ​เขียน cryptography หรือ authentication เอง ซึ่ง​มัก​ซ่อน​ช่อง​โหว่​ที่​ไม่มี​ใคร​มอง​เห็น​จน​ถูก​โจมตี​จริง
  • ภาระ​บำรุง​รักษา​ตก​อยู่​กับ​ทีม​เพียง​ลำพัง — library open source มี​ชุมชน​คอย patch, document, และ​ตอบ​คำถาม ส่วน code homegrown มี​แค่​ทีม​เดียว​ที่​ต้อง debug เอง แก้​เอง เขียน doc เอง และ​เมื่อ​คน​ที่​เขียน​มัน​ลาออก ความ​รู้​ก็​หาย​ไป​ด้วย
  • Onboarding และ​ต้นทุน​ซ่อนเร้น​สูง​ขึ้น — พนักงาน​ใหม่​ที่​เคยชิน​กับ​เครื่องมือ​มาตรฐาน​ของ​อุตสาหกรรม (เช่น Serilog, Entity Framework) ต้อง​มา​เรียนรู้​ระบบ homegrown ที่​ไม่มี​เอกสาร​มาตรฐาน ไม่มี Stack Overflow ให้​ค้น ทำให้ productivity ลด​ลง​โดย​ไม่​จำเป็น
  • Erik Dietrich ใน Telerik Blogs ชี้​ว่า​จุด​ที่​มัก​ถูก reinvent ซ้ำ ๆ ใน​องค์กร​คือ logger, utility function เล็ก ๆ, ORM, ตัว​แปลง file CSV/JSON/XML และ UI control อย่าง grid/calendar — ล้วน​เป็น​จุด​ที่ “ดูเหมือน​ง่าย” แต่​จริง ๆ มี​ความ​ซับซ้อน​ซ่อน​อยู่​มาก

สมมติ​ทีม​พัฒนา e-commerce ต้องการ​บันทึก log การ​ทำงาน​ของ​ระบบ แล้ว​ตัดสิน​ใจ​เขียน logger เอง​แบบ​ง่าย ๆ แทนที่​จะ​ใช้ library logging ที่​มี​อยู่:

// แอนตี้ pattern: เขียน logger เองแบบ ad-hoc
public static class SimpleLogger
{
private static readonly string LogPath = "app.log";
public static void Log(string message)
{
// เปิด file และเขียนทับใหม่ทุกครั้ง ไม่ thread-safe
// ไม่มี log level ไม่มี log rotation ไม่มี structured logging
// ไม่มี async I/O จึง block request thread ทุกครั้งที่เรียก
File.AppendAllText(LogPath, $"{DateTime.Now}: {message}{Environment.NewLine}");
}
}
public class OrderService
{
public void PlaceOrder(Order order)
{
SimpleLogger.Log($"เริ่มสั่งซื้อ order id {order.Id}");
// ... โลจิกการสั่งซื้อ ...
SimpleLogger.Log("สั่งซื้อสำเร็จ");
}
}

code นี้​ดูเหมือน​ใช้งาน​ได้​ใน​เดโม แต่​พอ​เข้า production จริง​จะ​เจอ​ปัญหา​เป็น​ระลอก: หลาย​คำขอ​เขียน file พร้อม​กัน​ทำให้ log บาง​บรรทัด​หาย file โต​ไม่มี​ที่​สิ้นสุด​เพราะ​ไม่มี rotation ไม่มี​ทาง​กรอง log level เพื่อ​ลด noise และ​ไม่มี​ทาง query แบบ structured เมื่อ​เกิด​เหตุการณ์​ผิด​ปกติ​ใน​เวลา 3 นาฬิกา — สิ่ง​เหล่า​นี้​คือ​ปัญหา​ที่ library logging ระดับ production แก้​ไป​เรียบร้อย​แล้ว

ก่อน​ลงมือ​เขียน​อะไร​เอง ให้​ตั้ง​คำถาม​ก่อน​เสมอ​ว่า “นี่​คือ​สิ่ง​ที่​สินค้า​ของ​เรา​ต้อง โดด​เด่น หรือ​เป็น​แค่ ส่วนประกอบ​สนับสนุน” ตาม​หลักการ​ของ Know Where You Are Going และ YAGNI:

flowchart TD
  A[ต้องการความสามารถใหม่] --> B{เป็นแกนหลักที่ทำให้สินค้าแตกต่าง}
  B -->|ใช่| C[ประเมินสร้างเองอย่างจริงจัง]
  B -->|ไม่ใช่| D{มี library หรือบริการที่โตแล้ว}
  D -->|มี| E[Adopt ของที่มีอยู่]
  D -->|ไม่มี หรือเงื่อนไข license ไม่เข้ากัน| F[สร้างเท่าที่จำเป็นจริง ๆ]
  C --> G[ลงทุนเวลาและทรัพยากรเต็มที่]
  E --> H[ทุ่มเวลาที่เหลือให้แกนหลักของสินค้า]
  F --> H

เมื่อ​คำ​ตอบ​คือ “ไม่ใช่​แกน​หลัก” ให้​แทนที่ code homegrown ด้วย library ที่​โต​แล้ว​ของ​อี​โค​ซิสเต็ม เช่น​ใน​ตัวอย่าง logger ด้าน​บน ควร​ใช้ Microsoft.Extensions.Logging ร่วม​กับ sink อย่าง Serilog ซึ่ง​มี log level, structured logging, rotation และ async I/O ให้​พร้อม​ใช้:

// ทางแก้: ใช้ library logging ที่โตแล้วผ่าน dependency injection
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public void PlaceOrder(Order order)
{
_logger.LogInformation("เริ่มสั่งซื้อ OrderId {OrderId}", order.Id);
// ... โลจิกการสั่งซื้อ ...
_logger.LogInformation("สั่งซื้อสำเร็จ OrderId {OrderId}", order.Id);
}
}
// Program.cs — ตั้งค่า Serilog เป็น logging provider พร้อม rotation และ enrichment
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSerilog(config => config
.WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
.WriteTo.Console()
.Enrich.FromLogContext());

หลัก​ปฏิบัติ​เมื่อ​ต้อง​ตัดสิน​ใจ build-vs-buy:

  • ทำ spike สั้น ๆ ก่อน​ตัดสิน​ใจ — ลอง​ใช้ library ตัว​เลือก​จริง​กับ​เคส​ของ​ทีม​สัก 1-2 วัน ก่อน​สรุป​ว่า “ของ​สำเร็จรูป​ไม่​พอดี” ซึ่ง​มัก​เป็น​สมมติฐาน​ที่​ผิด
  • แยก interface ออก​จาก implementation — หาก​จำเป็น​ต้อง​เขียน​เอง​จริง ๆ (เช่น​ติด​เงื่อนไข license หรือ compliance) ให้​ครอบ​ด้วย interface ของ​ทีม​เอง​ไว้​ก่อน เพื่อ​ให้​สลับ​ไป​ใช้ library มาตรฐาน​ภายหลัง​ได้​โดย​ไม่​กระทบ code ที่​เรียก​ใช้ ตาม​หลัก DIP
  • ถ้า​ตัดสิน​ใจ​สร้าง​เอง ให้​ทำ​อย่าง​จริงจัง — เขียน test ครอบคลุม เขียน​เอกสาร และ​ปฏิบัติ​กับ​มัน​เหมือน​เป็น​ผลิตภัณฑ์​จริง ไม่ใช่ code ชั่วคราว​ที่​ไม่มี​ใคร​ดูแล เพราะ​ถ้า​ตัดสิน​ใจ​ว่า​มัน​คือ​แกน​หลัก​ของ​สินค้า​แล้ว มัน​สมควร​ได้​รับ​ความ​ใส่ใจ​ระดับ​นั้น
  • ระวัง Not Invented Here — ถ้า​เหตุผล​ที่แท้​จริง​ใน​การ​ปฏิเสธ library คือ “ไม่​ได้​เขียน​โดย​ทีม​เรา” มากกว่า​เหตุผล​ทาง​เทคนิค นั่น​คือ​สัญญาณ​ของ​แอนตี้ pattern อีก​ตัว​หนึ่ง​ที่​เกี่ยวโยง​กัน​โดยตรง
  • Not Invented Here — แอนตี้ pattern พี่น้อง​ที่​ปฏิเสธ​ของ​ภายนอก​ด้วย​เหตุผล​ทาง​จิตวิทยา​มากกว่า​ทาง​เทคนิค
  • Shiny Toy — หลง​เทคโนโลยี​ใหม่​แทนที่​จะ​ใช้​โซลูชัน​ที่​พิสูจน์​แล้ว ซึ่ง​มัก​นำ​ไป​สู่​การ reinvent โดย​ไม่​ตั้งใจ
  • Golden Hammer — อีก​ด้าน​ของ​เหรียญ คือ​ใช้​เครื่องมือ​เดียว​ซ้ำ ๆ ทุก​ปัญหา​แทนที่​จะ​เลือก​เครื่องมือ​ให้​เหมาะ​กับ​งาน
  • YAGNI — อย่า​สร้าง​สิ่ง​ที่​ยัง​ไม่​ต้อง​ใช้​จริง หลักการ​เดียว​กับ​ที่​ควร​ใช้​ก่อน​ตัดสิน​ใจ build เอง
  • KISS — เลือก​ทางออก​ที่​เรียบ​ง่าย​ที่สุด​ที่​ใช้งาน​ได้​จริง ซึ่ง​มัก​หมาย​ถึง​การ​ใช้​ของ​ที่​มี​อยู่​แล้ว
  • Know Where You Are Going — รู้​เป้าหมาย​ทาง​ธุรกิจ เพื่อ​แยกแยะ​ว่า​อะไร​คือ​แกน​หลัก​ที่​ควร​ลงทุน​สร้าง​เอง