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

Assumption Driven Programming

สมมติ​ว่า​ผู้​ใช้​ทุก​คน​คิด​และ​ทำ​เหมือน​ตัว​นัก​พัฒนา

Assumption Driven Programming คือ​ภาวะ​ที่​นัก​พัฒนา​ตัดสิน​ใจ​เรื่อง​พฤติกรรม​ของ​ผู้​ใช้ กติกา​ของ​ข้อมูล หรือ flow การ​ทำงาน โดย​อิง​จาก “ถ้า​เป็น​ฉัน ฉัน​จะ​ทำ​แบบ​นี้” แทนที่​จะ​อิง​ข้อมูล​จริง​จาก​ผู้​ใช้ ปัญหา​ไม่​ได้​อยู่​ที่​การ​มี​สมมติฐาน — ทุก​การ​ตัดสิน​ใจ​ใน​งาน​ซอฟต์แวร์​ล้วน​มี​สมมติฐาน​แฝง​อยู่​บ้าง — แต่​อยู่​ที่​การ ไม่รู้ตัว​ว่า​กำลัง​สมมติ และ​ไม่​เคย​ตรวจสอบ​สมมติฐาน​นั้น​กับ​ความ​เป็น​จริง

มัน​แทรก​อยู่​ใน​ทุก​ระดับ ตั้งแต่​กติกา​ตรวจสอบ​ข้อมูล (validation) เช่น สมมติ​ว่า​ชื่อ​คน​ทุก​คน​เขียน​ด้วย ASCII และ​แยก​ชื่อ-นามสกุล​ชัดเจน ไป​จนถึง​การ​ออกแบบ flow ของ​หน้า​จอ​ที่​สมมติ​ว่า​ผู้​ใช้​จะ​เดิน​ตาม​เส้นทาง​ที่​นัก​พัฒนา​คิด​ไว้​ใน​หัว หรือ​แม้แต่​การ​ตีความ​กฎ​ธุรกิจ​ใน domain โดย​ไม่​เคย​ถาม​ผู้เชี่ยวชาญ domain จริง ๆ

วิธี​เลี่ยง​กับดัก​นี้​คือ​การ​มี​ปฏิสัมพันธ์​กับ​ผู้​ใช้​ให้​มาก​ที่สุด สำคัญ​ที่​จะ dogfood ซอฟต์แวร์​ของ​ตน แต่​ไม่ใช่​แทน​การ​ดู​ว่า​ลูกค้า​จริง​ใช้​มัน​อย่างไร ผู้​ใช้ app ของ​คุณ​ไม่​จำเป็น​ต้อง​ทำ​อย่าง​ที่​คุณ​ทำ​หรือ​ที่​คุณ​คิด​ว่า​เขา​ควร​ทำ

  • เร็ว​กว่า — ไม่​ต้อง​รอ stakeholder รอบ review หรือ​รอบ​สัมภาษณ์​ผู้​ใช้ ตัดสิน​ใจ​ได้​ทันที​ระหว่าง​เขียน code
  • รู้สึก​มั่นใจ — นัก​พัฒนา​คือ​คน​ที่​เข้าใจ​ระบบ​ลึก​ที่สุด​ใน​ทีม จึง​รู้สึก​ว่า​สัญชาตญาณ​ของ​ตัวเอง​น่า​เชื่อถือ นี่​คือ​ราก​ของ​อคติ​ที่​เรียก​ว่า false-consensus effect ซึ่ง​งาน​วิจัย​ของ Nielsen Norman Group ชี้​ว่า​คน​เรา​มัก​เชื่อ​ว่า​คน​อื่น​คิด​และ​ทำ​เหมือน​ตัวเอง​มากกว่า​ความ​เป็น​จริง
  • Dogfooding ให้​ความ​รู้สึก​ปลอดภัย​ลวง — เมื่อ​ทีม​ใช้​ซอฟต์แวร์​ของ​ตัวเอง​ทุก​วัน​แล้ว​ไม่​เจอ​ปัญหา ก็​ง่าย​ที่​จะ​สรุป​ว่า​ผู้​ใช้​ทั่วไป​ก็​จะ​ไม่​เจอ​ปัญหา​เหมือน​กัน ทั้ง​ที่​ทีม​พัฒนา​รู้ domain ทน​กับ rough edge ได้ และ​ใช้งาน​ใน​บริบท​ที่​ต่าง​จาก​ผู้​ใช้​จริง​อย่าง​มาก
  • แรง​กดดัน​เรื่อง​เวลา — ภาย​ใต้ deadline การ​เขียน spec แบบ “เดา​ว่า​คง​จะ​โอเค” ดูเหมือน​ถูก​กว่า​การ​รอ validate กับ​ผู้​ใช้​จริง โดย​เฉพาะ​เมื่อ requirement ดูเหมือน “สามัญสำนึก” อยู่​แล้ว

