🏗️
← Secure by Design

SECURE BY DESIGN DAY 12

เริ่มทำควิซ ⚡
Part 3: Applying the Fundamentals | Chapter 12

Guidance in Legacy Code

Safe Refactoring & Strangler Fig 🛠️
📌 TITLE ANCHOR QUESTION ทำไมบทนี้ถึงชื่อนี้? และ Legacy Code เกี่ยวข้องกับ Security อย่างไร?

คำตอบและหมุดเชื่อมโยง: คำว่า "Guidance in legacy code" (แนวทางปฏิบัติในระบบเดิม/โค้ดเก่า) ตั้งขึ้นเพื่อตอบคำถามที่นักพัฒนาทุกคนต้องเจอในชีวิตจริง เพราะในทางปฏิบัติ เราแทบไม่เคยได้สร้างระบบใหม่จากศูนย์ (Greenfield) แต่ต้องทำงานกับ Legacy Code ที่ถูกนิยามว่าเป็น "โค้ดที่ขาด Automated Tests, เต็มไปด้วย Primitive Obsession, พารามิเตอร์คลุมเครือ และผ่านการแก้แบบปะสอยชุนมาหลายปี"

ผู้เขียนตั้งชื่อบทนี้เพื่อมอบ "เข็มทิศและยุทธศาสตร์การปรับปรุง (Refactoring Strategies)" เพราะ Legacy Code คือรังซ่อนตัวของช่องโหว่ความปลอดภัย การสกัด Domain Primitives เข้าไปตรงขอบเขตความหมาย (Semantic Boundaries), การปลดล็อกพารามิเตอร์ด้วย 3 ยุทธศาสตร์ (Direct, Discovery, New API), และการกำจัดโค้ดระแวงตัวเอง (Defensive Code) จะช่วยเปลี่ยนระบบเก่าแสนบรรทัดให้กลายเป็นป้อมปราการที่แข็งแกร่งอย่างมั่นคงโดยไม่ทำให้ระบบเดิมพังครับ!

Part 1 & 2 ที่ผ่านมา (Recap)

Days 1-7: รากฐาน DDD, Immutability, Domain Primitives, State Integrity และ Entity Relay

ตำแหน่งปัจจุบัน (Where We Are)

Day 12 จาก 14 วัน — เข้าสู่การประยุกต์ใช้ในโค้ดเก่าแสนบรรทัด (Legacy Monoliths) ก่อนก้าวสู่ Microservices

เป้าหมายประจำวัน (Core Mission)

ชี้จุดรอยแตกร้าวในโค้ดเก่า และสยบปัญหาด้วย 3 ยุทธศาสตร์ Refactoring, กำจัด Defensive Code, และเลี่ยง Partial Primitives

02

เรื่องราวและมโนทัศน์เชิงลึก (Real Scenarios & Refactoring Strategies)

📦 SCENARIO 2.1 • พารามิเตอร์ปริศนา และ 3 ยุทธศาสตร์การสกัด DOMAIN PRIMITIVES Parameter Ambiguity

📖 เรื่องราวจริง: เมธอดจัดส่งพัสดุสุดเปราะบาง

พิจารณาเมธอดในระบบ e-Commerce เก่าแก่ที่ผ่านการแก้ไขมานับสิบปี:

public void shipItems(int itemId, int quantity, Address billing, Address to)
💥 จุดเปราะบางทาง Security และ Logic:
• สลับตำแหน่งตัวเลข (Parameter Swapping): itemId และ quantity ต่างก็เป็น int หากสลับตำแหน่ง คอมไพเลอร์จะไม่เตือน แต่ระบบจะส่งของผิดชนิดและผิดจำนวนทันที!
• สลับที่อยู่ (Address Swapping): billing และ to เป็นคลาส Address เหมือนกัน หากสลับตำแหน่ง พัสดุจะถูกส่งไปบ้านคนจ่ายเงินแทนที่จะเป็นบ้านผู้รับ!

