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

Progressive Disclosure

เผย​ความ​ซับซ้อน​ที​ละ​น้อย​เมื่อ​ผู้​อ่าน​พร้อม

Progressive Disclosure คือ​หลักการ​ของ​การ​เผย​ความ​ซับซ้อน​ออก​มา​ที​ละ​น้อย แทนที่​จะ​เท​ออก​มา​ทั้งหมด​ใน​คราว​เดียว ข้อมูล feature หรือ​รายละเอียด​จะ​ถูก​แนะนำ​แบบ​ค่อย​เป็น​ค่อย​ไป ตาม​ที่​จำเป็น​หรือ​ตาม​ความ​พร้อม​ของ​ผู้รับ แนวคิด​พื้นฐาน​ถูก​อธิบาย​ไว้​ตั้งแต่​ปี 1985 โดย Kristina Hooper Woolsey จาก Apple’s Human Interface Group ว่า​เป็นการ “เลือกสรร​ข้อมูล​บาง​ส่วน​มา​สื่อสาร​กับ​ผู้​ใช้” แทนที่​จะ​บอก​ทุก​อย่าง​ใน​คราว​เดียว ต่อ​มา Jakob Nielsen ผู้​ร่วม​ก่อตั้ง Nielsen Norman Group ได้​ทำให้ pattern นี้​เป็น​ที่​รู้จัก​กว้าง​ขวาง​ใน​วงการ UX โดย​นิยาม​ว่า​เป็นการ​แสดง “ตัว​เลือก​สำคัญ​เพียง​ไม่​กี่​ตัว” ให้​เห็น​ก่อน แล้ว​ค่อย​เปิด​ชุด​ตัว​เลือก​เฉพาะ​ทาง​ที่​ใหญ่​กว่า​เมื่อ​ผู้​ใช้​ร้องขอ

แม้​จะ​มี​ต้น​กำเนิด​จาก​การ​ออกแบบ UX แต่​หลักการ​นี้​นำ​ไป​ใช้ได้​กว้าง​ขวาง​กว่า​นั้น​มาก ทั้ง​กับ​สถาปัตยกรรม​ซอฟต์แวร์ การ​ออกแบบ API การ​สื่อสาร​เชิง​เทคนิค และ​ล่าสุด​คือ​การ​ออกแบบ​ระบบ AI agent หัวใจ​ร่วม​กัน​คือ การ​จัด​โครงสร้าง​ข้อมูล​เป็น​ชั้น (layer) โดย​ชั้น​แรก​ครอบคลุม​กรณี​ทั่วไป​และ​ตัว​เลือก​ที่​สำคัญ​ที่สุด ส่วน​ชั้น​ลึก​กว่า​จะ​ถูก​เปิด​ออก​มา​ก็​ต่อ​เมื่อ​ผู้รับ​ต้องการ​หรือ​พร้อม​รับ​มัน​จริง ๆ

ความ​จำ​ใช้งาน (working memory) ของ​มนุษย์​มี​จำกัด เมื่อ​ผู้​ใช้​หรือ​ผู้​อ่าน​ถูก​ยัดเยียด​ข้อมูล​มาก​เกิน​ไป​ใน​คราว​เดียว ภาวะ cognitive overload จะ​เกิด​ขึ้น ทำให้​ประมวล​ผล จดจำ หรือ​ลงมือ​ทำ​สิ่ง​ใด​ได้​ยาก​ขึ้น Progressive Disclosure จัดการ​ความ​ซับซ้อน​เชิง​ความคิด​ด้วย​การ​จัด​โครงสร้าง​ข้อมูล​เป็น​ชั้น ๆ ให้​ซึมซับ​แต่ละ​ชั้น​ได้​ก่อน​จะ​แนะนำ​ชั้น​ถัด​ไป

model AI เอง​ก็​ขึ้นชื่อ​เรื่อง​ความ​จำ​ใช้งาน​ที่​จำกัด นั่น​คือ context window ดังนั้น​หลักการ​นี้​จึง​ใช้ได้​ดี ถ้า​ไม่​มากกว่า สำหรับ AI agent ด้วย และ​สอดคล้อง​กับ Principle of Least Astonishment — ระบบ​ที่​เผย​ความ​ซับซ้อน​ที​ละ​น้อย​มัก​ตรง​กับ​ความ​คาด​หวัง​ของ​ผู้​ใช้​มากกว่า​ระบบ​ที่​เปิดเผย​ทุก​อย่าง​ตั้งแต่​แรก