สมมติฐาน​ที่​ไม่​ได้​ตรวจสอบ​จะ​ถูก​ฝัง​ลง​ไป​ใน code จริง กลาย​เป็น constraint ที่​แข็ง​เป็น​หิน แต่​ไม่มี​ใคร​ตั้ง​คำถาม​อีก จนกว่า​ผู้​ใช้​จริง​จะ​ไป​เจอ​ขอบ (edge) ที่​สมมติฐาน​นั้น​พัง — ซึ่ง​ตอน​นั้น​มัน​ขึ้น production ไป​แล้ว และ​การ​แก้ไข​แพง​กว่า​ตอน​ออกแบบ​มาก

ตัวอย่าง​คลาสสิก​คือ​ชุด​บทความ “Falsehoods Programmers Believe About Names” ของ Patrick McKenzie ที่​รวบรวม​สมมติฐาน​ผิด ๆ เกี่ยว​กับ​ชื่อ​คน เช่น ชื่อ​ทุก​คน​เขียน​ด้วย​ตัว​อักษร ASCII, ชื่อ​มี​ความ​ยาว​จำกัด​ใน​ช่วง​ที่​คาด​ไว้, ทุก​คน​มีชื่อ-นามสกุล​แยก​กัน​ชัดเจน และ​ใน​สังคม​เดียวกัน​ทุก​คน​เห็น​พ้อง​กับ​มาตรฐาน​การ​เขียน​ชื่อ​แบบ​เดียวกัน — ทั้งหมด​นี้​ล้วน​มา​จาก​นัก​พัฒนา​ที่​มอง​โลก​ผ่าน​ประสบการณ์​ชื่อ​ของ​ตัวเอง​แล้ว​ขยาย​ผล​ไป​ทั้ง​โลก โครงการ awesome-falsehood บน GitHub ยัง​รวบรวม​รายการ​แบบ​เดียวกัน​ใน​หัวข้อ​อื่น ๆ เช่น ที่​อยู่ เบอร์​โทร เวลา และ​สกุล​เงิน แสดง​ให้​เห็น​ว่า​อคติ​นี้​เกิด​ซ้ำ ๆ ใน​ทุก domain

Nielsen Norman Group อธิบาย​ว่า​นี่​คือ false-consensus effect ซึ่ง​เป็น​อคติ​ทาง​ความคิด​ที่​ทำให้​เรา​ประเมิน​สูง​เกิน​ไป​ว่า​คน​อื่น​เชื่อ​และ​ทำ​เหมือน​ที่​เรา​เชื่อ​และ​ทำ นัก​ออกแบบ​และ​นัก​พัฒนา​ที่​ตก​อยู่​ใน​อคติ​นี้​มัก​มอง​ว่า interface ของ​ตัวเอง “เข้าใจ​ง่าย​อยู่​แล้ว” เพราะ​มัน​เข้าใจ​ง่าย​สำหรับ​ตัวเอง แล้ว​ตัดสิน​ว่า​ผู้​ใช้​ที่​สับสน​คือ​คน​ที่ “ใช้​ผิด” ทั้ง​ที่​จริง​ปัญหา​อยู่​ที่​การ​ออกแบบ

