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

Duct Tape Coder

ปะ code ให้​ใช้ได้​เฉพาะ​หน้า โดย​ไม่​สน​คุณภาพ​หรือ​การ​ดูแล​รักษา

Duct Tape Coder คือ​คน​ที่ ปะ​ซอฟต์แวร์​ให้​แก้​ปัญหา​เฉพาะ​หน้า​ได้ โดย​ไม่​ใส่ใจ​คุณภาพ​หรือ​การ​ดูแล​รักษา ชื่อ​นี้​มา​จาก​คำ​ว่า “duct tape programmer” ที่ Joel Spolsky บัญญัติ​ไว้​ใน​บทความ​ชื่อ​ดัง​ปี 2009 ของ​เขา โดย​เขา​ยกย่อง​โปรแกรมเมอร์​แบบ​นี้​ว่า​เป็น​คน​ที่ “ship the fucking thing” — ส่ง​มอบ​ของ​จริง​ออก​ไป​ได้ แทนที่​จะ​นั่ง​ขัดเกลา code ใน​ห้อง​แล็บ​ไม่รู้​จบ เขา​บอกว่า​คน​กลุ่ม​นี้​จงใจ​หลีก​เลี่ยง​เทคนิค​ที่​ซับซ้อน​อย่าง multiple inheritance, multithreading หรือ COM/CORBA เพราะ​เชื่อ​ว่า​อะไร​ที่​ซับซ้อน​เกิน​ไป​มี​แต่​จะ​ทำให้ project พัง

ปัญหา​คือ Spolsky พูด​ถึง “duct tape” ใน​แง่​บวก แต่​สิ่ง​ที่​ตาม​มา​ใน​โลก​จริง​กลับ​กลาย​เป็น​แง่​ลบ​เสีย​มาก เพราะ​คำ​อธิบาย​ของ​เขา​ผูก​ติด​กับ​สมมติฐาน​ที่​ว่า​คน​คน​นั้น “เก่ง​มาก​พอ” จะ​รู้​ว่า​จุด​ไหน​ลัด​ได้​และ​จุด​ไหน​ลัด​ไม่​ได้ แต่​ใน​ทีม​จริง คำ​ว่า “duct tape” มัก​ถูก​ใช้​เป็น​ข้อ​อ้าง​ให้​กับ​พฤติกรรม​ที่แท้​จริง​แล้ว​คือ ความ​ประมาท​เชิง​วิชาชีพ: ไม่​เขียน​เทส ไม่​แยก​ความ​รับผิดชอบ ไม่​จัดการ error อย่าง​เหมาะสม และ​ไม่​ทิ้ง​ร่องรอย​ให้​คน​อื่น​เข้าใจ code ได้ ยิ่ง​ไป​กว่า​นั้น การ​ปะ​แก้​แบบ​นี้​มัก​ไม่ใช่​ทาง​เลือก​เดียว​ครั้ง​เดียว แต่​กลาย​เป็น “วิธี​ทำงาน​ประจำ” ที่​สะสม​ความ​เสี่ยง​ไป​เรื่อย ๆ จนถึง​จุด​ที่​ระบบ​เปราะบาง​เกิน​จะ​แก้ไข​เพิ่ม​ได้​อีก

  • ผลลัพธ์​มอง​เห็น​ได้​ทันที — feature ทำงาน​ได้ ลูกค้า​พอใจ ผู้จัดการ​ชม สิ่ง​เหล่า​นี้​เกิด​ขึ้น​เร็ว​กว่า​การ​ออกแบบ​ที่​ถูกต้อง
  • “Worse is Better” — Richard P. Gabriel เคย​เขียน​ไว้​ตั้งแต่​ปี 1989 ว่า​ซอฟต์แวร์​ที่ “ง่าย ๆ แต่​ใช้ได้​ก่อน” มัก​เอาชนะ​ซอฟต์แวร์​ที่ “สมบูรณ์​แบบ​แต่​มา​ทีหลัง” ใน​ตลาด​จริง เพราะ​ผู้​ใช้​กลุ่ม​แรก​จะ​สร้าง lock-in ให้​กับ design ที่​ยัง​ไม่​สมบูรณ์​นั้น แนวคิด​นี้​ถูก​อ้าง​ซ้ำ​แล้ว​ซ้ำ​เล่า​เพื่อ​สร้าง​ความ​ชอบธรรม​ให้​กับ​การ​ลัด​คุณภาพ
  • วัฒนธรรม​ฮีโร่ — ทีม​ที่​มี​เดดไลน์​บีบ​มัก​ให้​รางวัล​คน​ที่ “ดับ​ไฟ” ได้​เร็ว มากกว่า​คน​ที่​ป้องกัน​ไฟ​ตั้งแต่​แรก คน​ที่​ปะ code สำเร็จ​จึง​ถูก​มอง​ว่า​เป็น​ฮีโร่ ทั้ง​ที่​จริง​เขา​อาจ​กำลัง​สร้าง​ไฟ​กอง​ใหญ่​กว่า​เดิม​ไว้​รอ​วัน​ข้าง​หน้า
  • ดู​ขัดแย้ง​กับ “over-engineering” น้อย​กว่า — เมื่อ​เทียบ​กับ Architecture Astronaut ที่​ใช้​เวลา​ออกแบบ​ไม่รู้​จบ​แต่​ไม่​เคย​ส่ง​มอบ การ​ลงมือ​ทำ​ทันที​ย่อม​ดู​เป็น​ทาง​เลือก​ที่ “สม​เหตุ​สม​ผลกว่า” ใน​สายตา​คนนอก

