Intermission: The Anti-Hamlet
คำว่า "Anti-Hamlet": มาจากการเปรียบเปรยกับคำว่า Anti-matter (ปฏิสสาร) ในทางฟิสิกส์ หากหนังสือ Hamlet เล่มปกติ 1 เล่มมีราคา +$39 (มีมวลและมูลค่าเป็นบวก) หนังสือ Anti-Hamlet (-1 เล่ม) ก็คือ "ปฏิสสารทางธุรกิจ" ที่มีมูลค่าติดลบ -$39!
คำตอบและหมุดเชื่อมโยง: เมื่อผู้ใช้สั่งซื้อ Anti-Hamlet เข้ามา มูลค่าติดลบของมันจะเข้าไป "หักลบและกลืนกิน" ยอดราคารวมในตะกร้าให้ลดลง หรือแม้กระทั่งบังคับให้ระบบบัญชี "สั่งจ่ายเงินคืนให้แก่ลูกค้า" และทำให้ระบบคลังสินค้า "เสกหนังสือเล่มใหม่ขึ้นมาเองจากความว่างเปล่า" (เพราะ \(15 - (-1) = 16\)) ผู้เขียนตั้งชื่อบทนี้เพื่อใช้เป็นอุทาหรณ์เตือนสติว่า การออกแบบโมเดลแบบตื้นเขิน (Shallow Modeling) ที่ใช้ Primitive Type (int) โดยไม่มี Invariants จะสร้างช่องโหว่ความปลอดภัยที่ดูดเงินบริษัทไหลออกตลอดหลายเดือนโดยที่ ไม่มี Alert ใดๆ แจ้งเตือนเลยแม้แต่ตัวเดียว!
Security as a Concern (คดีตู้นิรภัย Öst-Götha Bank), Username Domain Invariants และ Billion Laughs XML Defense-in-depth
Day 2 จาก 14 วัน — ยังอยู่ใน Part 1: บทแทรกละครชีวิตจริงกรณีศึกษา Anti-Hamlet
เข้าใจรากของปัญหา Shallow Modeling vs. Deep Modeling และเห็นปฏิกิริยาโดมิโนข้ามระบบเมื่อกฎธุรกิจถูกละเมิด
เรื่องราวและมโนทัศน์เชิงลึก (The 3 Acts & System Chain Reaction)
บริษัทขายหนังสือออนไลน์ข้ามชาติว่าจ้างทีม Security Audit เข้ามาเจาะระบบ Production จริง ทีมงานยิงแพ็กเกจขยะใส่ Firewall, สแกน OS Port, เจาะ Session/Cookie, ยิง SQLi/XSS... ผลคือ "โครงสร้างพื้นฐานแน่นหนามาก" ไม่พบช่องโหว่ดั้งเดิมเลยแม้แต่น้อย!
Joe Tester ขยับมาทดสอบฟอร์มสั่งซื้อตรงช่อง Quantity หนังสือ Hamlet เล่มละ $39 Joe ยิง JS ➔ ไม่รัน, ยิง SQLi ➔ ปฏิเสธ... สุดท้าย Joe เกิดความสงสัย จึงลองกรอก -1 ผลคือ: ระบบสั่งซื้อผ่าน, ตัดบัตรเครดิตผ่าน, และส่งอีเมลยืนยันทันที!
บ่ายวันถัดมา พนักงานบัญชีเดินเข้ามาถามทีมว่า "มีใครรู้จักคนชื่อ Joe Tester ไหมคะ? ระบบบัญชีลูกหนี้พิมพ์เช็คสั่งจ่ายเงิน $39 ส่งไปให้เขา แต่พอจะใส่ซอง พบว่าที่อยู่ของ Joe คือสำนักงานใหญ่ของบริษัทเราเองค่ะ!"
รับยอด -$39 เข้ามา โมดูลตัดบัตรเครดิตรองรับเฉพาะยอดบวก เมื่อเจอยอดติดลบ มันคิดว่า "ไม่มีเงินต้องเรียกเก็บ" จึงปล่อยผ่านรายการไปเงียบๆ โดยไม่ตัดเงินบัตร Joe
ระบบบัญชีมีหลักการว่า Balance ติดลบหมายถึง "ร้านค้าเป็นหนี้ลูกค้า $39" เมื่อรัน Batch Job ตอนดึก ระบบจึง พิมพ์เช็คสั่งจ่ายเงิน $39 ส่งไปให้ Joe ฟรีๆ!
ขายปกติสต็อกลด \(15 - 1 = 14\) แต่พอสั่ง -1 เล่ม ระบบคำนวณ \(15 - (-1) = 16\)! การขาย Anti-Hamlet ส่งผลให้ สต็อกในระบบเพิ่มขึ้นเอง 1 เล่มจากความว่างเปล่า!
เจอรายการสั่งซื้อจำนวน -1 ชิ้น จัดส่งไม่ได้ จึงโยน RuntimeException ทิ้งลงใน System Log (ซึ่งไม่มีใครเปิดอ่าน) แล้วปล่อยคำสั่งซื้อผ่านไปเงียบๆ
👻 Systems Living the Same Lie
ปรากฏการณ์ที่ระบบ IT หลายระบบบันทึกข้อมูลเท็จ แต่ตัวเลขทางคณิตศาสตร์ข้ามระบบดัน "หักลบดุลกันเป๊ะพอดี":
ความจริงเปิดเผยตอนนับสต็อกสิ้นปี (End-of-year inventory) เมื่อของหาย ผู้บริหารคิดว่าพนักงานขโมยของ จนถึงขั้นส่งพนักงานไปอบรมจริยธรรม!
🎟️ Do-It-Yourself Discount Vouchers
การนำช่องโหว่ Anti-book มาแอบใช้เป็น "คูปองส่วนลดที่สร้างเองได้ตามใจชอบ" โดยใส่สินค้าติดลบเข้าไปในตะกร้าเพื่อลดราคารวม:
ข่าวลือการลดราคานี้แพร่สะพัดเงียบๆ ทำให้บริษัทสูญเสียเงินมหาศาลตลอดหลายเดือนโดยระบบจับไม่ได้
⛏️ Shallow vs. Deep Modeling
int ซึ่งรับค่าได้ตั้งแต่ -2.1 พันล้าน ถึง +2.1 พันล้าน เปิดช่องให้ค่าติดลบทะลุผ่าน
Quantity ที่ล็อก Invariants ว่าต้องเป็น 1 ถึง 240 เท่านั้น
แบบฝึกหัดปฏิบัติตามสถานการณ์จริง (Hands-on Refactoring)
// โค้ดเดิม: มองทุกอย่างเป็น Primitive Types class Book { String title; String isbn; double price; // WARNING: ห้ามใช้ float/double เก็บเงิน! } class Order { // ใช้ int รับ quantity เปิดโอกาสให้ส่งค่าติดลบ (-1) เข้ามาได้ void addOrderLine(Book book, int quantity) { // ไม่มี Validation เช็กค่าติดลบ! OrderLine line = new OrderLine(book, quantity); this.orderLineList.add(line); } }
int quantity = -1 เป็นตัวเลขที่ถูกต้องตามไวยากรณ์ภาษา โค้ดจึงทำงานผ่านฉลุยและสร้างตัวเลข -$39 ส่งต่อไปทำลายระบบบัญชีและคลังสินค้า
// 1. สร้าง Domain Primitive สำหรับ Quantity ลบล้างค่าติดลบตั้งแต่แรก public final class Quantity { private final int value; public Quantity(int value) { // บังคับกฎ Invariants ทันทีใน Constructor (Fail-Fast) isTrue(value > 0, "Quantity must be positive"); isTrue(value <= 240, "Cannot exceed box capacity of 240"); this.value = value; } public int value() { return value; } public Quantity add(Quantity addend) { notNull(addend, "Addend cannot be null"); return new Quantity(this.value + addend.value); } } // 2. ปรับปรุง Order Class ให้รับเฉพาะ Quantity Type เท่านั้น public class Order { public void addOrderLine(Book book, Quantity quantity) { this.orderLineList.add(new OrderLine(notNull(book), notNull(quantity))); } }
new Quantity(-1) คอมไพเลอร์และ Runtime จะไม่อนุญาตให้วัตถุ Anti-Hamlet มีตัวตนอยู่ในระบบได้เลยแม้แต่วินาทีเดียว!
ชุดควิซประเมินความรู้ประจำวัน
ตอบปุ๊บ รู้ผลเฉลยทันที พร้อมคำอธิบายและหลอดสะสมพลังงาน Energy Charge
ทดสอบความรู้ Day 2 สำเร็จแล้ว!
สรุปการเชื่อมโยงกลับสู่ Chapter 2 — Intermission: The anti-Hamlet
เรื่องราวของ Joe Tester, เช็คสั่งจ่ายเงินฝั่งบัญชี, สต็อกที่เพิ่มขึ้นเองในคลัง, และพฤติกรรม DIY Discount Vouchers ทั้งหมดนี้ ล้วนเป็นผลพวงมาจากวัตถุติดลบที่เรียกว่า "Anti-Hamlet" ซึ่งสะท้อนให้เห็นอย่างชัดเจนว่า หากเราออกแบบโมเดลแบบตื้นเขิน (Shallow Modeling) ปล่อยให้ Primitive Types อย่าง int เพ่นพ่านในระบบโดยไม่มีการทำ Deep Model ผลลัพธ์ของ Anti-Hamlet จะสร้างความสูญเสียทางธุรกิจมหาศาลโดยไม่มีเตือนภัยใดๆ เด้งขึ้นมาเลยครับ!