🛡️ 3 ยุทธศาสตร์การ Refactoring พารามิเตอร์

1. Direct Approach (หักดิบเปลี่ยนทันที): เปลี่ยน Signature ให้รับ ItemId, Quantity, BillingAddress, ShippingAddress ทันที แล้วตามแก้ Compile Errors ทั้งหมด เหมาะกับ Codebase ขนาดเล็กที่ดูแลโดยทีมเดียว
2. Discovery Approach (ซุ่มตรวจจับขยะเบื้องหลัง): คง Signature เดิมไว้ แต่นำโค้ดภายในไปลองสร้าง Domain Primitive ใน try-catch แล้ว Log ข้อผิดพลาดทิ้งไว้ใน Production เพื่อส่องดูว่ามี "ข้อมูลขยะ (Bad Data)" ไหลเข้ามามากน้อยเพียงใดก่อนเปลี่ยน API จริง
3. New API Approach (สร้าง API คู่ขนาน / Strangler Fig): สร้างเมธอดใหม่ที่รับ Domain Primitives ใส่ @Deprecated ที่เมธอดเก่า แล้วทยอยให้ทีมอื่นขยับมาใช้เมธอดใหม่ทีละโมดูลอย่างปลอดภัย
🛡️ SCENARIO 2.2 • โค้ดที่ไม่เคยเชื่อใจตัวเอง (DEFENSIVE CODE CONSTRUCTS & NULL CHECKS) Band-aid Coding

🪖 อุปมาอุปไมย: ทหารตรวจบัตรกันเองในค่าย

ใน Legacy Code มักพบโค้ดที่เต็มไปด้วย if (x != null) ซ้อนกัน 5-10 ชั้น ลึกลงไปจนถึง Private Helper Methods

🎖️ Analogy: เปรียบเหมือน "การที่ทหารในกองทัพต้องหยุดตรวจบัตรประชาชนเพื่อนทหารด้วยกันเองทุกๆ 5 ก้าวที่เดินในค่ายทหาร" เพราะไม่เคยเชื่อใจด่านตรวจหน้าประตูค่าย!
💥 ผลเสียทาง Security: เกิดจากการที่ระบบเคยพังด้วย NullPointerException โปรแกรมเมอร์จึงเอา if (x != null) ไปแปะเป็นพลาสเตอร์ยา (Band-aid) เมื่อเจอข้อมูลขยะ โค้ดมักจะ "เงียบเฉย (Ignore) แล้วทำงานต่อ" ทำให้ข้อมูลขยะค้างในระบบ และเปิดช่องโหว่ให้แฮกเกอร์ส่ง Payload แปลกๆ เข้ามาข้ามขั้นตอนตรวจสอบ!

⚔️ การสลาย Defensive Code ด้วย Boundary Contracts

วิธีแก้ปัญหาไม่ใช่การตามใส่ null check เพิ่ม แต่เป็นการสร้าง สัญญาเด็ดขาดที่ด่านหน้า (Boundary Contracts):

1. ตรวจสอบทันทีที่ประตูเข้า (Boundary Fail-Fast): บังคับใช้ Domain Primitives แปลงข้อมูลดิบทันทีที่เข้าสู่ระบบ
2. โค้ดชั้นในเชื่อใจได้ 100%: เมื่อพารามิเตอร์เป็น Domain Primitive แล้ว เมธอดชั้นในและ Private Methods ไม่จำเป็นต้องเขียน null check ซ้ำซ้อนอีกเลย โค้ดจะกระชับ สะอาด และตรวจสอบได้ง่าย
🧶 SCENARIO 2.3 • กับดัก DRY ผิดวิธี (MISAPPLIED DRY) และ PARTIAL DOMAIN PRIMITIVES Conceptual Whole

✂️ กับดัก DRY ผิดวิธี: โฟกัสที่ตัวอักษร ไม่ใช่ไอเดีย