งาน​วิจัย​ของ Nielsen (2006) สรุป​ว่า Progressive Disclosure ช่วย​ยก​ระดับ​ความ​สามารถ​ใน​การ​ใช้งาน​สาม​ด้าน​พร้อม​กัน ได้แก่ learnability (มือใหม่​เรียนรู้​ได้​เร็ว​ขึ้น​เพราะ​เห็น​เฉพาะ​สิ่ง​จำเป็น) efficiency of use (ผู้เชี่ยวชาญ​ไม่​ต้อง​ไล่​สายตา​ผ่าน​ตัว​เลือก​ที่​ไม่​เกี่ยวข้อง) และ error rate (ตัว​เลือก​ที่​ซับซ้อน​หรือ​ทำลาย​ข้อมูล​ถูก​ซ่อน​ไว้​จนกว่า​จะ​ตั้งใจ​เรียก​ใช้​จริง) หัวใจ​ของ​การ​ออกแบบ​ที่​ดี​คือ​การ​แบ่ง​เส้น​ให้​ถูก​จุด สิ่ง​ที่​ผู้​ใช้​ส่วน​ใหญ่​ต้องการ​บ่อย ๆ ควร​อยู่​ใน​ชั้น​แรก ส่วน​ทาง​ไป​สู่​ชั้น​ลึก​กว่า​ต้อง​มอง​เห็น​ชัดเจน​และ​มี “information scent” ที่​ดี ไม่ใช่​ซ่อน​จน​หาไม่​เจอ

ใน​บริบท​ของ​ซอฟต์แวร์​และ AI หลักการ​นี้​สำคัญ​เพราะ:

  • ลด onboarding friction — code API หรือ​เอกสาร​ที่​เปิดเผย​ทุก​อย่าง​พร้อม​กัน​ทำให้​ผู้​อ่าน​ใหม่​ไม่รู้​จะ​เริ่ม​จาก​ตรง​ไหน
  • ป้องกัน context overflow — ทั้ง​ของ​มนุษย์ (working memory) และ​ของ LLM agent (context window ที่​จำกัด)
  • รักษา​พื้น​ผิว​ง่าย ๆ ให้​เรียบ​ง่าย​จริง ๆ ขณะ​ที่​ยัง​เปิด​ทาง​ให้​ผู้เชี่ยวชาญ​เข้าถึง​ความ​สามารถ​เต็ม​รูปแบบ​ได้ ซึ่ง​ทับซ้อน​กับ​หลักการ Keep It Simple — สิ่ง​ที่​ง่าย​ควร​ทำได้​ง่าย ส่วน​สิ่ง​ที่​ซับซ้อน​ก็​ควร​เป็น​ไป​ได้
  • UX แบบ​คลาสสิก — กล่อง​โต้ตอบ Print บน macOS แสดง​ตัว​เลือก​พื้นฐาน​เพียง​ไม่​กี่​รายการ แล้ว​ให้​ผู้​ใช้​กด​ปุ่ม “Show Details” เพื่อ​เปิด​ตัว​เลือก​ขั้น​สูง; แบบ​ฟอร์ม​ที่​แสดง​เฉพาะ​ช่อง​จำเป็น​ก่อน​แล้ว​ค่อย​เผย​ช่อง​เสริม; expandable section ที่​ยุบ​เนื้อหา​รอง​ไว้​หลัง​ปุ่ม “แสดง​เพิ่มเติม”
  • งาน​นำ​เสนอ — สไลด์ “กำแพง​ข้อความ” (wall of text) ที่​เปิด​ไดอะแกรม​ซับซ้อน​ทั้งหมด​ใน​คราว​เดียว​คือ​ความ​ล้มเหลว​ที่​พบ​บ่อย วิธี​ที่​ดี​กว่า​คือ​เผย​เนื้อหา​ที​ละ​จุด​หรือ​ที​ละ​ส่วน​ของ​ไดอะแกรม เพื่อ​ให้​ผู้​ฟัง​โฟกัส​กับ​สิ่ง​ที่​กำลัง​พูด​ถึง​อยู่
  • เกม — ด่าน​แรก​มัก​นำ​เสนอ​เพียง​การ​ควบคุม​พื้นฐาน ส่วน power-up อุปสรรค และ​กลไก​ขั้น​สูง​จะ​ถูก​แนะนำ​ที​ละ​อย่าง​ใน​บริบท​ที่​เกี่ยวข้อง
  • แผนที่​และ GIS — ระบบ map tile (Google Maps, OpenStreetMap) และ image tile pyramid (เช่น มาตรฐาน IIIF สำหรับ​ภาพ​ทางการ​แพทย์​และ​งาน​ศิลปะ) แสดง​รายละเอียด​มาก​ขึ้น​เรื่อย ๆ เมื่อ​ผู้​ใช้​ซูม​เข้า แทนที่​จะ​โหลด​ทุก tile ทุก​ระดับ​ซูม​พร้อม​กัน
  • C4 Model — สถาปัตยกรรม​ซอฟต์แวร์​แบ่ง​เป็น​สี่​ระดับ abstraction คือ Context, Container, Component และ Code ไม่มี​ไดอะแกรม​ใด​พยายาม​แสดง​ทุก​อย่าง ผู้​มี​ส่วน​ได้​ส่วน​เสีย​ฝั่ง​ธุรกิจ​ต้องการ​เพียง​ไดอะแกรม​ระดับ Context ส่วน​นัก​พัฒนา​ที่​ลงมือ​ทำ feature หนึ่ง​อาจ​เจาะ​ลง​ไป​ถึง​ระดับ Component หรือ Code
  • AI agent และ LLM — แทนที่​จะ​โหลด​เอกสาร​อ้างอิง​ทุก​ฉบับ​เข้า context window ตั้งแต่​แรก ระบบ agent ที่​ออกแบบ​ดี​จะ​ให้​ข้อมูล​เพียงพอที่​จะ​เริ่มต้น พร้อม​ตัว​ชี้ (pointer) ไป​ยัง​เอกสาร​ละเอียด​ที่​ดึง​มา​ได้​เมื่อ​จำเป็น ทรัพยากร​อย่าง Model Context Protocol (MCP) server และ​คลัง skill ควร​ให้ metadata กระชับ เพื่อ​ให้ agent ตัดสิน​ใจ​ได้​ง่าย​ว่า​เมื่อไร​ควร​เรียก​ใช้​และ​โหลด​รายละเอียด​เพิ่ม​เข้า​มา

