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

Postel’s Law

ใจ​กว้าง​กับ​สิ่ง​ที่​รับ​เข้า เข้มงวด​กับ​สิ่ง​ที่​ส่ง​ออก

Postel’s Law หรือ​ที่​รู้จัก​กัน​ใน​ชื่อ Robustness Principle ตั้ง​ชื่อ​ตาม Jon Postel วิศวกร​ผู้​บุกเบิก​อินเทอร์เน็ต ซึ่ง​เขียน​หลักการ​นี้​ไว้​ครั้ง​แรก​ใน spec IPv4 ปี 1979 และ​ปรากฏ​อีก​ครั้ง​ใน RFC 761 (มกราคม 1980) ซึ่ง​เป็น spec ยุค​แรก​ของ TCP

“Be conservative in what you do, be liberal in what you accept from others.” (จง​เข้มงวด​ใน​สิ่ง​ที่​คุณ​ทำ และ​ใจ​กว้าง​ใน​สิ่ง​ที่​คุณ​รับ​เข้า​มา​จาก​ผู้​อื่น)

— Jon Postel, RFC 761

มัก​ถูก​พูด​ถึง​ใน version สั้น​กว่า​ว่า “จง​เข้มงวด​ใน​สิ่ง​ที่​คุณ​ส่ง​ออก และ​ใจ​กว้าง​ใน​สิ่ง​ที่​คุณ​รับ​เข้า​มา” (be conservative in what you send, be liberal in what you accept) หลักการ​นี้​ถูก​เขียน​ขึ้น​เพื่อ​ให้ implementation ของ TCP ต่าง​ค่าย​ที่​สื่อสาร​กัน​ผ่าน spec ที่​ยัง​ไม่​สมบูรณ์​และ​คลุมเครือ สามารถ​ทำงาน​ร่วม​กัน​ได้​จริง แทนที่​จะ​พัง​เพราะ​ตีความ​รายละเอียด​ปลีกย่อย​ต่าง​กัน

เป้าหมาย​คือ​ลด​ความ​ล้มเหลว​ของ​ระบบ​ที่​เชื่อม​ต่อ​กัน การ​ยอมรับ input หลาย​รูปแบบ (เช่น endpoint ที่​รับ​ทั้ง XML และ JSON หรือ parser ที่​ยอมรับ header ที่​เขียน​ผิด​เล็กน้อย) ทำให้​ระบบ​ทำงาน​ร่วม​กับ client ที่​หลากหลาย​ได้ ขณะ​ที่​การ​ส่ง output ที่​สม่ำเสมอ​และ​ตรง spec ทำให้​ระบบ​อื่น​ที่​เรียก​ใช้​ไม่​ต้อง​มา​คอย​เดา​หรือ​เขียน code พิเศษ​รับมือ​ความ​ไม่​แน่นอน​จาก​ฝั่ง​เรา หลักการ​นี้​จึง​เป็น​เสา​หลัก​ต้นๆ ที่​ทำให้​อินเทอร์เน็ต​ยุค​แรก​ขยาย​ตัว​และ​อยู่​รอด​ได้ ทั้ง​ที่ spec จำนวน​มาก​ยัง​ไม่​รัดกุม​และ implementation แต่ละ​เจ้า​ก็​มี bug ของ​ตัวเอง

ฝั่ง​รับ (accept): โปรแกรม​ที่​รับ​ข้อความ​ควร​ยอมรับ input ที่​ไม่​ตรง spec ทุก​กระเบียด​นิ้ว ตราบ​ใด​ที่​ความหมาย (semantics) ยัง​ชัดเจน เช่น ยอมรับ field ที่​เรียง​ลำดับ​ผิด ยอมรับ​ค่าที่​ขาด​หาย​ไป​โดย​ใช้ default ที่​สม​เหตุ​สม​ผล หรือ​เพิกเฉย​ต่อ field แปลกปลอม​ที่​ตนเอง​ไม่รู้จัก​แทนที่​จะ​โยน exception

ฝั่ง​ส่ง (send): โปรแกรม​ที่​ส่ง​ข้อความ​ควร​เคารพ spec อย่าง​เคร่งครัด​ที่สุด ไม่​ใช้ shortcut หรือ feature นอก spec แม้​ระบบ​ปลายทาง​จะ​รองรับ​ได้​ใน​ทาง​ปฏิบัติ เพราะ output ที่ “เดา​ไม่​ยาก” และ​ไม่​สร้าง​เซอร์ไพรส์​ให้​ผู้​เรียก​คือ​สิ่ง​ที่​ทำให้​ระบบ​อื่น​วางใจ​เชื่อม​ต่อ​กับ​เรา​ได้ (แนวคิด​นี้​เกี่ยวโยง​โดยตรง​กับ Principle of Least Astonishment)