อันตราย​อีก​ชั้น​คือ​สมมติฐาน​สะสม​กัน​แบบ​เงียบ ๆ — แต่ละ​จุด​ตัดสิน​ใจ​ที่​ไม่​ถูก​ตรวจสอบ​เพิ่ม​ระยะ​ห่าง​ระหว่าง model ใน​หัว​นัก​พัฒนา​กับ​พฤติกรรม​จริง​ของ​ผู้​ใช้​ที​ละ​นิด จน​วัน​หนึ่ง​ช่องว่าง​นั้น​ใหญ่​เกิน​กว่า​จะ​แก้​ด้วย​การ​ปรับ​เล็กน้อย ต้อง​รื้อ​ออกแบบ​ใหม่​ทั้ง feature ซึ่ง​เป็นต้นทุน​ที่​ป้องกัน​ได้​ตั้งแต่​ต้น​ถ้า​ตรวจสอบ​สมมติฐาน​เนิ่น ๆ

code ต่อ​ไป​นี้​ฝัง​สมมติฐาน​ส่วนตัว​ของ​นัก​พัฒนา​ไว้​โดย​ไม่รู้ตัว ทั้ง​เรื่อง​รูปแบบ​ชื่อ​และ​สกุล​เงิน:

// สมมติฐาน: ชื่อผู้ใช้ทุกคนเขียนด้วยตัวอักษร ASCII และมี "ชื่อ" กับ "นามสกุล" แยกกันชัดเจน
public class CustomerNameValidator
{
private static readonly Regex NamePattern = new(@"^[A-Za-z]+\s[A-Za-z]+$");
public bool IsValid(string fullName) => NamePattern.IsMatch(fullName);
}
public class CheckoutService
{
public OrderConfirmation Checkout(Order order)
{
// สมมติฐาน: ลูกค้าทุกคนจ่ายด้วยสกุลเงิน USD เท่านั้น เพราะทีมพัฒนานั่งอยู่ในสหรัฐฯ
var totalUsd = order.Items.Sum(i => i.PriceUsd * i.Quantity);
return new OrderConfirmation(totalUsd, "USD");
}
}

ผล​คือ​ผู้​ใช้​ที่​ชื่อ​มี​คำ​เดียว (เช่น​ชื่อ​ไทย​หลาย​คน) หรือ​เขียน​ด้วย​อักษร​ที่​ไม่ใช่​ละติน จะ​ถูก​ปฏิเสธ​ที่​หน้า​สมัคร​สมาชิก​ทันที และ​ลูกค้า​นอกสหรัฐฯ จะ​เห็น​ยอด​เป็น USD ที่​ไม่​ตรง​กับ​สิ่ง​ที่​ธนาคาร​ของ​ตัวเอง​เรียก​เก็บ — ทั้ง​สอง​จุด​ไม่​เคย​ถูก​ตั้ง​เป็น​คำถาม​ที่​ต้อง​ตรวจสอบ​เลย เพราะ “ดู​เป็น​สามัญสำนึก” สำหรับ​ทีม​ที่​เขียน​มัน​ขึ้น​มา

refactor ให้​สมมติฐาน​กลาย​เป็น​สิ่ง​ที่​ตรวจสอบ​ได้​และ​อิง​ข้อมูล​จริง แทนที่​จะ hardcode ความ​เชื่อ​ส่วนตัว:

public class CustomerNameValidator
{
// ไม่บังคับรูปแบบชื่อ ปล่อยกว้างที่สุดเท่าที่ระบบ downstream รองรับได้
// ค่า MaxNameLength มาจากการสำรวจความยาวชื่อจริงของผู้ใช้ ไม่ใช่ค่าที่เดาเอาเอง
private const int MaxNameLength = 200;
public bool IsValid(string fullName) =>
!string.IsNullOrWhiteSpace(fullName) && fullName.Length <= MaxNameLength;
}
public class CheckoutService
{
private readonly ICurrencyProvider _currencyProvider; // อ้างอิง locale ของลูกค้าจริง ไม่ใช่ hardcode
public CheckoutService(ICurrencyProvider currencyProvider) =>
_currencyProvider = currencyProvider;
public OrderConfirmation Checkout(Order order)
{
var currency = _currencyProvider.ResolveFor(order.Customer);
var total = order.Items.Sum(i => i.Price.ConvertTo(currency) * i.Quantity);
return new OrderConfirmation(total, currency);
}
}