แผนภาพ​ด้าน​ล่าง​สรุป​แนวคิด​ของ​การ​เผย​ที​ละ​ชั้น ผู้​อ่าน​เริ่ม​ที่​สรุป​ระดับ​บน​สุด แล้ว​เลือก​เจาะ​ลึก​เฉพาะ​ส่วน​ที่​เกี่ยวข้อง​กับ​ตน​เท่านั้น:

flowchart TD
    Summary[ภาพรวมระดับบนสุด] --> Common[ตัวเลือกที่ใช้บ่อย]
    Common --> NeedMore{ต้องการรายละเอียดเพิ่มไหม}
    NeedMore -->|ไม่| Done[ใช้งานได้ทันที]
    NeedMore -->|ต้องการ| Advanced[ตัวเลือกขั้นสูง]
    Advanced --> Expert[การตั้งค่าเฉพาะทาง]

ซอฟต์แวร์​มี​ลำดับ​ชั้น​ของ scope โดย​ธรรมชาติ​ที่​รองรับ Progressive Disclosure เมื่อ​สำรวจ codebase ที่​ไม่​คุ้น​เคย การ​เริ่ม​จาก​ระดับ application แล้ว​เจาะ​ลง​ไป​เป็น​เส้นทาง​ที่​เป็น​ธรรมชาติ Robert C. Martin เรียก​แนวคิด​ที่​เกี่ยวข้อง​กัน​นี้​ว่า “step-down rule” — แต่ละ function ควร​อยู่​ใน​ระดับ abstraction เดียว และ​เรียก function ใน​ระดับ​ที่​ต่ำ​กว่า​ลง​ไป​ที​ละ​ขั้น เพื่อ​ให้​ผู้​อ่าน​ไล่​จาก​ภาพ​รวม​ลง​ไป​หา​รายละเอียด​ได้​อย่าง​เป็น​ธรรมชาติ เช่น​เดียว​กับ​ที่ Mark Seemann อธิบาย​ไว้​ใน Code That Fits In Your Head

ตัวอย่าง code: พื้น​ผิว API ที่​เผย​ความ​ซับซ้อน​ที​ละ​ชั้น

หัวข้อ​ที่​มีชื่อ​ว่า “ตัวอย่าง code: พื้น​ผิว API ที่​เผย​ความ​ซับซ้อน​ที​ละ​ชั้น”

ไม่​ดี — method เดียว​บังคับ​ให้​ผู้​เรียก​ต้อง​รู้​และ​กรอก parameter ทุก​ตัว​ตั้งแต่​แรก แม้​ส่วน​ใหญ่​จะ​ใช้​ค่า​มาตรฐาน ผู้​อ่าน​โค้ง​การ​เรียนรู้​สูง​เพราะ​ต้อง​ทำความ​เข้าใจ​ตัว​เลือก​ขั้น​สูง​ก่อน​จะ​ส่ง​อีเมล​ง่าย ๆ สัก​ฉบับ:

// ไม่ดี: ต้องรู้ทุกรายละเอียดตั้งแต่ครั้งแรกที่เรียกใช้
public class EmailSender
{
public void Send(
string to,
string subject,
string body,
bool isHtml,
int retryCount,
TimeSpan retryDelay,
string smtpOverrideHost,
int smtpOverridePort,
bool useTls,
Dictionary<string, string> customHeaders,
string trackingId)
{
// ผู้เรียกต้องตัดสินใจเรื่อง retry, smtp override, custom header
// ทั้งที่ 90% ของการใช้งานจริงแค่อยากส่งอีเมลธรรมดา
}
}