Martin Fowler อธิบาย​ไว้​ใน Technical Debt Quadrant ว่า​หนี้​ทาง​เทคนิค​มี 2 มิติ: ตั้งใจ​หรือ​ไม่​ตั้งใจ (deliberate/inadvertent) และ​รอบคอบ​หรือ​ประมาท (prudent/reckless) การ​ตัดสิน​ใจ​แบบ “เดดไลน์​บีบ​จน​ต้อง​ลัด” ที่มา​พร้อม​การ​ประเมิน​ความ​เสี่ยง​ไว้​ล่วงหน้า (deliberate-prudent) ยัง​พอ​รับ​ได้ แต่ Duct Tape Coder ส่วน​ใหญ่​ตก​อยู่​ใน​โซน deliberate-reckless หรือ​แม้แต่ inadvertent-reckless — คือ​รู้​ว่า​ลัด​แต่​ไม่​ประเมิน​ราคา​ที่​ต้อง​จ่าย​คืน หรือ​แย่​กว่า​นั้น​คือ​ไม่รู้ตัว​ด้วย​ซ้ำ​ว่า​กำลัง​สร้าง​หนี้​อยู่

Ayende Rahien วิจารณ์​บทความ​ของ Spolsky ไว้​ตรง ๆ ว่า การ ship ซอฟต์แวร์​ครั้ง​เดียว​มัน​ง่าย แต่​โจทย์​จริง​ของ​งาน​วิชาชีพ​คือ “การ ship ซอฟต์แวร์​ต่อ​เนื่อง” เขา​ยก​กรณี Netscape เป็น​ตัวอย่าง: เบราว์เซอร์​ที่​ไม่​เสถียร​เรื้อรัง ต้อง​เขียน code ใหม่​ทั้งหมด​ซึ่ง​กิน​เวลา 5-6 ปี และ​สุดท้าย​ก็​แพ้​ทาง​ให้ Internet Explorer ปัญหา​เชิง​ลึก​ของ Duct Tape Coder มี​ดังนี้

  • ไม่​มี​เทส​รองรับ — เมื่อ​ไม่มี automated test การ​แก้ไข​ครั้ง​ต่อ​ไป​จะ​เสี่ยง​ต่อ regression ทุก​ครั้ง ทำให้​ทีม​กลัว​การ​เปลี่ยนแปลง (ดู Poor Tests)
  • Bus factor ต่ำ — code ที่​ปะ​แบบ​ไม่มี​โครงสร้าง​มัก​เข้าใจ​ได้​เฉพาะ​คน​ที่​เขียน เมื่อ​คน​นั้น​ลาออก​หรือ​โยกย้าย ทีม​ที่​เหลือ​แทบ​ต้อง​เขียน​ใหม่​ทั้งหมด
  • หนี้​สะสม​แบบ​ทบ​ต้น — การ​ลัด​ครั้ง​แรก​ดูเหมือน​ไม่มี​อะไร แต่​การ​ลัด​ซ้ำ ๆ บน​ฐาน​ที่​ไม่​มั่นคง​จะ​ทำให้​ทุก feature ใหม่​ใช้​เวลา​นาน​ขึ้น​เรื่อย ๆ จนถึง​จุด​ที่​ความเร็ว​ใน​การ​ส่ง​มอบ​กลับ​ช้า​กว่า​ทีม​ที่​ลงทุน​กับ​คุณภาพ​ตั้งแต่​ต้น
  • ปน​กับ Broken Windows — เมื่อ code ส่วน​หนึ่ง “ดูรก” อยู่​แล้ว คน​อื่น​ใน​ทีม​ก็​มัก​รู้สึก​ว่า​ลัด​เพิ่ม​อีก​นิด​ก็​ไม่​เป็นไร ทำให้​ทั้ง codebase เสื่อม​สภาพ​เร็ว​กว่า​ที่​ควร (ดู Broken Windows)
  • สับสน​ระหว่าง​เก่ง​กับ​ลัด — สมมติฐาน​ของ Spolsky ที่​ว่า “ต้อง​เก่ง​มาก​พอ” ถึง​จะ​ลัด​ได้​อย่าง​ปลอดภัย ใน​ทาง​ปฏิบัติ​แทบ​วัด​ไม่​ได้​ล่วงหน้า ทีม​จึง​มัก​ค้น​พบ​ว่า​ตัวเอง​ลัด​ผิด​ที่​หลัง​จาก​ความ​เสียหาย​เกิด​ขึ้น​แล้ว

