Kata 1: Primitive Obsession Breakdown
ภารกิจประจำวัน: ยินดีต้อนรับสู่ Phase 2: Deliberate Refactoring Katas! วันนี้เราจะสวมบทบาทเป็น Security Auditor & Software Architect เพื่อสแกนค้นหารอยแตกร้าวใน Legacy Codebase ของระบบสั่งซื้อสินค้า (OrderProcessingService) วิเคราะห์รายการภัยคุกคามที่เกิดจากโรคเสพติดพริมิทิฟ (Primitive Obsession) และตีกรอบ Bounded Context เพื่อสกัด 2 โดเมนพริมิทิฟแรก: EmailAddress และ Quantity พร้อมติดตั้ง Validation Pipeline 5 ขั้น ให้ทำงานตั้งแต่หน้าประตู!
ปูพื้นฐานทฤษฎีครบ 100%: DDD Building Blocks, Immutability, Failure Handling, Cloud 3R's, และ Antifragile Culture
Day 15 จาก 35 วัน — วันแรกของ Phase 2 เริ่มต้น Kata 1 ตรวจสอบและระบุรอยแตกร้าวในโค้ดเดิม
ชี้ชัด 4 ช่องโหว่จาก Primitive Obsession และสร้าง EmailAddress และ Quantity ให้ปลอดภัย 100%
การวิเคราะห์รอยแตกร้าว (Code Audit & Threat Analysis)
// Legacy Codebase: พึ่งพา Primitive Types กว้างๆ และเขียน Defensive Code แบบพลาสเตอร์ยา public class OrderProcessingService { public void processOrder(String customerEmail, String shippingAddress, String itemCode, int quantity, double unitPrice, String discountCode) { // 💥 Defensive Code แบบพลาสเตอร์ยา (Band-Aid): เมินเฉยข้อมูลขยะและปัดตกเงียบๆ! if (customerEmail == null || shippingAddress == null) { return; // Ignore ไม่แจ้งเตือน ไม่ Throw Exception ผู้เรียกคิดว่าสั่งซื้อสำเร็จ! } // 💥 เช็กไม่เด็ดขาด แค่พิมพ์บอก แต่ปล่อยให้คำนวณราคาติดลบต่อ! if (quantity <= 0) { System.out.println("Invalid quantity"); } double totalAmount = quantity * unitPrice; System.out.println("Processing order for: " + customerEmail + " Total: " + totalAmount); } }
🔄 Parameter Swapping Attack / Error
customerEmail, shippingAddress, itemCode, และ discountCode ล้วนใช้ประเภทข้อมูล String เหมือนกันทั้งหมด!
processOrder(shippingAddress, customerEmail, ...) คอมไพเลอร์จะไม่เตือนแม้แต่คำเดียว แต่ระบบจะนำที่อยู่จัดส่งไปใช้เป็นอีเมล ส่งผลให้เกิด Sensitive Data Leakage ส่งใบเสร็จหรือลิงก์สำคัญไปผิดคนทันที
📉 Anti-Hamlet Effect (Negative Value Injection)
quantity ใช้ int และ unitPrice ใช้ double ซึ่งเปิดกว้างรับค่าติดลบได้อย่างอิสระ
quantity = -5 เข้ามา ราคารวมจะกลายเป็นติดลบ (-5 * 100 = -500) ระบบการเงินอาจทำรายการคืนเงิน (Credit Invoice) ให้แฮกเกอร์ฟรีๆ ซ้ำรอยโศกนาฏกรรม Anti-Hamlet ใน Day 2!
💣 Unchecked Injection Payloads & ReDoS
customerEmail ไม่มีการตรวจสอบความยาว (Size Check) และไม่ตรวจไวยากรณ์ (Syntax Check)
🩹 Defensive Band-Aid Code (Silent Failures)
การใช้ if (customerEmail == null) return; แบบไร้คำเตือนและไม่บันทึก Audit Log
การสกัด Domain Primitives สองตัวแรก (EmailAddress & Quantity)
import java.util.Objects; import java.util.regex.Pattern; public final class EmailAddress { // Regex ตรวจสอบไวยากรณ์อีเมลมาตรฐาน private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[a-zA-Z]{2,}$"); private final String value; public EmailAddress(String rawEmail) { // ขั้นที่ 1: Origin Validation (ตรวจ null ทันทีที่ประตูหน้า) if (rawEmail == null) { throw new IllegalArgumentException("Email address cannot be null"); } String trimmed = rawEmail.trim(); // ขั้นที่ 2: Size Validation (ขอบเขตความยาว 1-254 ตามมาตรฐาน RFC ป้องกัน ReDoS) if (trimmed.isEmpty() || trimmed.length() > 254) { throw new IllegalArgumentException("Email length must be between 1 and 254 characters"); } // ขั้นที่ 3 & 4: Lexical & Syntax Validation (ตรวจรูปแบบและโครงสร้างด้วย Regex) if (!EMAIL_PATTERN.matcher(trimmed).matches()) { throw new IllegalArgumentException("Invalid email format: " + rawEmail); } // ขั้นที่ 5: Semantic Validation (กฎธุรกิจเฉพาะ เช่น บล็อกโดเมนอันตราย) if (trimmed.endsWith("@malicious-domain.com")) { throw new IllegalArgumentException("Domain is restricted"); } this.value = trimmed.toLowerCase(); // Normalization ป้องกันความคลาดเคลื่อน } public String value() { return value; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EmailAddress that = (EmailAddress) o; return Objects.equals(value, that.value); } @Override public int hashCode() { return Objects.hash(value); } @Override public String toString() { return value; } }
public final class Quantity { private static final int MAX_ORDER_QUANTITY = 10000; // Invariant ขีดจำกัดทางธุรกิจ private final int value; public Quantity(int rawQuantity) { // ขั้นที่ 1 & 5: Origin & Semantic Fail-Fast Validation (ตัดค่าศูนย์และติดลบทันที!) if (rawQuantity <= 0) { throw new IllegalArgumentException("Quantity must be greater than zero. Provided: " + rawQuantity); } // ขั้นที่ 2: Size / Bound Validation if (rawQuantity > MAX_ORDER_QUANTITY) { throw new IllegalArgumentException("Quantity exceeds maximum limit of " + MAX_ORDER_QUANTITY); } this.value = rawQuantity; } public int value() { return value; } // Domain Operation: รวมศูนย์การคำนวณไว้ในตัวเอง คืนค่าเป็น Quantity ตัวใหม่ที่เป็น Valid เสมอ public Quantity add(Quantity other) { return new Quantity(this.value + other.value); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Quantity quantity = (Quantity) o; return value == quantity.value; } @Override public int hashCode() { return Integer.hashCode(value); } @Override public String toString() { return String.valueOf(value); } }
public class OrderProcessingService { // ✅ ปลอดภัยในระดับ Compile-Time 100%! // ไม่สามารถส่ง String สลับตำแหน่ง หรือส่ง Quantity ติดลบเข้ามาได้อีกต่อไป public void processOrder(EmailAddress customerEmail, String shippingAddress, String itemCode, Quantity quantity, double unitPrice, String discountCode) { // โค้ดภายในเชื่อใจ EmailAddress และ Quantity ได้ทันที 100% ไม่ต้องเขียน Defensive Checks ซ้ำซ้อน! double totalAmount = quantity.value() * unitPrice; System.out.println("Processing order for: " + customerEmail.value() + " Quantity: " + quantity.value() + " Total: " + totalAmount); } }
ชุดควิซประเมินความรู้สะสมประจำวัน (Day 15 Cumulative Quiz - 10 ข้อ)
ทบทวน Spaced Repetition (Days 1–14) และเจาะลึก Invariants Day 15 ตอบปุ๊บ รู้ผลเฉลยทันทีพร้อมคำอธิบาย
ทดสอบความรู้ Day 15 สะสมครบ 10 ข้อสำเร็จแล้ว!
สรุปปิดท้าย Day 15 — Kata 1: Primitive Obsession Breakdown (Part 1)
ในวันนี้เราได้ทำ Code Audit ผ่าวิเคราะห์รอยแตกร้าวของ Primitive Obsession ใน Legacy Codebase ชี้ชัด 4 มิติภัยคุกคาม และลงมือสกัด Domain Primitives 2 ตัวแรก (EmailAddress และ Quantity) พร้อมฝัง Validation Pipeline 5 ขั้น สำเร็จเรียบร้อย! ควิซทั้ง 10 ข้อได้ดึงทฤษฎีสำคัญจาก Phase 1 (Hinge vs Lock, Anti-Hamlet, และ Fail-Fast) กลับมาเชื่อมต่อได้อย่างเหนียวแน่น