ดี — พื้น​ผิว​เริ่มต้น​เรียบ​ง่าย ครอบคลุม​กรณี​ทั่วไป​ด้วย​ค่า​เริ่มต้น​ที่​สม​เหตุ​สม​ผล ส่วนตัว​เลือก​ขั้น​สูง​ถูก​ย้าย​ไป​ไว้​ใน options object ที่​เปิด​ให้ override ได้​เมื่อ​จำเป็น​เท่านั้น:

// ดี: ชั้นแรกเรียบง่าย ชั้นลึกกว่าเข้าถึงได้เมื่อต้องการจริง
public class EmailSender
{
// เคสทั่วไป 90% ใช้ overload นี้พอ
public void Send(string to, string subject, string body)
=> Send(to, subject, body, EmailOptions.Default);
// เคสขั้นสูงเปิดผ่าน options ที่มีค่า default อยู่แล้ว
public void Send(string to, string subject, string body, EmailOptions options)
{
// ใช้ options.RetryCount, options.SmtpOverrideHost, ฯลฯ
// เมื่อผู้เรียกตั้งใจ override เท่านั้น
}
}
public class EmailOptions
{
public static EmailOptions Default => new();
public bool IsHtml { get; init; } = false;
public int RetryCount { get; init; } = 3;
public TimeSpan RetryDelay { get; init; } = TimeSpan.FromSeconds(5);
public string? SmtpOverrideHost { get; init; }
public int SmtpOverridePort { get; init; } = 587;
public bool UseTls { get; init; } = true;
public Dictionary<string, string> CustomHeaders { get; init; } = new();
public string? TrackingId { get; init; }
}

ผู้​เรียก​ทั่วไป​เห็น​เพียง Send(to, subject, body) — พื้น​ผิว​ที่​ค้น​พบ​และ​ใช้งาน​ได้​ทันที ส่วน​ผู้​ที่​ต้องการ​ควบคุม retry หรือ SMTP override สามารถ​เจาะ​ลง​ไป​ยัง EmailOptions ได้​โดย​ไม่​กระทบ​ผู้​เรียก​ราย​อื่น รูปแบบ​นี้​ยัง​เป็น​สิ่ง​ที่ pattern อย่าง Facade ทำ​อยู่​เสมอ คือ​ซ่อน​ความ​ซับซ้อน​ของ subsystem ไว้​หลัง​พื้น​ผิว​ที่​เรียบ​ง่าย​กว่า

  • method หรือ constructor ที่​มี parameter ยาวเหยียด ผู้​เรียก​ต้อง​ตัดสิน​ใจ​เรื่อง​ขั้น​สูง​ตั้งแต่​การ​เรียก​ใช้งาน​พื้นฐาน​ที่สุด (ดู code smell Long Parameter List)
  • file config ที่​บังคับ​ให้​กำหนด​ทุก field อย่าง​ชัดเจน โดย​ไม่มี​ค่า default ที่​สม​เหตุ​สม​ผล
  • สไลด์​หรือ​เอกสาร​แบบ “กำแพง​ข้อความ” ที่​เท​ข้อมูล​ทุก​ระดับ​รายละเอียด​ออก​มา​พร้อม​กัน​ใน​หน้า​เดียว
  • class หรือ method ที่​ปน​หลาย​ระดับ abstraction เข้า​ด้วย​กัน บังคับ​ให้​ผู้​อ่าน​เผชิญ​รายละเอียด​การ implement ระดับ​ล่าง​ก่อน​จะ​เข้าใจ​เจตนา​ระดับ​สูง (ดู code smell Abstraction Levels และ antipattern The Blob)
  • ยัด​เอกสาร​อ้างอิง​ทั้งหมด​เข้า context ของ LLM agent ตั้งแต่​ต้น แทนที่​จะ​ให้ pointer ไป​ยัง​รายละเอียด​ที่​ดึง​มา​เมื่อ​จำเป็น ทำให้​สิ้น​เปลือง context window และ​ลด signal-to-noise
  • UI ที่​ไม่มี​ทาง​ไป​สู่​รายละเอียด​ขั้น​สูง​เลย ผู้เชี่ยวชาญ​ถูก​บังคับ​ให้​ทำงาน​ผ่าน​พื้น​ผิว​ที่​เรียบ​ง่าย​เกิน​ไป​สำหรับ​ความ​ต้องการ​ของ​ตน (นี่​คือ​ฝั่ง​ตรง​ข้าม​ของ​ปัญหา คือ​เผย​น้อย​เกิน​ไป​จน​ไม่มี​ทางออก)