code ต่อ​ไป​นี้​เป็น​ตัวอย่าง​ทั่วไป​ของ Duct Tape Coder ใน​ระบบ​ประมวล​ผล​คำ​สั่ง​ซื้อ — ทำงาน​ได้​ใน​เดโม แต่​ซ่อน​ความ​เสี่ยง​ไว้​เต็ม​ไป​หมด: กลืน exception ทุก​ชนิด​แบบ​เงียบ ๆ, ต่อ SQL ด้วย string concatenation, ผสม​ตรรกะ​ธุรกิจ​กับ​การ​เข้าถึง​ข้อมูล​และ​การ​ส่ง​อีเมล​ไว้​ใน method เดียว และ​ไม่​มี​เทส​รองรับ​เลย

// ก่อนแก้: วิธีของ Duct Tape Coder — ปะให้ผ่าน ไม่สนโครงสร้าง
public class OrderProcessor
{
public void Process(int orderId, string customerEmail, decimal amount)
{
try
{
// ต่อ SQL ตรง ๆ เสี่ยง SQL injection และแก้ยากเวลา schema เปลี่ยน
var conn = new SqlConnection("Server=prod-db;Database=Orders;...");
conn.Open();
var cmd = new SqlCommand(
"UPDATE Orders SET Status='PAID', Amount=" + amount +
" WHERE Id=" + orderId, conn);
cmd.ExecuteNonQuery();
// ส่งอีเมลแบบ hardcode ปนอยู่กับ logic การชำระเงิน
var smtp = new SmtpClient("smtp.internal");
smtp.Send("orders@shop.com", customerEmail, "ชำระเงินสำเร็จ", "ขอบคุณค่ะ");
}
catch (Exception)
{
// กลืน error ทุกชนิดแบบเงียบ ๆ — ไม่ log ไม่แจ้งใคร
// "มันรันผ่านแล้วก็จบ ไม่มีเวลาคิดเรื่อง edge case"
}
}
}

การ refactor แยก​ความ​รับผิดชอบ​ตาม SRP, ใช้ dependency injection แทน​การ​สร้าง connection ตรง ๆ, จัดการ error อย่าง​เจาะจง และ​เปิด​ทาง​ให้​เขียน​เทส​ได้

// หลังแก้: แยกชั้นความรับผิดชอบ จัดการ error อย่างเจาะจง เทสได้
public class OrderProcessor
{
private readonly IOrderRepository _orders;
private readonly IPaymentNotifier _notifier;
private readonly ILogger<OrderProcessor> _logger;
public OrderProcessor(
IOrderRepository orders,
IPaymentNotifier notifier,
ILogger<OrderProcessor> logger)
{
_orders = orders;
_notifier = notifier;
_logger = logger;
}
public async Task ProcessAsync(OrderId orderId, Money amount)
{
try
{
var order = await _orders.GetAsync(orderId)
?? throw new OrderNotFoundException(orderId);
order.MarkAsPaid(amount);
await _orders.SaveAsync(order);
await _notifier.NotifyPaymentSucceededAsync(order);
}
catch (OrderNotFoundException ex)
{
_logger.LogWarning(ex, "ไม่พบคำสั่งซื้อ {OrderId}", orderId);
throw;
}
catch (SqlException ex)
{
_logger.LogError(ex, "บันทึกคำสั่งซื้อ {OrderId} ไม่สำเร็จ", orderId);
throw new OrderProcessingException(orderId, ex);
}
}
}