จุด​สำคัญ​ไม่ใช่​แค่ “code หลัง​แก้​ดูถูก​กว่า” แต่​คือ​ที่มา​ของ​ค่า​คงที่​และ dependency แต่ละ​ตัว​เปลี่ยน​จาก “นัก​พัฒนา​คิด​ว่า​ใช่” ไป​เป็น “มา​จาก​ข้อมูล​ผู้​ใช้​จริง​หรือ service ที่​ตรวจสอบ​ได้”

หัวใจ​ของ​ทาง​แก้​คือ​เปลี่ยน​สมมติฐาน​ที่​ซ่อน​อยู่​ใน​หัว​ให้​กลาย​เป็น สมมติฐาน​ที่​ทดสอบ​ได้​อย่าง​ชัดเจน แล้ว​วน loop ตรวจสอบ​กับ​ข้อมูล​จริง​ก่อน​ที่​มัน​จะ​ฝัง​ลง code:

flowchart TD
    A[เจอจุดตัดสินใจเกี่ยวกับผู้ใช้] --> B{มีข้อมูลผู้ใช้จริงรองรับไหม}
    B -- ไม่มี ใช้สัญชาตญาณล้วน --> C[เขียน code ตามที่ตนเองคิดว่าถูก]
    C --> D[ปล่อยใช้งานจริง]
    D --> E[ผู้ใช้เจอปัญหาที่ไม่คาดคิด]
    E --> F[ต้องแก้ทีหลังด้วยต้นทุนสูง]
    B -- มี หรือระบุเป็นสมมติฐานที่ตรวจสอบได้ --> G[เก็บ feedback หรือทำ usability test เล็ก ๆ]
    G --> H[ปรับการตัดสินใจตามข้อมูลจริง]
    H --> I[ปล่อยใช้งานจริงอย่างมั่นใจ]

แนวทาง​ปฏิบัติ​ที่​ช่วย​ได้​จริง:

  • แปลง​สมมติฐาน​ให้​เป็น​ข้อความ​ชัดเจน​ที่​เขียน​ออก​มา​ได้ เช่น “เรา​เชื่อ​ว่า​ผู้​ใช้ 80% จะ​กรอก​ที่​อยู่​จัด​ส่ง​เดียว​กับ​ที่​อยู่​เรียก​เก็บ​เงิน” แล้ว​จัด​ลำดับ​ตาม​ความ​เสี่ยง ก่อน​ตรวจสอบ​ด้วย​วิธี​ที่​ถูก​ที่สุด​ก่อน เช่น prototype เล็ก ๆ หรือ spike
  • ใช้​เครื่องมือ analytics/observability ดู​เส้นทาง​ที่​ผู้​ใช้​เดิน​จริง​ใน​ระบบ แทนที่​จะ​เดา​จาก flow ที่​ออกแบบ​ไว้​ใน​หัว (ดู Know Where You Are Going)
  • เปิด​ช่อง​ทาง feedback ที่​เข้าถึง​ง่าย และ​ตรวจสอบ​มัน​สม่ำเสมอ ไม่ใช่​แค่​ตอน​มี​คน​บ่น​ดัง ๆ
  • แบ่ง persona ตาม​ข้อมูล​จริง สำหรับ​ผลิตภัณฑ์​ขนาด​ใหญ่​ที่​มี​ผู้​ใช้​หลาย​กลุ่ม แทนที่​จะ​สร้าง persona เดียว​ที่​หน้าตา​เหมือน​ทีม​พัฒนา
  • Dogfood ควบคู่​กับ​ผู้​ใช้​ทดสอบ​ภายนอก เสมอ เพราะ​ทีม​พัฒนา​ไม่ใช่​กลุ่ม​ตัวอย่าง​ที่​เป็น​ตัวแทน​ของ​ผู้​ใช้​จริง
  • เขียน acceptance criteria ร่วม​กับ​ผู้​มี​ส่วน​ได้​ส่วน​เสีย​จริง​ผ่าน Behavior Driven Development แทน​การ​เดา​กฎ​ธุรกิจ​เอง และ​ตรวจสอบ​ภาษา​ที่​ใช้​กับ​ผู้เชี่ยวชาญ domain ให้​ตรง​กับ Ubiquitous Language ของ​ธุรกิจ​จริง ไม่ใช่​ศัพท์​ที่​ทีม​พัฒนา​คิด​ขึ้น​เอง