หลักการ Don't Repeat Yourself (DRY) มุ่งเน้นการไม่ฉายซ้ำ "ความรู้ทางธุรกิจ (Knowledge)" ไม่ใช่การลบข้อความหรือ Regex ที่บังเอิญเหมือนกัน!

• ZipCode ใช้ Regex \d{5}
• OrderNumber บังเอิญใช้ Regex \d{5} เหมือนกัน
💥 ความผิดพลาด: โปรแกรมเมอร์รวม Regex นี้ไปไว้ใน Util.MATCH_5_DIGITS ส่งผลให้โมดูลขนส่งและสั่งซื้อผูกติดกันผ่าน Util ขยะ! หากวันหนึ่ง ZipCode เปลี่ยนเป็น 6 หลัก OrderNumber จะพังไปด้วยทันที!

💰 กับดัก Partial Domain Primitives (บทเรียนเรื่องเงิน)

Domain Primitive ต้องเป็น Conceptual Whole (เอกภาพเชิงมโนทัศน์) ห้ามหั่นขาดความหมาย:

คดีคลาส Amount ที่ไม่มี Currency:

สร้างคลาส Amount เก็บเฉพาะตัวเลขทศนิยม 100.00 เมื่อบริษัทขยายไปต่างประเทศ มีการเอา $100 ดอลลาร์ ไปบวกกับ €100 ยูโรตรงๆ ผ่าน amount1.add(amount2) ส่งผลให้ยอดการเงินผิดพลาดมหาศาล!

🛡️ ต้องสร้างเป็นคลาส Money ที่มีทั้ง Amount + Currency เพื่อให้เมธอด add() บล็อกการบวกข้ามสกุลเงินได้ตั้งแต่ในคลาส!
⚠️ DANGER OF LOGGING UNCHECKED STRINGS (LOG INJECTION)

การสั่ง logger.info("Order from: " + rawInput) โดยไม่ผ่านการตรวจสอบ เป็นภัยคุกคามร้ายแรง เพราะแฮกเกอร์สามารถส่งอักขระขึ้นบรรทัดใหม่ (\r\n) เพื่อปลอมแปลง Log (Log Forgery) หรือส่งสคริปต์อันตรายเข้าไปทำลายระบบวิเคราะห์ Log ของทีมงาน (Second-Order Injection)!

03

แบบฝึกหัดปฏิบัติตามสถานการณ์จริง (Hands-on Refactoring)

Before: Ambiguous Parameters & Defensive null Checks เสี่ยงสลับพารามิเตอร์ & ข้อมูลขยะค้าง
// โค้ดเดิม: พารามิเตอร์เป็น String/int ทั้งหมด และเช็ก null ซ้ำซ้อน
public class OrderService {

    public void processOrder(String customerId, String isbn, int qty, String zipCode) {
        // 💥 Danger 1: สลับ customerId กับ isbn ได้ง่ายเพราะเป็น String ทั้งคู่
        if (customerId != null && isbn != null && zipCode != null) {
            if (zipCode.matches("^\\d{5}$")) { // Regex ซ้ำซ้อน
                executeShipping(customerId, isbn, qty, zipCode);
            }
        }
    }

    private void executeShipping(String customerId, String isbn, int qty, String zipCode) {
        // 💥 Danger 2: ไม่เชื่อใจด่านหน้า แปะ null check ซ้ำซ้อน และถ้า null จะเงียบเฉย!
        if (customerId == null || isbn == null) {
            return; // Ignore ไม่แจ้งเตือน ทำให้ข้อมูลขยะค้างในระบบ
        }
        // ดำเนินการจัดส่ง...
    }
}
⚠️ จุดบกพร่องร้ายแรง: โปรแกรมเมอร์อาจเผลอสลับ customerId กับ isbn ได้ง่าย, เมธอดชั้นในแอบแปะ null check ซ้ำซ้อนแล้วเงียบเฉยเมื่อพบข้อมูลผิดปกติ ทำให้ขยะสะสมในฐานข้อมูล
After: New API Approach & Domain Primitives Strangler Fig & Compile Check
import static org.apache.commons.lang3.Validate.*;