ผลลัพธ์: แต่ละ​ส่วน​เทส​แยก​กัน​ได้ (mock IOrderRepository, IPaymentNotifier), error ที่​เกิด​ขึ้น​จริง​ถูก log และ​ไม่​หาย​ไป​เงียบ ๆ และ​เมื่อ schema หรือ​ผู้​ให้​บริการ​อีเมล​เปลี่ยน ก็​แก้​แค่​จุด​เดียว​โดย​ไม่​กระทบ​ตรรกะ​ธุรกิจ

แผนภาพ​ด้าน​ล่าง​แสดง​วงจร​ที่ Duct Tape Coder มัก​ติด​อยู่ และ​จุด​ที่​ควร​ตัด​วงจร​นั้น

flowchart TD
  A[เดดไลน์บีบ] --> B[เขียน code ลัดขั้นตอน]
  B --> C[ส่งมอบได้เร็ว]
  C --> D[ได้รับคำชมว่าเก่ง]
  D --> A
  B --> E[ไม่มี tests ไม่มี design]
  E --> F[หนี้ทางเทคนิคสะสม]
  F --> G[แก้ไขแต่ละจุดยากขึ้นเรื่อย]
  G --> H[ระบบเปราะจนต้อง rewrite ทั้งหมด]

แนวทาง​ที่​สมดุล​กว่า​ไม่ใช่​การ​ห้าม​ลัด​คุณภาพ​เด็ดขาด แต่​คือ​ทำให้​ทุก​การ​ลัด​เป็น deliberate & prudent ตาม Technical Debt Quadrant ของ Fowler:

  • ตั้งใจ​ก่อน​ลัด ไม่ใช่​ลัด​โดย​ไม่รู้ตัว — ถ้า​จำเป็น​ต้อง​ข้าม​เทสหรือ design ที่​ดี​เพื่อ​เดดไลน์ ให้​บันทึก​ไว้​ชัดเจน (ticket/comment) ว่า​ลัด​ตรง​ไหน​และ​ทำไม เพื่อ​ให้​ทีม​กลับ​มา​จ่าย​คืน​ได้ ไม่ใช่​ลืม​ไป​เลย
  • refactor เป็น​นิสัย ไม่ใช่ project แยก — ใช้ Refactoring และ Boy Scout Rule คือ “ทิ้ง code ให้​สะอาด​กว่า​ตอน​ที่​เจอ” ทุก​ครั้ง​ที่​แตะ file เดิม แทนที่​จะ​รอ “sprint ทำความ​สะอาด” ที่​มัก​ไม่มี​วัน​มา​ถึง (ดู Boy Scout Rule)
  • มี​เทสก่อนขยาย code เก่าTest-Driven Development ช่วย​ให้​มั่นใจ​ว่าการ​แก้ไข​ใน​อนาคต​ไม่​ทำลาย​พฤติกรรม​เดิม​โดย​ไม่รู้ตัว แม้​จะ​เริ่ม​จาก​เทส​แบบ characterization test บน code ที่​ยัง​ไม่​สะอาด​ก็ตาม
  • แยก​วงจร​ฟีด​แบ็ก​จาก​แรง​กดดัน​เดดไลน์ — ปัญหา​จริง​มัก​ไม่ใช่​ตัว​โปรแกรมเมอร์ แต่​คือ​วัฒนธรรม​ที่​ให้​รางวัล​ความเร็ว​อย่าง​เดียว ผู้จัดการ​ควร​วัดผล​จาก sustainable pace ไม่ใช่​จำนวน​ไฟ​ที่​ดับ​ได้​ต่อ​สัปดาห์ ไม่​เช่น​นั้น​ทีม​จะ​เข้า​สู่ Death March ใน​ระยะ​ยาว
  • อย่า​แกว่ง​ไป​อีก​สุด​ขั้ว — คำ​ตอบ​ไม่ใช่​การ​ออกแบบ​ทุก​อย่าง​ล่วงหน้า​จน​ไม่​ได้​ส่ง​มอบ (Fast Beats Right เตือน​ไว้​อีก​ด้าน) เป้าหมาย​คือ YAGNI — ทำ​เท่า​ที่​จำเป็น​แต่​ทำให้​ถูกต้อง ไม่ใช่​ทำ​น้อย​แบบ​ไม่​รับผิดชอบ