ใน​เชิง​ทฤษฎี​ชนิด​ข้อมูล (type theory) หลักการ​นี้​สะท้อน​แนวคิด covariance/contravariance ของ function: function ควร​จะ contravariant ต่อ​ชนิด​ของ input (ยอมรับ supertype ที่​กว้าง​กว่า​ที่​คาด​ไว้​ได้) แต่ covariant ต่อ​ชนิด​ของ output (คืน​ค่าที่​จำเพาะ​เจาะจง​กว่า​หรือ​เท่ากับ​ที่​สัญญา​ไว้​เท่านั้น) — เป็น​ภาพ​สะท้อน​ของ “ใจ​กว้าง​ตอน​รับ เข้มงวด​ตอน​ส่ง” ใน​ระดับ signature ของ code

Martin Fowler นำ​หลักการ​นี้​มา​เป็น​ฐาน​ของ pattern ที่​เรียก​ว่า Tolerant Reader: เวลา​อ่าน​ข้อมูล​จาก​ระบบ​ภายนอก (เช่น XML/JSON response) ให้​ดึง​เฉพาะ field ที่​ต้อง​ใช้​จริง ใช้ query แบบ​ยืดหยุ่น (เช่น //order แทน path ตายตัวอย่าง /order-history/order-list/order) และ​เพิกเฉย​ต่อ​โครงสร้าง​ส่วน​ที่​ไม่รู้จัก วิธี​นี้​ทำให้​ผู้​ให้​บริการ (provider) ปรับ​โครงสร้าง​ข้อมูล​ได้​โดย​ไม่​ทำให้​ผู้บริโภค (consumer) พัง​ไป​ด้วย

อย่างไร​ก็ตาม หลักการ​นี้​ไม่​ได้​ถูก​ยอมรับ​แบบ​ไร้​ข้อ​กังขา​ตลอด​มา:

  • RFC 1122 (1989) โดย Bob Braden ขยาย​ความคิด​ของ Postel โดย​แนะนำ​ให้​นัก​พัฒนา “สมมติ​ว่า​เครือข่าย​เต็ม​ไป​ด้วย​ฝ่าย​ร้าย​ที่​จะ​ส่ง packet ที่​ออกแบบ​มา​ให้​สร้าง​ผล​เสีย​ที่สุด​เท่า​ที่​จะ​ทำได้” — ระบบ​ไม่​ควร​แค่ “รอด” จาก host ที่​พฤติกรรม​ผิด​ปกติ แต่​ต้อง​จำกัด​ความ​เสียหาย​ที่​จะ​ลาม​ไป​ยัง​ส่วน​อื่น​ของ​เครือข่าย​ที่​ใช้​ร่วม​กัน​ด้วย
  • Eric Allman, ผู้​เขียน sendmail, เขียน​บทความ “The Robustness Principle Reconsidered” ใน ACM Queue (2011) ชี้​ว่าการ “ใจ​กว้าง​เกิน​ไป” กลาย​เป็น​ช่อง​โหว่​ด้าน​ความ​ปลอดภัย​ใน​โลก​ที่​อินเทอร์เน็ต​เต็ม​ไป​ด้วย​ผู้​ไม่​หวังดี — การ​ยอมรับ input ที่​ไม่​ตรง spec อย่าง​ไม่​ระวัง​เปิด​ช่อง​ให้​เกิด​ปัญหา​อย่าง DNS poisoning, connection hijacking หรือ input ที่​ถูก​ออกแบบ​มา​โจมตี​โดย​เฉพาะ เขา​ยก​ตัวอย่าง​เบราว์เซอร์​ที่​ยอมรับ HTML ผิด spec จน​กลาย​เป็น “มาตรฐาน​โดย​พฤตินัย” ที่​ทุก​เบราว์เซอร์​ต้อง​เลียน​แบบ bug ตาม​กัน​ไป​เรื่อยๆ ข้อ​เสนอ​ของ​เขา​คือ​ใช้​หลักการ​นี้​อย่าง​มี​บริบท ไม่ใช่​ยึด​เป็น​ความ​จริง​ตายตัว
  • ในปี 2023 เอกสาร RFC 9413 (Thomson และ Schinazi) ถก​ว่า​หลักการ​นี้​ใน​ทาง​ปฏิบัติ​กลับ​บั่นทอน​ความ​ทนทาน​ของ​ระบบ เพราะ​ข้อ​บกพร่อง​ของ implementation หนึ่ง​ที่​ถูก​ฝั่ง​รับ “ใจ​กว้าง” ยอมรับ​ไป​เรื่อยๆ จะ​กลาย​เป็น​พฤติกรรม​ที่ implementation อื่น​ทุก​ตัว​ต้อง​เลียน​แบบ​ตาม​เพื่อ interoperability (bug-for-bug compatibility) ทำให้​แก้ไข​ปัญหา​ที่​ต้นตอ​ได้​ยาก​ขึ้น​เรื่อยๆ ตาม​เวลา

สรุป​คือ Postel’s Law ยัง​คง​เป็น​แนวทาง​ที่​มี​ประโยชน์​มาก​ใน​การ​ออกแบบ integration point แต่​ต้อง​ใช้​คู่​กับ​สามัญสำนึก​ด้าน​ความ​ปลอดภัย: “ใจ​กว้าง” ไม่​ได้​แปล​ว่ายอมรับ​ทุก​อย่าง​โดย​ไม่​ตรวจสอบ

เว็บ​เบราว์เซอร์​กับ HTML: เบราว์เซอร์​แทบ​ทุก​ตัว​ยอม​พาร์ส HTML ที่ tag ไม่​ปิด, attribute ไม่มี quote หรือ​โครงสร้าง​ผิด​เพี้ยน แล้ว​พยายาม​เดา​เจตนา​ของ​ผู้​เขียน​หน้า​เว็บ นี่​คือ​เหตุผล​ที่​เว็บ​ยุค​แรกๆ ที่​เขียนสไตล์หลวมๆ ยัง​เปิด​ดู​ได้​ทุก​วัน​นี้ แต่​ผล​ข้าง​เคียง​คือ​พฤติกรรม “เดา​ใจ” ของ​แต่ละ​เบราว์เซอร์​กลาย​เป็น​มาตรฐาน​ที่ implicit ต้อง​ตาม​กัน​ไป

Sendmail และ​อีเมล: Eric Allman เล่า​ประสบการณ์​ตรง​ว่า code “แก้ไข address” ของ sendmail ที่​ตั้งใจ​ใจ​กว้าง​กับ input ทำงาน​ได้​ดี​ใน​สภาพ​แวดล้อม​หนึ่ง แต่​กลับ​สร้าง​ปัญหา​เมื่อ​ย้าย​ไป​รัน​บน hosting ที่​ต่าง​บริบท เพราะ​มัน​เดา​ผิด​ใน​สถานการณ์​ที่​ไม่​เคย​เจอ

HTTP API ที่​รับ​ได้​หลาย format: endpoint ที่​รับ​ได้​ทั้ง Content-Type: application/json และ application/xml, หรือ deserializer ที่​ไม่​ล้ม​เมื่อ​เจอ field แปลกปลอม​ใน​การ​ตอบ​กลับ​ของ 3rd-party API เป็นการ​นำ Postel’s Law มา​ปรับ​ใช้​ใน​ระดับ​สถาปัตยกรรม​สมัย​ใหม่ ตัวอย่าง Tolerant Reader ใน code:

// ฝั่งรับ (deserialize): ใจกว้าง — เพิกเฉย field ที่ไม่รู้จัก, ใช้ default เมื่อขาดหาย
public class OrderReader
{
public OrderSummary Parse(JsonElement payload)
{
// ดึงเฉพาะ field ที่ต้องใช้จริง ไม่ผูกกับ schema ทั้งหมดของผู้ให้บริการ
var id = payload.GetProperty("orderId").GetString() ?? string.Empty;
var total = payload.TryGetProperty("total", out var t)
? t.GetDecimal()
: 0m; // default ที่สมเหตุสมผลเมื่อ field ขาดหาย
// field อื่น ๆ ที่ผู้ให้บริการอาจเพิ่มเข้ามาในอนาคตจะถูกละเว้นโดยอัตโนมัติ
return new OrderSummary(id, total);
}
}
// ฝั่งส่ง (serialize): เข้มงวด — ส่งออกตาม schema ที่ตกลงกันเท่านั้น พร้อม validate ก่อนส่งเสมอ
public class OrderWriter
{
public string Serialize(OrderSummary order)
{
if (string.IsNullOrEmpty(order.Id))
throw new InvalidOperationException("OrderId ต้องไม่ว่างก่อนส่งออก");
return JsonSerializer.Serialize(new
{
orderId = order.Id,
total = order.Total
});
}
}

โครงสร้าง​นี้​แสดง asymmetry ที่​เป็น​หัวใจ​ของ​กฎ: ฝั่ง​อ่าน (OrderReader) ยืดหยุ่น​และ​ทนทาน​ต่อ​การ​เปลี่ยนแปลง​ของ​ผู้​ให้​บริการ ส่วน​ฝั่ง​เขียน (OrderWriter) เข้มงวด​และ validate ก่อน​ส่ง​ออก​เสมอ เพื่อ​ไม่​ให้​ผู้บริโภค​ของ​เรา​ต้อง​มา​ปวด​หัว​กับ output ที่​ไม่​แน่นอน

ภาพ​รวม​ของ flow นี้​สรุป​ได้​ดังนี้:

flowchart LR
    A[Input หลายรูปแบบจากภายนอก] --> B[Parse อย่างยืดหยุ่น]
    B --> C[Validate และ Normalize]
    C --> D[Domain Model ภายใน]
    D --> E[Serialize อย่างเข้มงวด]
    E --> F[Output ตรง spec เดียวเสมอ]
  • แยก​จุด​ที่​รับ​ข้อมูล​กับ​จุด​ที่​ส่ง​ข้อมูล​ให้​ชัด แล้ว​ใช้​กติกา​ต่าง​กัน: ตัว deserializer/parser ควร​ทนทาน​และ​ให้ default ที่​ปลอดภัย ส่วน serializer/validator ก่อน​ส่ง​ออก​ควร​เข้มงวด​และ​ปฏิเสธ​ข้อมูล​ที่​ไม่​สมบูรณ์
  • อย่า​ปล่อย​ให้ “ใจ​กว้าง” กลาย​เป็น “ไม่​ตรวจสอบ​เลย” โดย​เฉพาะ​กับ​ข้อมูล​ที่​กระทบ​ความ​ปลอดภัย (เช่น ค่าที่​จะ​ถูก​นำ​ไป query, exec, หรือ deserialize เป็น object) ตาม​คำ​เตือน​ของ Eric Allman ควร​ตรวจสอบ input ที่มา​จาก​ภายนอก​อย่าง​เข้มงวด​ใน​จุด​ที่​มี​ความ​เสี่ยง แม้​จะ​ยอมรับ​รูปแบบ​หลากหลาย​ใน​จุด​ที่​ไม่​เสี่ยง
  • ใช้ Tolerant Reader ร่วม​กับ Anti-Corruption Layer เมื่อ integrate กับ​ระบบ​ภายนอก​หรือ legacy system ให้​มี​จุด​แปล​ข้อมูล​จุด​เดียว​ที่​รับผิดชอบ​ความ​ยืดหยุ่น​ทั้งหมด แทนที่​จะ​กระจาย​ตรรกะ “เผื่อ​กรณี​แปลกๆ” ไป​ทั่ว​ทั้ง codebase
  • share contract test กับ​ผู้​ให้​บริการ แนวทาง​ที่ Fowler แนะนำ​คือ​ให้​ผู้บริโภค​เขียน test ที่​จำลอง response ที่​คาด​หวัง แล้ว share กับ​ทีม​ผู้​ให้​บริการ เพื่อ​ให้​รู้ทันที​เมื่อ​การ​เปลี่ยนแปลง​ฝั่ง​ใด​ฝั่ง​หนึ่ง​จะ​ทำให้​อีก​ฝั่ง​พัง — เป็นต้น​กำเนิด​ของ​แนวคิด Consumer-Driven Contracts
  • อย่า​ปล่อย​ให้​หนี้​ความ​เข้า​กัน​ได้​สะสม​นาน​เกิน​ไป implementation ที่​ผิด spec แต่​ถูก​ฝั่ง​รับ​ใจ​กว้าง​ยอมรับ​ไป​เรื่อยๆ จะ​กลาย​เป็น​พฤติกรรม​ที่​ต้อง​รองรับ​ตลอด​ไป ยิ่ง​ปล่อย​นาน​ยิ่ง​แก้​ยาก ดังนั้น​ควร log/แจ้ง​เตือน​เมื่อ​รับ input ที่​ผิด spec แม้​จะ​ยอม​ให้​ผ่าน​ไป​ก็ตาม เพื่อ​ให้​เห็น​ภาพ​หนี้​เทคนิค​ที่​สะสม​อยู่