// โค้ดใหม่: Refactoring ด้วย New API Approach อย่างปลอดภัย
public class OrderService {

    // 1. API ใหม่: ปลอดภัย ไม่ทางสลับพารามิเตอร์ผิด Compile Check 100%
    public void processOrder(CustomerId customerId, ISBN isbn, Quantity qty, ZipCode zipCode) {
        notNull(customerId, "CustomerId cannot be null");
        notNull(isbn, "ISBN cannot be null");
        notNull(qty, "Quantity cannot be null");
        notNull(zipCode, "ZipCode cannot be null");

        // เมธอดชั้นในเชื่อใจ Domain Primitives ได้ทันที ไร้ defensive null check
        executeShippingSecurely(customerId, isbn, qty, zipCode);
    }

    // 2. API เดิม: ใส่ @Deprecated เพื่อให้โค้ดเก่าทยอยย้ายมาใช้ API ใหม่
    @Deprecated
    public void processOrder(String customerId, String isbn, int qty, String zipCode) {
        processOrder(
            new CustomerId(customerId),
            new ISBN(isbn),
            new Quantity(qty),
            new ZipCode(zipCode)
        );
    }

    private void executeShippingSecurely(CustomerId cId, ISBN isbn, Quantity qty, ZipCode zip) {
        // โค้ดสะอาด กระชับ มั่นใจในข้อมูล 100%
    }
}
🛡️ จุดเปลี่ยนสำคัญ: พารามิเตอร์ไม่สามารถสลับตำแหน่งกันได้อีกต่อไป (ตรวจเช็กตั้งแต่ตอนคอมไพล์), เมธอดเก่าไม่พังเพราะมี @Deprecated รองรับ, และกำจัด defensive null checks ในโค้ดชั้นลึกได้อย่างหมดจด!
04

ชุดควิซประเมินความรู้ประจำวัน

ตอบปุ๊บ รู้ผลเฉลยทันที พร้อมคำอธิบายและหลอดพลังงาน Legacy Refactoring Charge

Refactor Energy
0 / 10
📌 CLOSING TITLE CONNECTION WRAP-UP สรุปหมุดเชื่อมโยงกลับหาหัวข้อบท

สรุปการเชื่อมโยงกลับสู่ Chapter 12 — Guidance in legacy code

เรื่องราวการแก้พารามิเตอร์คลุมเครือด้วย 3 ยุทธศาสตร์ (Direct, Discovery, New API), การกำจัด Defensive Code ที่ไม่เคยเชื่อใจตัวเอง, การรู้ทันกับดัก Misapplied DRY, และการระวัง Partial Primitives ในวันนี้ ล้วนตอกย้ำหัวข้อบท "Guidance in legacy code" อย่างสมบูรณ์แบบว่า การยกระดับความปลอดภัยในโค้ดเก่าไม่ใช่เรื่องของการรื้อทิ้งเขียนใหม่ทั้งหมด แต่คือการมีเข็มทิศในการสกัด Domain Primitives เข้าไปตรงขอบเขตความหมายทีละก้าวอย่างมั่นคง เพื่อเปลี่ยน Legacy Code ที่เปราะบางให้กลายเป็นซอฟต์แวร์ที่ปลอดภัยและทรงพลังอย่างยั่งยืนครับ!

พร้อมก้าวสู่การประยุกต์ใช้ในระบบกระจายตัวในบทถัดไปแล้วหรือยัง? Next: Day 13 — Chapter 13: Guidance on microservices (สถาปัตยกรรมไมโครเซอร์วิสปลอดภัย) ➔