Guidance in Legacy Code
คำตอบและหมุดเชื่อมโยง: คำว่า "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) จะช่วยเปลี่ยนระบบเก่าแสนบรรทัดให้กลายเป็นป้อมปราการที่แข็งแกร่งอย่างมั่นคงโดยไม่ทำให้ระบบเดิมพังครับ!
Days 1-7: รากฐาน DDD, Immutability, Domain Primitives, State Integrity และ Entity Relay
Day 12 จาก 14 วัน — เข้าสู่การประยุกต์ใช้ในโค้ดเก่าแสนบรรทัด (Legacy Monoliths) ก่อนก้าวสู่ Microservices
ชี้จุดรอยแตกร้าวในโค้ดเก่า และสยบปัญหาด้วย 3 ยุทธศาสตร์ Refactoring, กำจัด Defensive Code, และเลี่ยง Partial Primitives
เรื่องราวและมโนทัศน์เชิงลึก (Real Scenarios & Refactoring Strategies)
📖 เรื่องราวจริง: เมธอดจัดส่งพัสดุสุดเปราะบาง
พิจารณาเมธอดในระบบ e-Commerce เก่าแก่ที่ผ่านการแก้ไขมานับสิบปี:
itemId และ quantity ต่างก็เป็น int หากสลับตำแหน่ง คอมไพเลอร์จะไม่เตือน แต่ระบบจะส่งของผิดชนิดและผิดจำนวนทันที!billing และ to เป็นคลาส Address เหมือนกัน หากสลับตำแหน่ง พัสดุจะถูกส่งไปบ้านคนจ่ายเงินแทนที่จะเป็นบ้านผู้รับ!🛡️ 3 ยุทธศาสตร์การ Refactoring พารามิเตอร์
ItemId, Quantity, BillingAddress, ShippingAddress ทันที แล้วตามแก้ Compile Errors ทั้งหมด เหมาะกับ Codebase ขนาดเล็กที่ดูแลโดยทีมเดียว
try-catch แล้ว Log ข้อผิดพลาดทิ้งไว้ใน Production เพื่อส่องดูว่ามี "ข้อมูลขยะ (Bad Data)" ไหลเข้ามามากน้อยเพียงใดก่อนเปลี่ยน API จริง
@Deprecated ที่เมธอดเก่า แล้วทยอยให้ทีมอื่นขยับมาใช้เมธอดใหม่ทีละโมดูลอย่างปลอดภัย
🪖 อุปมาอุปไมย: ทหารตรวจบัตรกันเองในค่าย
ใน Legacy Code มักพบโค้ดที่เต็มไปด้วย if (x != null) ซ้อนกัน 5-10 ชั้น ลึกลงไปจนถึง Private Helper Methods
NullPointerException โปรแกรมเมอร์จึงเอา if (x != null) ไปแปะเป็นพลาสเตอร์ยา (Band-aid) เมื่อเจอข้อมูลขยะ โค้ดมักจะ "เงียบเฉย (Ignore) แล้วทำงานต่อ" ทำให้ข้อมูลขยะค้างในระบบ และเปิดช่องโหว่ให้แฮกเกอร์ส่ง Payload แปลกๆ เข้ามาข้ามขั้นตอนตรวจสอบ!
⚔️ การสลาย Defensive Code ด้วย Boundary Contracts
วิธีแก้ปัญหาไม่ใช่การตามใส่ null check เพิ่ม แต่เป็นการสร้าง สัญญาเด็ดขาดที่ด่านหน้า (Boundary Contracts):
null check ซ้ำซ้อนอีกเลย โค้ดจะกระชับ สะอาด และตรวจสอบได้ง่าย
✂️ กับดัก DRY ผิดวิธี: โฟกัสที่ตัวอักษร ไม่ใช่ไอเดีย
หลักการ Don't Repeat Yourself (DRY) มุ่งเน้นการไม่ฉายซ้ำ "ความรู้ทางธุรกิจ (Knowledge)" ไม่ใช่การลบข้อความหรือ Regex ที่บังเอิญเหมือนกัน!
ZipCode ใช้ Regex \d{5}OrderNumber บังเอิญใช้ Regex \d{5} เหมือนกันUtil.MATCH_5_DIGITS ส่งผลให้โมดูลขนส่งและสั่งซื้อผูกติดกันผ่าน Util ขยะ! หากวันหนึ่ง ZipCode เปลี่ยนเป็น 6 หลัก OrderNumber จะพังไปด้วยทันที!💰 กับดัก Partial Domain Primitives (บทเรียนเรื่องเงิน)
Domain Primitive ต้องเป็น Conceptual Whole (เอกภาพเชิงมโนทัศน์) ห้ามหั่นขาดความหมาย:
สร้างคลาส Amount เก็บเฉพาะตัวเลขทศนิยม 100.00 เมื่อบริษัทขยายไปต่างประเทศ มีการเอา $100 ดอลลาร์ ไปบวกกับ €100 ยูโรตรงๆ ผ่าน amount1.add(amount2) ส่งผลให้ยอดการเงินผิดพลาดมหาศาล!
Money ที่มีทั้ง Amount + Currency เพื่อให้เมธอด add() บล็อกการบวกข้ามสกุลเงินได้ตั้งแต่ในคลาส!
การสั่ง logger.info("Order from: " + rawInput) โดยไม่ผ่านการตรวจสอบ เป็นภัยคุกคามร้ายแรง เพราะแฮกเกอร์สามารถส่งอักขระขึ้นบรรทัดใหม่ (\r\n) เพื่อปลอมแปลง Log (Log Forgery) หรือส่งสคริปต์อันตรายเข้าไปทำลายระบบวิเคราะห์ Log ของทีมงาน (Second-Order Injection)!
แบบฝึกหัดปฏิบัติตามสถานการณ์จริง (Hands-on Refactoring)
// โค้ดเดิม: พารามิเตอร์เป็น 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 ซ้ำซ้อนแล้วเงียบเฉยเมื่อพบข้อมูลผิดปกติ ทำให้ขยะสะสมในฐานข้อมูล
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 ในโค้ดชั้นลึกได้อย่างหมดจด!
ชุดควิซประเมินความรู้ประจำวัน
ตอบปุ๊บ รู้ผลเฉลยทันที พร้อมคำอธิบายและหลอดพลังงาน Legacy Refactoring Charge
ทดสอบความรู้ Day 12 สำเร็จแล้ว!
สรุปการเชื่อมโยงกลับสู่ 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 ที่เปราะบางให้กลายเป็นซอฟต์แวร์ที่ปลอดภัยและทรงพลังอย่างยั่งยืนครับ!