หลักการ DRY: วิธีหลีกเลี่ยงการเขียนโค้ดซ้ำซ้อน

ผู้เขียน: บรรณาธิการซอฟต์แวร์ Webizmเผยแพร่: 24 ส.ค. 2569อัปเดต: 11 ก.ย. 256910 นาที

หลักการ DRY (Don't Repeat Yourself) เป็นแนวทางปฏิบัติหลักในการพัฒนาซอฟต์แวร์ที่มีเป้าหมายเพื่อลดความซ้ำซ้อนของโค้ด เพื่อเพิ่มความสามารถในการบำรุงรักษาและความสามารถในการขยายระบบ

Featured image for หลักการ DRY: วิธีหลีกเลี่ยงการเขียนโค้ดซ้ำซ้อน
Featured image for หลักการ DRY: วิธีหลีกเลี่ยงการเขียนโค้ดซ้ำซ้อน

หลักการ DRY (Don't Repeat Yourself) เป็นหลักการพื้นฐานทางวิศวกรรมซอฟต์แวร์ที่ระบุว่า ความรู้ (knowledge) ทุกส่วนภายในระบบจะต้องมีตัวแทนที่ชัดเจน ไม่กำกวม และเชื่อถือได้เพียงแห่งเดียวเท่านั้น การนำระเบียบวิธีนี้ไปใช้จะช่วยลดหนี้ทางเทคนิค (technical debt) ป้องกันการแตกแขนงของลอจิก (logic fragmentation) และเพิ่มประสิทธิภาพในการดำเนินงานทั่วทั้งฐานโค้ดระดับองค์กรสมัยใหม่

ผู้นำด้านวิศวกรรมซอฟต์แวร์ต้องคอยรักษาสมดุลระหว่างการส่งมอบฟีเจอร์อย่างรวดเร็วกับความสมบูรณ์ของฐานโค้ดในระยะยาวอยู่เสมอ การเชี่ยวชาญหลักการ DRY: วิธีหลีกเลี่ยงการเขียนโค้ดซ้ำซ้อนจึงเป็นสิ่งจำเป็นสำหรับผู้จัดการฝ่ายวิศวกรรม สถาปนิกของระบบ (system architect) และนักพัฒนาที่ต้องการออกแบบสถาปัตยกรรมที่ปรับขยายขนาดได้โดยไม่ก่อให้เกิดต้นทุนการบำรุงรักษาที่เกินจะควบคุม การกำจัดความซ้ำซ้อนที่เกิดขึ้นโดยไม่ตั้งใจจะช่วยเพิ่มประสิทธิภาพเวิร์กโฟลว์การดีบัก ลดระยะเวลาในรอบการดีพลอย (deployment cycles) และสร้างแหล่งข้อมูลความจริงจุดเดียว (single source of truth) ทั่วทั้งบริการต่าง ๆ คู่มือนี้จะวิเคราะห์รูปแบบการรีแฟกเตอร์ (refactoring patterns) ที่นำไปใช้ได้จริง การจัดการขอบเขตทางสถาปัตยกรรม เครื่องมือวิเคราะห์โค้ดแบบสแตติก (static analysis tooling) รวมถึงข้อดีข้อเสียเชิงกลยุทธ์ระหว่างการทำนามธรรมที่สะอาด (clean abstraction) กับการออกแบบซับซ้อนเกินจำเป็นก่อนเวลาอันควร (premature over-engineering)

ทำความเข้าใจหลักการ DRY ในวิศวกรรมซอฟต์แวร์สมัยใหม่

หลักการ DRY มีต้นกำเนิดมาจากผลงานระดับรากฐานในปี 1999 เรื่องThe Pragmatic Programmerโดย Andy Hunt และ Dave Thomas โดยหัวใจสำคัญของหลักการนี้มุ่งจัดการกับการซ้ำซ้อนของความรู้และเจตนามากกว่าการเป็นเพียงบรรทัดไวยากรณ์ที่เหมือนกัน ในการพัฒนาซอฟต์แวร์ระดับองค์กร ลอจิกที่ซ้ำซ้อนจะสร้างเส้นทางการพัฒนาที่วิวัฒนาการคู่ขนานกัน: เมื่อกฎทางธุรกิจเปลี่ยนแปลง วิศวกรจะต้องค้นหาและอัปเดตทุกอินสแตนซ์ที่กระจัดกระจายของกฎนั้น หากมองข้ามไปแม้แต่จุดเดียว ระบบจะตกอยู่ในสภาวะความคลาดเคลื่อนทางความหมาย (semantic divergence) ซึ่งก่อให้เกิดความเสียหายของข้อมูลแบบเงียบ ๆ (silent data corruption) และพฤติกรรมการทำงานขณะรันไทม์ที่ไม่สามารถคาดเดาได้

สถาปัตยกรรมแบบกระจายศูนย์สมัยใหม่ยิ่งตอกย้ำความสำคัญของการออกแบบที่ยึดหลักแหล่งข้อมูลความจริงจุดเดียว (single source of truth) นโยบายทางธุรกิจเพียงข้อเดียว—เช่น การคำนวณภาษีมูลค่าเพิ่มหรือการตรวจสอบยืนยันตัวตนลูกค้า—มักครอบคลุมทั้งไคลเอนต์ฝั่งฟรอนต์เอนด์, API gateway, ไมโครเซอร์วิส และข้อจำกัดของฐานข้อมูล (database constraints) เมื่อทีมพัฒนาซอฟต์แวร์ปฏิบัติต่อ DRY เป็นเพียงแค่การบีบอัดข้อความ พวกเขาก็จะพลาดคุณค่าในระดับระบบของมันไป การยึดมั่นใน DRY อย่างแท้จริงจะช่วยให้มั่นใจได้ว่าการแก้ไขข้อจำกัดของโดเมนใด ๆ จะต้องทำที่คอมโพเนนต์หลักที่เชื่อถือได้เพียงแห่งเดียวทั่วทั้งโครงสร้างพื้นฐานทั้งหมด

// Anti-pattern: Duplicated domain knowledge across separate payment handlers
function processCreditCardPayment(amount: number, taxRate: number): number {
  const serviceFee = 2.50;
  const taxableAmount = amount + serviceFee;
  return taxableAmount + (taxableAmount * taxRate);
}

function processBankTransferPayment(amount: number, taxRate: number): number {
  const serviceFee = 2.50; // Duplicated domain rule: service fee definition
  const taxableAmount = amount + serviceFee;
  return taxableAmount + (taxableAmount * taxRate);
}

// Canonical Refactoring: Single Source of Truth for Fee and Tax Calculation
class FinancialCalculationEngine {
  private static readonly BASE_SERVICE_FEE = 2.50;

  public static calculateTotalWithTax(amount: number, taxRate: number, additionalFee = 0): number {
    const totalFee = this.BASE_SERVICE_FEE + additionalFee;
    const taxableBase = amount + totalFee;
    return Number((taxableBase * (1 + taxRate)).toFixed(2));
  }
}

จุดกำเนิดและปรัชญาหลัก

ปรัชญาพื้นฐานของ DRY มีรากฐานมาจากความสามารถในการบำรุงรักษาและการลดภาระทางปัญญา (cognitive load) เมื่อฐานโค้ดขยายใหญ่ขึ้นจนมีขนาดหลายแสนบรรทัด ไม่มีวิศวกรคนใดสามารถจดจำแผนภาพความคิด (mental map) ของกฎทางธุรกิจที่ซ้ำซ้อนทั้งหมดได้อย่างสมบูรณ์ หากความรู้กระจัดกระจาย การแก้ไขตามปกติจะกลายเป็นการดำเนินการรีแฟกเตอร์ที่มีความเสี่ยงสูง โดย Hunt และ Thomas ได้นิยามความรู้ว่าประกอบด้วยกฎทางธุรกิจ รูปแบบตัวแทนในฐานข้อมูล พารามิเตอร์การกำหนดค่า และสัญญาอินเทอร์เฟซภายนอก

องค์กรด้านวิศวกรรมมักสับสนระหว่างการซ้ำซ้อนของโครงสร้างโค้ดกับการซ้ำซ้อนของความรู้ ซับรูทีน (subroutine) สองตัวอาจมีไวยากรณ์ที่เหมือนกันทุกประการ แต่กลับเป็นตัวแทนของแนวคิดทางโดเมนที่ไม่เกี่ยวข้องกันเลยและเปลี่ยนแปลงด้วยเหตุผลที่ต่างกันโดยสิ้นเชิง ตัวอย่างเช่น กฎการตรวจสอบความถูกต้องที่ตรวจว่าอายุของผู้ใช้มากกว่า 18 ปีหรือไม่ กับการตรวจสอบสินค้าคงคลังเพื่อให้แน่ใจว่าสต็อกขั้นต่ำมากกว่า 18 รายการ ทั้งคู่ใช้ตัวเลข 18 และตัวดำเนินการเปรียบเทียบเหมือนกัน การรวมทั้งสองสิ่งนี้เข้าด้วยกันเป็นฟังก์ชันทั่วไป (generic function) ฟังก์ชันเดียวจะผูกมัดโดเมนที่ไม่เกี่ยวข้องกันเข้าด้วยกัน และสร้างนามธรรมที่เปราะบางซึ่งจะพังทลายลงเมื่อความต้องการของโดเมนใดโดเมนหนึ่งเปลี่ยนแปลงไป

เหนือกว่าเรื่องโค้ด: DRY ในสคีมาฐานข้อมูลและเอกสารประกอบ

หลักการ DRY ครอบคลุมตลอดวงจรการส่งมอบซอฟต์แวร์ รวมถึงเลเยอร์การจัดเก็บข้อมูลและการกำหนดค่าโครงสร้างพื้นฐาน ในการออกแบบฐานข้อมูลเชิงสัมพันธ์ การทำนอร์แมลไลเซชันของฐานข้อมูล (database normalization โดยเฉพาะอย่างยิ่ง Third Normal Form หรือ 3NF) ทำหน้าที่เทียบเท่ากับ DRY ในเชิงความสัมพันธ์ การจัดเก็บที่อยู่ของลูกค้ารายเดิมไว้ในตารางธุรกรรมหลาย ๆ ตารางจะนำไปสู่ความผิดปกติของการอัปเดต (update anomalies) เมื่อลูกค้าแก้ไขโปรไฟล์ของตน การทำนอร์แมลไลเซชันจะกำจัดความซ้ำซ้อนของข้อมูลโดยการอ้างอิงถึงตัวระบุเอนทิตี (entity identifier) ที่เป็นทางการ

-- Violating DRY: Storing customer contact details inside every order record
CREATE TABLE customer_orders (
    order_id UUID PRIMARY KEY,
    customer_id UUID NOT NULL,
    customer_email VARCHAR(255) NOT NULL, -- Redundant
    customer_phone VARCHAR(50) NOT NULL,  -- Redundant
    total_amount DECIMAL(12, 2) NOT NULL
);

-- DRY Compliant: Normalized tables maintaining a Single Source of Truth (SSOT)
CREATE TABLE customers (
    customer_id UUID PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    phone VARCHAR(50) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE orders (
    order_id UUID PRIMARY KEY,
    customer_id UUID NOT NULL REFERENCES customers(customer_id) ON DELETE RESTRICT,
    total_amount DECIMAL(12, 2) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

DRY ยังครอบคลุมถึงเอกสารประกอบ สัญญา API (API contracts) และนิยามสคีมาอีกด้วย การซิงโครไนซ์เอกสาร API กับโค้ดแบ็กเอนด์ด้วยตนเองมักทำให้พอร์ทัลของนักพัฒนาล้าสมัย เวิร์กโฟลว์ทางวิศวกรรมสมัยใหม่ใช้การสร้างอัตโนมัติแบบ schema-first หรือ code-first โดยเครื่องมือจะดึงข้อกำหนด OpenAPI (Swagger) โดยตรงจากโมเดลแบ็กเอนด์ที่มีการกำกับแอนโนเทชันไว้ และชุดพัฒนาซอฟต์แวร์สำหรับไคลเอนต์ (SDKs) จะถูกสร้างขึ้นในขั้นตอนถัดไปผ่านไปป์ไลน์ CI/CD อัตโนมัติ ซึ่งจะช่วยให้มั่นใจได้ว่าเอกสารประกอบ ไลบรารีฝั่งไคลเอนต์ และเอนด์พอยต์ของเซิร์ฟเวอร์จะใช้สัญญาร่วมกันอย่างเหมือนกันและซิงค์ตรงกันเสมอ

ต้นทุนทางธุรกิจและทางเทคนิคของความซ้ำซ้อนของโค้ด

การทำโค้ดซ้ำซ้อนอย่างควบคุมไม่ได้ก่อให้เกิดความติดขัดในการดำเนินงานเชิงระบบ ซึ่งส่งผลให้ความเร็วในการพัฒนาทางวิศวกรรม (Engineering Velocity) ลดลงโดยตรง เมื่อทีมพัฒนาซอฟต์แวร์คัดลอกลอจิกซ้ำเพื่อเร่งให้ทันกำหนดส่งงานระยะสั้น พวกเขากำลังก่อหนี้ทางเทคนิคที่มีดอกเบี้ยสูง แม้การคัดลอกและวางโค้ดในตอนแรกจะใช้เวลาเพียงไม่กี่วินาที แต่ต้นทุนที่ตามมาภายหลังกลับทบยอดสะสมตลอดหลายปีของการบำรุงรักษา การตรวจทานโค้ด (Code Review) ภาระการทดสอบ และการแก้ไขข้อผิดพลาดที่ส่งผลกระทบต่อลูกค้า

ในมุมมองทางการเงิน โค้ดที่ซ้ำซ้อนทำให้ต้นทุนรวมในการเป็นเจ้าของ (TCO) ของสินทรัพย์ซอฟต์แวร์เพิ่มสูงขึ้น การส่งมอบฟีเจอร์ใหม่ๆ ช้าลงเนื่องจากวิศวกรต้องใช้เวลามากเกินความจำเป็นไปกับการวิเคราะห์ผลกระทบ และการตรวจสอบว่าบั๊กที่พบในโมดูลหนึ่งมีอยู่ในที่เก็บโค้ด (Repository) หรือไมโครเซอร์วิสอื่นอีกสามแห่งหรือไม่ องค์กรจึงต้องจ่ายต้นทุนให้กับการแก้ไขบั๊กเดิมซ้ำแล้วซ้ำเล่าในหลายไตรมาสและผ่านบุคลากรหลายคน

ตัวชี้วัดทางธุรกิจและทางเทคนิคผลกระทบของสถาปัตยกรรมแบบ DRYผลกระทบของฐานโค้ดแบบ WET (ซ้ำซ้อน)
เวลาที่ใช้ในการแก้ไขข้อผิดพลาดต่ำ (แพตช์จุดเดียวในโมดูลมาตรฐานหลัก)สูง (ต้องค้นหาในหลายไฟล์และมีความเสี่ยงต่อปัญหาเดิมที่อาจเกิดขึ้นซ้ำ)
เสถียรภาพของข้อกำหนด API (API Contract)สูง (สร้างขึ้นจากสคีมาเดี่ยว)ต่ำ (เกิดความคลาดเคลื่อนจากการซิงโครไนซ์ด้วยตนเอง)
ความเร็วในการตรวจทานโค้ดเร็ว (การเปรียบเทียบโค้ดสั้นกระชับและเป็นโมดูล)ช้า (ต้องตรวจทานบล็อกโค้ดขนาดใหญ่ที่ซ้ำซ้อน)
ประสิทธิภาพของความครอบคลุมในการทดสอบสูง (การทดสอบระดับหน่วยครอบคลุมต่อหนึ่งหน่วย)ต่ำ (การทดสอบเจือจางและมีการตรวจสอบเงื่อนไขซ้ำซ้อน)
ความติดขัดในการเริ่มต้นทำงานของพนักงานใหม่ต่ำ (มีกรอบความคิดและขอบเขตการทำงานที่ชัดเจน)สูง (ภาระการรับรู้มากเกินไปจากลอจิกที่ไม่สอดคล้องกัน)

เวลาที่ใช้ในการแก้ไขข้อผิดพลาด

ผลกระทบของสถาปัตยกรรมแบบ DRY

ต่ำ (แพตช์จุดเดียวในโมดูลมาตรฐานหลัก)

ผลกระทบของฐานโค้ดแบบ WET (ซ้ำซ้อน)

สูง (ต้องค้นหาในหลายไฟล์และมีความเสี่ยงต่อปัญหาเดิมที่อาจเกิดขึ้นซ้ำ)

เสถียรภาพของข้อกำหนด API (API Contract)

ผลกระทบของสถาปัตยกรรมแบบ DRY

สูง (สร้างขึ้นจากสคีมาเดี่ยว)

ผลกระทบของฐานโค้ดแบบ WET (ซ้ำซ้อน)

ต่ำ (เกิดความคลาดเคลื่อนจากการซิงโครไนซ์ด้วยตนเอง)

ความเร็วในการตรวจทานโค้ด

ผลกระทบของสถาปัตยกรรมแบบ DRY

เร็ว (การเปรียบเทียบโค้ดสั้นกระชับและเป็นโมดูล)

ผลกระทบของฐานโค้ดแบบ WET (ซ้ำซ้อน)

ช้า (ต้องตรวจทานบล็อกโค้ดขนาดใหญ่ที่ซ้ำซ้อน)

ประสิทธิภาพของความครอบคลุมในการทดสอบ

ผลกระทบของสถาปัตยกรรมแบบ DRY

สูง (การทดสอบระดับหน่วยครอบคลุมต่อหนึ่งหน่วย)

ผลกระทบของฐานโค้ดแบบ WET (ซ้ำซ้อน)

ต่ำ (การทดสอบเจือจางและมีการตรวจสอบเงื่อนไขซ้ำซ้อน)

ความติดขัดในการเริ่มต้นทำงานของพนักงานใหม่

ผลกระทบของสถาปัตยกรรมแบบ DRY

ต่ำ (มีกรอบความคิดและขอบเขตการทำงานที่ชัดเจน)

ผลกระทบของฐานโค้ดแบบ WET (ซ้ำซ้อน)

สูง (ภาระการรับรู้มากเกินไปจากลอจิกที่ไม่สอดคล้องกัน)

ภาระการบำรุงรักษาและหนี้ทางเทคนิคที่เพิ่มสูงขึ้นอย่างต่อเนื่อง

หนี้ทางเทคนิคที่ปรากฏในรูปของโค้ดที่ซ้ำซ้อนนั้นสร้างความเสียหายอย่างยิ่ง เพราะมันทำให้ความอ่านง่ายเข้าใจง่ายของฐานโค้ดโดยรวมลดลง เมื่อวิศวกรใหม่เข้าร่วมทีม พวกเขาจะพบกับการนำกฎทางธุรกิจเดียวกันไปใช้งานจริงในหลายรูปแบบ โดยแต่ละแบบถูกปรับแก้เล็กน้อยเพื่อให้เข้ากับกรณีขอบเขตเฉพาะส่วน สิ่งนี้ก่อให้เกิดความคลุมเครือ กล่าวคือนักพัฒนาไม่สามารถระบุได้ว่าการเขียนโค้ดแบบใดเป็นมาตรฐานที่ถูกต้องเป็นทางการ และแบบใดมีบั๊กเดิมที่ยังไม่ได้รับการแพตช์แก้ไข

ยิ่งไปกว่านั้น โค้ดที่ซ้ำซ้อนยังเพิ่มพื้นที่ผิว (Surface Area) ที่ชุดทดสอบอัตโนมัติจะต้องครอบคลุม แทนที่จะเขียนการทดสอบระดับหน่วย (Unit Test) ที่ละเอียดรอบคอบและให้ผลลัพธ์แน่นอนสำหรับโมดูลเดี่ยวที่แยกเป็นอิสระ ทีมกลับต้องเขียนการทดสอบการผสานรวม (Integration Test) และการทดสอบแบบครบวงจร (End-to-End Test) ซ้ำซ้อนครอบคลุมทุกการนำโค้ดไปใช้ที่ซ้ำกัน สิ่งนี้ทำให้ระยะเวลาการรันระบบการผสานรวมอย่างต่อเนื่อง (CI) ยาวนานขึ้น ทำให้ไปป์ไลน์การปรับใช้ระบบ (Deployment Pipeline) ล่าช้า และเพิ่มค่าใช้จ่ายในการประมวลผลบนคลาวด์สำหรับโครงสร้างพื้นฐานการทดสอบ

ความเสี่ยงที่เพิ่มขึ้นต่อลอจิกที่ไม่สอดคล้องกันและบั๊ก

ช่องโหว่ด้านความปลอดภัยและความล้มเหลวด้านการปฏิบัติตามข้อกำหนดเป็นผลข้างเคียงที่พบบ่อยจากความซ้ำซ้อนของโค้ด ในสภาพแวดล้อมที่ควบคุมโดยมาตรฐานการกำกับดูแล เช่น GDPR, HIPAA หรือ PCI-DSS การดำเนินการประมวลผลข้อมูลจะต้องปฏิบัติตามโปรโตคอลที่เข้มงวดและตรวจสอบได้ หากรูทีนการชำระล้างข้อมูลนำเข้า (Input Sanitization) การตรวจสอบความถูกต้องของโทเคน หรือการแฮชแบบเข้ารหัสถูกทำซ้ำแบบแมนนวลในบริการต่างๆ แทนที่จะเผยแพร่เป็นไลบรารีที่ผ่านการเสริมความปลอดภัยและดูแลรักษาจากศูนย์กลาง ความคลาดเคลื่อนไม่สอดคล้องกันก็จะเกิดขึ้น

ลองพิจารณาช่องโหว่ในการตรวจสอบความถูกต้องของข้อมูลนำเข้า เช่น SQL Injection หรือ Cross-Site Scripting (XSS) เมื่อการตรวจสอบความปลอดภัยพบเร็กเอ็กซ์ (Regex) หรืออัลกอริทึมการแยกวิเคราะห์พารามิเตอร์ที่มีข้อบกพร่องในชิ้นส่วนโค้ดที่ซ้ำกัน การแก้ไขเฉพาะจุดที่ถูกแจ้งเตือนจะปล่อยให้อินสแตนซ์ที่เหลือตกอยู่ในความเสี่ยง ผู้โจมตีมักจะสำรวจจุดสิ้นสุด (Endpoint) ในกรณีขอบเขตอยู่เป็นประจำ เพราะรู้ว่าระบบรองมักจะพลาดแพตช์ความปลอดภัยที่สำคัญซึ่งถูกนำไปใช้กับฐานโค้ดหลัก

# Security Risk: Inconsistent cryptographic hashing implementations

# Module: User Management Service
import hashlib

def hash_password_v1(password: str, salt: str) -> str:
    # Outdated, insecure hashing repeated in legacy user service
    return hashlib.sha256((password + salt).encode('utf-8')).hexdigest()

# Module: Authentication Service
import bcrypt

def hash_password_v2(password: str) -> str:
    # Modern, secure hashing used in newer auth service
    return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')

ตัวอย่างข้างต้นแสดงให้เห็นถึงองค์กรที่มีเส้นทางการยืนยันตัวตนสองเส้นทางซึ่งจัดการรหัสผ่านโดยใช้มาตรฐานความปลอดภัยที่แตกต่างกัน การคงไว้ซึ่งการทำงานทั้งสองรูปแบบทำให้ผู้ใช้ที่ยืนยันตัวตนผ่านโมดูลรุ่นเก่าเสี่ยงต่อการถูกเจาะรหัสผ่าน ซึ่งสะท้อนให้เห็นว่าการทำซ้ำองค์ความรู้ (Knowledge Duplication) บั่นทอนระดับความมั่นคงปลอดภัยของระบบโดยตรงอย่างไร

กลยุทธ์ที่นำไปปฏิบัติได้จริงเพื่อขจัดโค้ดที่ซ้ำซ้อน

การขจัดโค้ดที่ซ้ำซ้อนอย่างเป็นระบบจำเป็นต้องอาศัยการจัดโครงสร้างสถาปัตยกรรมอย่างรอบคอบ ระเบียบการรีแฟกเตอร์ที่มีวินัย และรูปแบบการแยกส่วนเป็นโมดูลที่ทันสมัย องค์กรต่างๆ จะต้องเปลี่ยนผ่านจากการคัดลอกและวางแบบตั้งรับ ไปสู่การนำคอมโพเนนต์กลับมาใช้ใหม่ในเชิงรุก การเปลี่ยนผ่านนี้ต้องพึ่งพาเสาหลักทางวิศวกรรม 3 ประการ ได้แก่ การสกัดฟังก์ชันยูทิลิตีบริสุทธิ์ การนำแบบแผนการออกแบบเชิงโครงสร้างไปปรับใช้ และการแจกจ่ายโมดูลในระดับแพ็กเกจ

ก่อนที่จะเริ่มการรีแฟกเตอร์ วิศวกรควรกำหนดความครอบคลุมของการทดสอบระดับหน่วยให้ครบถ้วนเหนือโค้ดเป้าหมายเพื่อรักษาพฤติกรรมการทำงานเดิมไว้ การทดสอบระดับหน่วยอัตโนมัติจะทำหน้าที่เป็นตาข่ายนิรภัย เพื่อให้มั่นใจว่าการรวมบล็อกโค้ดที่กระจัดกระจายเข้าสู่แอบสแตรกชันส่วนกลางจะไม่ก่อให้เกิดปัญหาเดิมซ้ำ (Zero Regressions) เมื่อมีความครอบคลุมของการทดสอบแล้ว วิศวกรจึงจะสามารถสกัดลอจิกที่ซ้ำซ้อนออกมาเป็นฟังก์ชันบริสุทธิ์ โดเมนเซอร์วิสส่วนกลาง หรือแบบแผนการออกแบบที่มีการกำหนดพารามิเตอร์

การใช้ประโยชน์จากฟังก์ชันและโมดูลที่นำกลับมาใช้ใหม่ได้

กลไกที่ตรงไปตรงมาที่สุดในการกำจัดความซ้ำซ้อนคือการแยกย่อยโพรซีเยอร์ขนาดใหญ่แบบโมโนลิทิก (monolithic) ให้เป็นฟังก์ชันขนาดเล็กที่มีหน้าที่รับผิดชอบเพียงอย่างเดียว (single-responsibility) ฟังก์ชันที่นำกลับมาใช้ใหม่ได้ควรปฏิบัติตามหลักการของการเขียนโปรแกรมเชิงฟังก์ชัน (functional programming) โดยจะต้องมีความแน่นอน (pure) ซึ่งหมายความว่าเมื่อได้รับอินพุตเดิม ฟังก์ชันจะต้องคืนค่าผลลัพธ์เดิมเสมอโดยไม่ก่อให้เกิดผลข้างเคียง (side effects) แอบแฝงต่อสถานะส่วนกลาง (global state)

// Shared Utility Module: Canonical Address Parsing Engine
export interface Address {
  street: string;
  city: string;
  postalCode: string;
  country: string;
}

export class AddressNormalizer {
  /**
   * Deterministic normalization of unstructured address strings.
   * Single authoritative implementation used across Billing, Shipping, and CRM.
   */
  public static normalize(rawAddress: string): Address {
    const parts = rawAddress.split(',').map(part => part.trim());
    if (parts.length < 4) {
      throw new Error("Invalid address format: insufficient geographic segments.");
    }
    
    return {
      street: parts[0],
      city: parts[1],
      postalCode: parts[2].toUpperCase(),
      country: parts[3].toUpperCase(),
    };
  }
}

การแยกกระบวนการปรับมาตรฐานที่อยู่ (address normalization) ออกมาเป็นโมดูลเดี่ยวๆ ทำให้การแก้ไขตรรกะการจัดรูปแบบหรือการตรวจสอบความถูกต้องของที่อยู่นานาชาติทำเพียงครั้งเดียว ส่วนผู้ใช้งานปลายทาง (downstream consumers) จะนำเข้าโมดูลดังกล่าวไปใช้ ซึ่งช่วยขจัดการแยกวิเคราะห์ด้วย regex แบบเฉพาะกิจในบริการเรียกเก็บเงินและบริการจัดการคำสั่งซื้อ

การนำรูปแบบการออกแบบเชิงมาตรฐาน (Design Patterns) มาปรับใช้

รูปแบบการออกแบบเชิงวัตถุและเชิงฟังก์ชันเป็นพิมพ์เขียวที่มีโครงสร้างสำหรับการรวมตรรกะทางสถาปัตยกรรมที่ซ้ำซ้อนเข้าด้วยกัน รูปแบบทั่วไปที่ใช้ในการกำจัดความซ้ำซ้อน ได้แก่:

  • Strategy Pattern:แคปซูลแปรผันของอัลกอริทึม (algorithmic variations) ให้อยู่ในคลาสที่สับเปลี่ยนกันได้ ซึ่งช่วยขจัดบล็อกเงื่อนไขขนาดใหญ่ (if/elseหรือswitchที่ต่อกันเป็นลูกโซ่) ที่ซ้ำซ้อนกันในแฮนด์เลอร์ต่างๆ

  • Template Method Pattern:กำหนดโครงร่างของอัลกอริทึมไว้ในคลาสฐาน (base class) โดยเปิดโอกาสให้คลาสย่อย (subclass) ปรับเปลี่ยนขั้นตอนเฉพาะบางอย่างได้ใหม่ โดยไม่กระทบต่อโครงสร้างอัลกอริทึมโดยรวมที่ใช้งานร่วมกัน

  • Decorator / Middleware Pattern:แยกประเด็นที่ส่งผลกระทบข้ามส่วน (cross-cutting concerns) เช่น การบันทึกข้อมูล (logging), การยืนยันตัวตน (authentication), การจำกัดอัตราการเรียกใช้ (rate limiting), การตรวจสอบความถูกต้องของคำขอ (request validation) ออกมาเป็นลำดับขั้นตอนไปป์ไลน์แบบแยกส่วน แทนที่จะต้องเขียนโค้ดสำเร็จรูป (boilerplate) ซ้ำๆ ในทุกเส้นทาง API

// Template Method Pattern: Eliminating duplicated workflow logic in report generation
public abstract class DataExporter {
    
    // The overarching execution workflow is defined once (DRY)
    public final void exportData(String query) {
        connectDataSource();
        byte[] rawData = fetchData(query);
        byte[] formattedData = formatData(rawData);
        writeToDestination(formattedData);
        logCompletion();
    }

    private void connectDataSource() {
        System.out.println("Connecting to primary database cluster...");
    }

    private void logCompletion() {
        System.out.println("Data export pipeline finalized successfully.");
    }

    // Concrete variations implemented by specialized subclasses
    protected abstract byte[] fetchData(String query);
    protected abstract byte[] formatData(byte[] data);
    protected abstract void writeToDestination(byte[] formattedData);
}

การปรับใช้สถาปัตยกรรมแบบแยกส่วน (Modular Architecture) และไมโครเซอร์วิส (Microservices)

ในระดับองค์กร ความซ้ำซ้อนมักเกิดขึ้นครอบคลุมที่เก็บโค้ด (repository) อิสระหลายแห่ง การกำจัดความซ้ำซ้อนข้ามที่เก็บโค้ดจำเป็นต้องมีกลยุทธ์การจัดการแพ็กเกจอย่างเป็นทางการ ทีมวิศวกรรมจะแยกตรรกะโดเมนส่วนกลาง, ไคลเอนต์ API และยูทิลิตี้ด้านความปลอดภัย ออกมาเป็นแพ็กเกจภายในที่มีการกำกับเวอร์ชัน และเผยแพร่ไปยังรีจิสทรีอาร์ทิแฟกต์ส่วนตัว (เช่น npm, PyPI, Maven Central หรือ Artifactory)

ในสถาปัตยกรรมไมโครเซอร์วิส ขอบเขตของโดเมนต้องได้รับการกำหนดไว้อย่างชัดเจนโดยใช้หลักการออกแบบที่ขับเคลื่อนด้วยโดเมน (Domain-Driven Design หรือ DDD) โดย shared kernel ควรมีเพียงองค์ประกอบพื้นฐานของโดเมนที่เสถียรเท่านั้น และหลีกเลี่ยงไลบรารี "shared-utility" ขนาดใหญ่แบบโมโนลิทิกที่ก่อให้เกิดความผูกพันกันอย่างแน่นหนา (tight coupling) ข้ามการดีพลอยที่เป็นอิสระจากกัน เมื่อไมโครเซอร์วิสหลายตัวจำเป็นต้องเข้าถึงความสามารถในการประมวลผลข้อมูลร่วมกัน การแยกออกมาเป็นบริการโดเมนเฉพาะ (dedicated domain service) จึงเป็นทางเลือกที่ดีกว่าการเขียนตรรกะซ้ำซ้อนข้ามขอบเขตของเซอร์วิส

ขั้นตอนการดำเนินงาน

ขั้นตอนการปรับโครงสร้างโค้ดเบสอย่างเป็นระบบ (Systematic Codebase Refactoring Workflow)

กระบวนการ 4 ขั้นตอนเพื่อรวมตรรกะที่ซ้ำซ้อนเข้าสู่แอบสแตรกชันส่วนกลางได้อย่างปลอดภัย

01

กำหนดการทดสอบอัตโนมัติพื้นฐาน (Baseline Automated Tests)

เขียนการทดสอบระดับหน่วย (unit test) และการทดสอบรวมหน่วย (integration test) อย่างครอบคลุมรอบบล็อกโค้ดที่ซ้ำซ้อนทั้งหมด เพื่อบันทึกพฤติกรรมการทำงานที่มีอยู่และกรณีขอบเขต (edge cases) ต่างๆ

02

แยกตรรกะส่วนกลางออกมาเป็นโมดูลเดี่ยว

สร้างเพียวฟังก์ชัน (pure function) หรือคลาสเดี่ยวที่แคปซูลองค์ความรู้ส่วนกลางนั้นไว้ พร้อมทั้งแปลงตัวแปรที่แตกต่างกันให้เป็นพารามิเตอร์

03

ปรับเปลี่ยนจุดเรียกใช้โค้ดให้ไปยังแอบสแตรกชันส่วนกลาง

แทนที่ส่วนย่อยของโค้ดที่ซ้ำซ้อนในโค้ดเบสด้วยการเรียกใช้โมดูลที่รวมขึ้นใหม่

04

รันชุดทดสอบความถดถอย (Regression Suites) และตรวจสอบข้อมูลมาตรวัดระยะไกล (Telemetry)

ดำเนินการไปป์ไลน์การทดสอบ CI แบบอัตโนมัติ และติดตามระบบตรวจจับข้อผิดพลาดขณะทำงาน (runtime error tracking) เพื่อยืนยันเสถียรภาพในการปฏิบัติงาน

การทำความเข้าใจและตรวจจับแอนตีแพตเทิร์น WET (Write Everything Twice)

สิ่งที่อยู่ตรงข้ามกับ DRY คือแอนตีแพตเทิร์นWETซึ่งเป็นคำย่อที่ตีความได้หลากหลาย เช่น"Write Everything Twice" (เขียนทุกอย่างสองครั้ง), "We Enjoy Typing" (พวกเราสนุกกับการพิมพ์)หรือ"Waste Everyone's Time" (เสียเวลาของทุกคน)การพัฒนาซอฟต์แวร์แบบ WET เกิดขึ้นเมื่อวิศวกรคัดลอกและวางบล็อกโค้ดเดิมเพื่อให้ส่งมอบฟังก์ชันการทำงานได้ทันที โดยไม่ได้คำนึงถึงความสอดคล้องทางสถาปัตยกรรม แม้ว่าวิธีนี้จะช่วยเพิ่มความเร็วชั่วขณะในระหว่างการสร้างต้นแบบระยะแรก แต่จะสร้างปัญหาคอขวดด้านความสามารถในการบำรุงรักษาในระยะยาว

โค้ดเบสแบบ WET เผชิญกับภาวะเอนโทรปี (entropy) ที่รุนแรง เมื่อตรรกะถูกทำซ้ำในหลายเซอร์วิส การทำงานเหล่านั้นจะเบี่ยงเบนออกจากกันอย่างหลีกเลี่ยงไม่ได้ เนื่องจากนักพัฒนาแต่ละคนทำการแก้ไขข้อผิดพลาด (bug fixes), ปรับปรุงประสิทธิภาพ หรืออัปเกรดเฟรมเวิร์กแยกเฉพาะในโค้ดชุดหนึ่งโดยปล่อยให้ชุดอื่นไม่ได้รับการแก้ไข ความเบี่ยงเบนนี้เปลี่ยนโค้ดเบสที่เรียบง่ายให้กลายเป็นสถาปัตยกรรมที่เปราะบาง ซึ่งการแก้ไขฟีเจอร์เพียงจุดเดียวกลับต้องอัปเดตไฟล์ต่างๆ มากมายที่ไม่เกี่ยวข้องกัน

สัญญาณบ่งชี้ทั่วไปของโค้ดเบสแบบ WET

ผู้นำฝ่ายวิศวกรรมสามารถวินิจฉัยโค้ดเบสแบบ WET ได้โดยการติดตามตัวชี้วัดเชิงโครงสร้างและพฤติกรรมที่เฉพาะเจาะจง ดังนี้:

  • การทำแพตช์ซ้ำแบบเดียวกัน:Pull Request ต้องคอยใช้การแก้ไขเชิงตรรกะแบบเดียวกันซ้ำๆ ในหลายไฟล์หรือหลาย Repository ที่แยกจากกันอยู่ตลอดเวลา

  • กฎการตรวจสอบความถูกต้องที่ไม่สอดคล้องกัน:มีการจัดการกรณีขอบเขต (Edge cases) แตกต่างกันไปตาม Endpoint ต่างๆ (เช่น การตรวจสอบความถูกต้องของอีเมลในการลงทะเบียนผ่านมือถือยอมรับอักขระมาตรฐาน แต่การลงทะเบียนบนเว็บกลับปฏิเสธ Top-Level Domain ที่ถูกต้องบางรายการ)

  • การแพร่กระจายของโค้ดสำเร็จรูป (Boilerplate Proliferation):API controller ใหม่ทุกตัวมีโค้ดตรวจสอบการอนุญาตสิทธิ์, การถอดรหัสพารามิเตอร์ และตัวจัดการข้อผิดพลาดที่ซ้ำกันถึง 30–50 บรรทัด

  • ชุดข้อมูลทดสอบที่กระจัดกระจาย (Fragmented Test Fixtures):ชุดการทดสอบต้องพึ่งพา Mock object และสคริปต์ตั้งค่าฐานข้อมูลที่คัดลอกและวางซ้ำๆ กันนับร้อยจุด แทนที่จะใช้ Factory function ที่รวมศูนย์ไว้ที่เดียว

// WET Anti-Pattern: Duplicated Authorization & Validation Logic in Controllers
public class InvoiceController : ControllerBase 
{
    [HttpGet("{id}")]
    public IActionResult GetInvoice(Guid id) 
    {
        // Duplicated security check
        var userRole = HttpContext.Request.Headers["X-User-Role"].FirstOrDefault();
        if (userRole != "Admin" && userRole != "BillingManager") {
            return Forbid();
        }
        return Ok(_invoiceService.GetById(id));
    }
}

public class PaymentController : ControllerBase 
{
    [HttpGet("{id}")]
    public IActionResult GetPayment(Guid id) 
    {
        // Identical security check repeated manually
        var userRole = HttpContext.Request.Headers["X-User-Role"].FirstOrDefault();
        if (userRole != "Admin" && userRole != "BillingManager") {
            return Forbid();
        }
        return Ok(_paymentService.GetById(id));
    }
}

ในตัวอย่าง C# ข้างต้น การตรวจสอบการอนุญาตสิทธิ์ด้วยตนเองถูกทำซ้ำไปมาในหลาย Controller หากองค์กรเพิ่มบทบาทใหม่เข้ามา (เช่น"Auditor") การทำงานของ Controller action ทุกตัวจะต้องได้รับการอัปเดตและทดสอบซ้ำด้วยตนเอง การปรับโครงสร้างโค้ด (Refactoring) การตรวจสอบนี้ไปไว้ใน Custom ASP.NET Core Authorization Policy หรือ Action Filter จะช่วยรวมศูนย์ตรรกะความปลอดภัยให้เป็นมาตรฐานเดียวที่ดูแลรักษาง่าย

การประเมินผลกระทบต่อความสามารถในการขยายขนาดของระบบ

การทำซ้ำโค้ดส่งผลเสียโดยตรงต่อความสามารถในการขยายขนาดเชิงการคำนวณและโครงสร้างพื้นฐาน การแยกส่วนเชิงนามธรรม (Abstraction) ที่ซ้ำซ้อนจะเพิ่มการใช้หน่วยความจำ (Memory footprint), ทำให้ขนาด Container image บวมขึ้น และเพิ่มเวลาในการคอมไพล์ไบนารี ในสภาพแวดล้อมการประมวลผลแบบไร้เซิร์ฟเวอร์ (เช่น AWS Lambda หรือ Google Cloud Functions) แพ็กเกจการปรับใช้ (Deployment package) ที่บวมโตจะเพิ่มเวลาแฝงในการเริ่มต้นระบบขณะเครื่องยังไม่พร้อม (Cold-start latency) ซึ่งส่งผลกระทบโดยตรงต่อเวลาตอบสนองของผู้ใช้งานปลายทาง

ในมุมมองของการขยายขนาดระดับองค์กร โค้ดเบสแบบ WET สร้างอุปสรรคอย่างรุนแรงต่อการรับพนักงานใหม่ วิศวกรซอฟต์แวร์ใหม่ต้องอ่านโค้ดที่ซ้ำซ้อนหลายพันบรรทัดเพื่อทำความเข้าใจการทำงานร่วมกันหลักของระบบ เมื่อโค้ดเบสขาด Abstraction ที่สะอาดและการนำโมดูลกลับมาใช้ใหม่ ประสิทธิภาพการทำงานของนักพัฒนาย่อมลดลงตามขนาดทีมที่ขยายขึ้น ซึ่งตรงข้ามกับกฎของบรูคส์ (Brooks' Law) โดยตรง และส่งผลให้งบประมาณการส่งมอบซอฟต์แวร์พุ่งสูงขึ้น

อันตรายของการออกแบบระบบที่ซับซ้อนเกินจำเป็น: เมื่อใดที่ไม่ควรยึดหลัก DRY

แม้ว่าการทำซ้ำโค้ดจะนำมาซึ่งหนี้ทางเทคนิค (Technical debt) แต่การยึดติดกับหลักการ DRY อย่างเคร่งครัดและไร้การวิเคราะห์อาจส่งผลเสียต่อสถาปัตยกรรมระบบที่ร้ายแรงยิ่งกว่า ดังที่ Sandi Metz ผู้เชี่ยวชาญด้านซอฟต์แวร์ชื่อดังเคยกล่าวไว้อย่างลึกซึ้งว่า"การมีโค้ดซ้ำยังมีต้นทุนต่ำกว่าการสร้าง Abstraction ที่ผิดพลาดมากนัก"การบีบบังคับให้แนวคิดทางธุรกิจที่แตกต่างกันมารวมอยู่ใน Abstraction เดียวกันสร้างการผูกมัดที่แน่นหนา (Tight coupling) ลดทอนความเป็นโมดูล และเปลี่ยนการแก้ไขโค้ดตามปกติให้กลายเป็นงานวิศวกรรมที่มีความเสี่ยงสูง

การออกแบบที่ซับซ้อนเกินจำเป็น (Over-engineering) เกิดขึ้นเมื่อนักพัฒนาเข้าใจว่าความคล้ายคลึงกันของโครงสร้างภายนอกคือความเป็นส่วนงานทางธุรกิจ (Domain) เดียวกัน หากสองโมดูลมีหน้าตาคล้ายกันในวันนี้ แต่ทำหน้าที่ในบริบทการทำงานที่แตกต่างกันและจะพัฒนาแยกออกจากกันในอนาคต การบังคับให้โมดูลเหล่านี้ใช้ Abstraction ร่วมกันจะนำไปสู่การพึ่งพาซึ่งกันและกันที่เปราะบาง เมื่อส่วนงานหนึ่งต้องการการปรับเปลี่ยน Abstraction ที่ใช้ร่วมกันก็จะต้องถูกเสริมด้วย Flag เงื่อนไข, พารามิเตอร์แบบบูลีน และตรรกะการแยกสาขาที่ซับซ้อน จนกลายเป็นโค้ดเบสที่ไม่สามารถดูแลรักษาได้

# The Wrong Abstraction: Forcing divergent domain requirements into one shared function
def generate_user_greeting(user_type: str, name: str, balance: float, is_vip: bool, locale: str) -> str:
    # Highly coupled, brittle abstraction full of conditional branches
    if locale == "es":
        greeting = f"Hola, {name}"
    else:
        greeting = f"Hello, {name}"
        
    if user_type == "customer":
        if is_vip:
            greeting += " (VIP Customer)"
        greeting += f" - Balance: ${balance:.2f}"
    elif user_type == "vendor":
        greeting += f" - Outstanding Invoice: ${balance:.2f}"
    elif user_type == "internal_admin":
        greeting += " - [Admin Console Access]"
        
    return greeting

ตัวอย่างข้างต้นแสดงให้เห็นถึง Abstraction ที่บกพร่อง แทนที่จะคงตัวจัดรูปแบบ (Formatter) ขนาดเล็กที่มีความกระชับและสอดคล้องตามหน้าที่สำหรับผู้ใช้แต่ละกลุ่ม นักพัฒนากลับรวบรวมคำทักทายทั้งหมดไว้ในฟังก์ชันเดียว เมื่อความต้องการทางธุรกิจเปลี่ยนแปลงไป ฟังก์ชันนี้จะยิ่งทวีความซับซ้อนมากขึ้นเรื่อยๆ ด้วย Flag เงื่อนไขเพิ่มเติม และในที่สุดจะกลายเป็นต้นตอสำคัญของข้อผิดพลาดบนระบบจริง (Production defects)

ความเสี่ยงของการสร้าง Abstraction ก่อนเวลาอันควร

การสร้าง Abstraction ก่อนเวลาอันควรเกิดขึ้นเมื่อวิศวกรออกแบบเฟรมเวิร์กทั่วไปขึ้นมา โดยที่ยังไม่เข้าใจความผันแปรของโดเมนเบื้องลึกอย่างถ่องแท้ การกระทำนี้ละเมิดแนวทาง Clean Code ขั้นพื้นฐาน โดยเฉพาะหลักการ KISS (Keep It Simple, Stupid) และหลักการ YAGNI (You Aren't Gonna Need It).

เมื่อนักพัฒนาสร้าง Abstraction ทั่วไปขึ้นมาจากสมมติฐานเกี่ยวกับความต้องการในอนาคต พวกเขากำลังผูกมัดสถาปัตยกรรมไว้กับสมมติฐานที่ตายตัว และเมื่อเกิดความต้องการทางธุรกิจจริงที่เบี่ยงเบนไปจากแบบจำลองที่คาดเดาไว้ Abstraction นั้นก็จะพังทลายลง การปรับแก้ Abstraction ที่ผิดพลาดซึ่งฝังรากลึกในจุดเรียกใช้งาน (Call sites) หลายร้อยแห่งต้องใช้ความพยายามทางวิศวกรรมมากกว่าการรวมบล็อกโค้ดซ้ำๆ ธรรมดาเข้าด้วยกันอย่างมาก

การผูกมัดแน่นหนา (Tight Coupling) กับการทำซ้ำโค้ดที่ยอมรับได้

สถาปนิกซอฟต์แวร์ต้องแยกแยะให้ออกระหว่างการทำซ้ำโดยบังเอิญ (Accidental duplication)และการทำซ้ำที่จำเป็น (Essential duplication)ในสถาปัตยกรรมไมโครเซอร์วิส การแชร์ Database Entity หรือตรรกะของโดเมนข้าม Bounded Context ถือเป็นการละเมิดขอบเขตการแยกตัวของบริการ (Service isolation) หากไมโครเซอร์วิส A (Billing) และไมโครเซอร์วิส B (Shipping) ใช้ไลบรารีCustomerร่วมกัน การอัปเดตแอตทริบิวต์เฉพาะของระบบ Billing จะบังคับให้ต้อง Deploy ระบบ Shipping ไปพร้อมกัน ซึ่งเป็นการทำลายความสามารถในการ Deploy ระบบอย่างเป็นอิสระ (Autonomous deployment)

ในระบบไมโครเซอร์วิสแบบกระจายศูนย์ การทำซ้ำออบเจกต์ถ่ายโอนข้อมูล (DTO) แบบง่าย หรืออินเทอร์เฟซการตรวจสอบความถูกต้องข้าม Bounded Context ถือเป็นข้อแลกเปลี่ยนทางสถาปัตยกรรมที่ตั้งใจทำ โดยให้ความสำคัญกับการผูกมัดอย่างหลวมๆ (Loose coupling) และความสามารถในการ Deploy อย่างอิสระ มากกว่าการยึดหลัก DRY ในเชิงไวยากรณ์อย่างเคร่งครัดและสัมบูรณ์

+-----------------------------------------------------------------------------------+
|                        STRATEGIC DRY DECISION MATRIX                              |
+--------------------------+-----------------------+--------------------------------+
| Factor                   | Favor Duplication     | Favor Shared Abstraction (DRY) |
+--------------------------+-----------------------+--------------------------------+
| Domain Evolution         | Divergent trajectories| Unified, identical rules       |
| Service Boundaries       | Across Bounded Context| Within single module/service   |
| Code Volatility          | High (Rapid flux)     | Low (Stable core logic)        |
| Abstraction Complexity   | Complex parameter branching | Simple, pure interface   |
| Organizational Ownership | Separate teams        | Single agile team              |
+--------------------------+-----------------------+--------------------------------+

การนำกฎสามข้อ (Rule of Three) มาปรับใช้ในการ Refactoring

เพื่อหลีกเลี่ยงการสร้าง Abstraction ก่อนเวลาอันควร สถาปนิกซอฟต์แวร์ที่มีประสบการณ์จะพึ่งพากฎสามข้อ (Rule of Three). โดยหลักการแบบศึกษาสำนึก (heuristic) นี้ระบุว่า ควรเขียนโค้ดอย่างเรียบง่ายที่สุดในครั้งแรก ทำซ้ำโค้ดโดยไม่ต้องสร้างแอ็บสแตรกชันในครั้งที่สอง และทำการรีแฟกเตอร์ (refactor) ไปสู่แอ็บสแตรกชันที่ใช้ร่วมกันเมื่อพบการนำไปใช้งานที่เหมือนกันเป็นครั้งที่สามเท่านั้น

  1. ครั้งแรกที่พบ:เขียนโค้ดที่เรียบง่ายที่สุดเท่าที่จะเป็นไปได้ซึ่งตอบโจทย์ความต้องการเชิงฟังก์ชันเฉพาะหน้าและผ่านการทดสอบยูนิตเทสต์ (unit test) ทั้งหมด

  2. ครั้งที่สองที่พบ:ทำซ้ำลอจิกเดิม การมีโค้ดสองจุดจะช่วยให้ทีมวิศวกรรมสามารถสังเกตว่าการนำไปใช้งานทั้งสองส่วนมีการเปลี่ยนแปลงพัฒนาอย่างเป็นอิสระต่อกันอย่างไร โดยไม่สร้างการเชื่อมต่อผูกมัดก่อนเวลาอันควร (premature coupling)

  3. ครั้งที่สามที่พบ:หากจำเป็นต้องมีการนำไปใช้งานแบบเดียวกันเป็นครั้งที่สามและปัจจัยขับเคลื่อนเชิงโดเมน (domain drivers) เบื้องหลังยังคงเหมือนเดิม ให้ดึงลอจิกที่ใช้ร่วมกันนั้นออกมาเป็นโมดูลมาตรฐานที่กำหนดพารามิเตอร์ได้ (parameterized module)

การสร้างวัฒนธรรมทางวิศวกรรมตามหลักการ DRY

การรักษาฐานโค้ดให้เป็นไปตามหลักการ DRY นั้นอาศัยมากกว่าวินัยส่วนบุคคลของนักพัฒนา เพราะจำเป็นต้องมีวัฒนธรรมองค์กรที่สนับสนุนด้วยการกำกับดูแลแบบอัตโนมัติ มาตรฐานการตรวจทานโค้ด (code review) ที่ครอบคลุม และเกตวัดคุณภาพในการผสานรวมโค้ดอย่างต่อเนื่อง (Continuous Integration: CI) หากปราศจากเครื่องมืออัตโนมัติ หนี้ทางเทคนิค (technical debt) ก็จะสะสมขึ้นอย่างหลีกเลี่ยงไม่ได้เมื่อทีมต้องเร่งส่งมอบงานให้ทันกำหนดการปล่อยระบบที่เข้มงวด

ผู้นำด้านวิศวกรรมต้องนำเครื่องมือ linting อัตโนมัติและเครื่องมือวิเคราะห์โค้ดแบบสแตติกมาใช้งาน เพื่อสแกน pull request หาความซ้ำซ้อนเชิงโครงสร้างก่อนที่จะรวมโค้ดเข้าสู่บรันช์หลัก (trunk branch) เมื่อการตรวจสอบความซ้ำซ้อนของโค้ดกลายเป็นขั้นตอนอัตโนมัติในไปป์ไลน์การส่งมอบอย่างต่อเนื่อง นักพัฒนาย่อมได้รับผลตอบรับที่ตรงไปตรงมาและทันที ซึ่งช่วยลดภาระของผู้ตรวจทานโค้ดที่เป็นมนุษย์

การตรวจทานโค้ดที่มีประสิทธิภาพและการเขียนโปรแกรมคู่กัน

การตรวจทานโค้ดควรทำหน้าที่เป็นจุดตรวจสอบความสอดคล้องทางสถาปัตยกรรมมากกว่าเป็นเพียงการตรวจรูปแบบการจัดหน้าโค้ดเพียงผิวเผิน ผู้ตรวจทานควรประเมิน pull request ที่เข้ามาผ่านมุมมองของการเป็นตัวแทนความรู้ (knowledge representation):

  • pull request นี้มีการเพิ่มกฎเชิงโดเมนที่มีอยู่แล้วในอีกโมดูลหนึ่งหรือไม่?

  • ผู้เขียนได้แก้ไขแอ็บสแตรกชันที่ใช้ร่วมกันในลักษณะที่สร้างการผูกมัดแบบมีเงื่อนไข (conditional coupling) ต่อส่วนอื่นที่นำไปใช้งานหรือไม่?

  • ฟังก์ชันยูทิลิตี (utility function) ที่สร้างขึ้นใหม่ควรได้รับการยกฐานะไปเป็นแพ็กเกจที่ใช้ร่วมกันภายในหรือไม่?

การเขียนโปรแกรมคู่กัน (pair programming) และการเป็นเจ้าของโค้ดร่วมกัน (collective code ownership) ช่วยเร่งกระบวนการนี้ เมื่อวิศวกรทำงานร่วมกันในหลากหลายโดเมนภายในรีโพซิทอรีอย่างสม่ำเสมอ พวกเขาจะเกิดความตระหนักรู้ในภาพรวมที่กว้างขึ้นเกี่ยวกับยูทิลิตี ไลบรารี และรูปแบบการออกแบบ (design patterns) ที่มีอยู่เดิม ความรู้ที่แบ่งปันร่วมกันนี้จะช่วยป้องกันไม่ให้นักพัฒนาเผลอสร้างฟังก์ชันยูทิลิตีซ้ำซ้อนกับสิ่งที่มีอยู่แล้วในรีโพซิทอรีอื่นๆ ขององค์กรโดยไม่ได้ตั้งใจ

การใช้งานเครื่องมือวิเคราะห์แบบสแตติกและ Linting

ทีมวิศวกรรมซอฟต์แวร์ยุคใหม่พึ่งพาเครื่องมือวิเคราะห์แบบสแตติกเพื่อตรวจจับความซ้ำซ้อนของโค้ด ความซับซ้อนที่พุ่งสูงขึ้น และการเบี่ยงเบนทางสถาปัตยกรรม (architectural drift) อย่างต่อเนื่อง เครื่องมือเหล่านี้จะวิเคราะห์โครงสร้าง Abstract Syntax Trees (AST) เพื่อชี้เป้าโค้ดที่คัดลอกกันทั้งในเชิงไวยากรณ์ (syntactic) และเชิงความหมาย (semantic) ทั่วทั้งฐานโค้ดขนาดใหญ่

  • SonarQube / SonarCloud:แพลตฟอร์มการวิเคราะห์แบบสแตติกมาตรฐานอุตสาหกรรมที่ติดตามเปอร์เซ็นต์ความซ้ำซ้อนของโค้ด ความซับซ้อนเชิงไซโคลเมติก (cyclomatic complexity) และจุดเสี่ยงด้านความปลอดภัย (security hotspots) ในหลากหลายภาษาโปรแกรม ทีมสามารถตั้งค่า "Quality Gates" เพื่อบล็อก pull request โดยอัตโนมัติ หากโค้ดที่ซ้ำซ้อนเกินเกณฑ์ที่องค์กรกำหนดไว้ (โดยทั่วไปอยู่ที่ 3–5%)

  • jscpd (Copy/Paste Detector):เครื่องมือตรวจจับโค้ดที่ซ้ำซ้อนแบบน้ำหนักเบาและไม่ขึ้นกับภาษาโปรแกรม (language-agnostic) ซึ่งได้รับการปรับให้เหมาะสมกับไปป์ไลน์ CI เพื่อระบุบล็อกโค้ดที่ซ้ำซ้อนได้อย่างรวดเร็วในหลายสิบภาษา

  • ESLint / PMD / Checkstyle:เครื่องมือ static linter ที่บังคับใช้กฎความเป็นโมดูล ระบุโค้ดสำเร็จรูป (boilerplate) ที่ซ้ำซาก และป้องกันไม่ให้นักพัฒนานำรูปแบบสถาปัตยกรรมที่ไม่อนุญาตเข้ามาใช้

# GitHub Actions Workflow: Automated Duplicate Code Quality Gate
name: Continuous Code Quality & Duplication Check

on:
  pull_request:
    branches: [ main, develop ]

jobs:
  code-duplication-scan:
    name: Scan for Duplicate Code
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v4

      - name: Setup Node.js Environment
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Duplication Scanner
        run: npm install -g jscpd

      - name: Execute Duplicate Code Detection
        run: |
          jscpd ./src --threshold 3.0 --reporters console,badge --ignore "**/tests/**,**/generated/**"

การกำหนดค่าเวิร์กโฟลว์ CI ข้างต้นแสดงให้เห็นว่าองค์กรสามารถบังคับใช้มาตรฐาน DRY โดยอัตโนมัติได้อย่างไร การตั้งค่าเกณฑ์ความซ้ำซ้อนที่เข้มงวด (เช่น 3.0%) และเรียกใช้การสแกนในทุก pull request จะช่วยป้องกันไม่ให้โค้ดที่ซ้ำซ้อนหลุดไปยังสภาพแวดล้อมจริง (production) ในขณะที่ยกเว้น test fixtures และสกีมาที่สร้างขึ้นโดยอัตโนมัติอย่างเจาะจง ซึ่งความซ้ำซ้อนในส่วนเหล่านั้นเป็นที่ยอมรับได้ในเชิงโครงสร้าง

คำถามที่พบบ่อย

S1: วัตถุประสงค์หลักของหลักการ DRY คืออะไร?
C1: วัตถุประสงค์หลักของหลักการ DRY (Don't Repeat Yourself) คือการทำให้แน่ใจว่าองค์ความรู้ทางธุรกิจ (business knowledge) แต่ละส่วนที่แตกต่างกันมีตัวแทนที่เชื่อถือได้เพียงแห่งเดียวและเป็นหนึ่งเดียวภายในระบบ ซึ่งจะช่วยขจัดความคลาดเคลื่อนของตรรกะ ลดความซับซ้อนในการบำรุงรักษา และลดหนี้ทางเทคนิค (technical debt) เมื่อความต้องการเปลี่ยนแปลงไป

S2: หลักการ DRY ใช้ได้กับโค้ดฝั่งแบ็กเอนด์เท่านั้นหรือไม่?
C2: ไม่ใช่ หลักการ DRY ครอบคลุมทั่วทั้งสแต็กวิศวกรรมซอฟต์แวร์ ซึ่งรวมถึงตรรกะฝั่งฟรอนต์เอนด์, สกีมาฐานข้อมูล, ข้อกำหนด API (API contracts), การกำหนดค่าการปรับใช้ (deployment configurations) และเอกสารทางเทคนิค ทุกส่วนที่มีการนำเสนอกฎเกณฑ์ทางธุรกิจต้องปฏิบัติตามหลักการแหล่งความจริงแหล่งเดียว (single-source-of-truth) เพื่อป้องกันความคลาดเคลื่อนในการซิงโครไนซ์

S3: หลักการ DRY มีความเกี่ยวข้องกับหลักการ KISS และ YAGNI อย่างไร?
C3: DRY, KISS (Keep It Simple, Stupid) และ YAGNI (You Aren't Gonna Need It) เป็นหลักการที่ส่งเสริมซึ่งกันและกัน ในขณะที่ DRY ช่วยลดความซ้ำซ้อนขององค์ความรู้ KISS และ YAGNI จะช่วยป้องกันการออกแบบที่ซับซ้อนเกินจำเป็น (over-engineering) โดยปรามไม่ให้นักพัฒนาสร้าง abstraction ที่ซับซ้อนและก่อนเวลาอันควร ก่อนที่จะเกิดความซ้ำซ้อนขึ้นจริง

S4: ความแตกต่างระหว่างความซ้ำซ้อนที่จำเป็น (essential duplication) และความซ้ำซ้อนโดยบังเอิญ (accidental duplication) คืออะไร?
C4: ความซ้ำซ้อนที่จำเป็น (Essential duplication) เกิดขึ้นเมื่อโค้ดที่เหมือนกันแสดงถึงองค์ความรู้ทางธุรกิจเดียวกันและต้องเปลี่ยนแปลงไปพร้อมกันเสมอ ส่วนความซ้ำซ้อนโดยบังเอิญ (Accidental duplication) เกิดขึ้นเมื่อบล็อกโค้ดแยกกันสองบล็อกมีไวยากรณ์คล้ายกันโดยบังเอิญ แต่แสดงถึงแนวคิดของโดเมนที่แตกต่างกันซึ่งจะพัฒนาแยกจากกันอย่างอิสระ

S5: กฎสามส่วน (Rule of Three) ในการปรับโครงสร้างโค้ด (refactoring) คืออะไร?
C5: Rule of Three คือหลักการคร่าวๆ ทางวิศวกรรม (heuristic) ที่ระบุว่าโค้ดควรถูกเขียนขึ้นหนึ่งครั้ง ทำซ้ำเมื่อมีกรณีการใช้งานครั้งที่สองเกิดขึ้น และจะถูกรีแฟกเตอร์เป็น abstraction ที่ใช้ร่วมกันก็ต่อเมื่อพบการนำไปใช้งานที่เหมือนกันเป็นครั้งที่สามเท่านั้น ซึ่งจะช่วยป้องกันการสร้าง abstraction ที่เปราะบางและก่อนเวลาอันควร

S6: ความซ้ำซ้อนของโค้ดส่งผลกระทบต่อความปลอดภัยของซอฟต์แวร์อย่างไร?
C6: ความซ้ำซ้อนของโค้ดสร้างมาตรการความปลอดภัยที่ไม่สอดคล้องกันทั่วทั้งแอปพลิเคชัน หากช่องโหว่ได้รับการแพตช์ในรูทีนที่ซ้ำกันจุดหนึ่งแต่มองข้ามในอีกจุดหนึ่ง เอนด์พอยต์ที่เปิดเผยอยู่ก็จะยังคงเสี่ยงต่อการถูกโจมตี ซึ่งบั่นทอนการตรวจสอบความถูกต้อง การตรวจสอบข้อมูลขาเข้า (input validation) และมาตรฐานการเข้ารหัสลับ (cryptographic standards)

S7: หลักการ DRY สามารถนำไปใช้ข้ามไมโครเซอร์วิสได้หรือไม่?
C7: ใช้ได้ แต่ต้องนำไปใช้อย่างระมัดระวัง แม้ว่าฟังก์ชันยูทิลิตีและข้อกำหนด API (API contracts) จะสามารถแชร์ผ่านแพ็กเกจภายในได้ แต่การแชร์เอนทิตีโดเมนหลัก (core domain entities) ข้ามไมโครเซอร์วิสสามารถสร้างความเชื่อมโยงทางสถาปัตยกรรมที่แน่นหนาเกินไป (tight architectural coupling) ซึ่งส่งผลเสียต่อความเป็นอิสระของเซอร์วิสและการปรับใช้ (deployment) แบบอิสระ

S8: เครื่องมือ static analysis ใดบ้างที่สามารถตรวจจับความซ้ำซ้อนของโค้ดใน CI pipeline ได้?
C8: เครื่องมือวิเคราะห์โค้ดแบบสแตติกยอดนิยมสำหรับการระบุความซ้ำซ้อน ได้แก่ SonarQube, jscpd (Copy/Paste Detector), PMD และ CodeClimate เครื่องมือเหล่านี้จะตรวจสอบโครงสร้างไวยากรณ์ (syntax trees) ในหลากหลายภาษาโปรแกรม และสามารถสั่งให้การบิลด์ continuous integration ล้มเหลวได้หากเกินเกณฑ์ขีดจำกัดความซ้ำซ้อนที่กำหนด

คำถามที่พบบ่อย

วัตถุประสงค์หลักของหลักการ DRY คืออะไร?

วัตถุประสงค์หลักของหลักการ DRY (Don't Repeat Yourself) คือการทำให้แน่ใจว่าองค์ความรู้ทางธุรกิจ (business knowledge) แต่ละส่วนที่แตกต่างกันมีตัวแทนที่เชื่อถือได้เพียงแห่งเดียวและเป็นหนึ่งเดียวภายในระบบ ซึ่งจะช่วยขจัดความคลาดเคลื่อนของตรรกะ ลดความซับซ้อนในการบำรุงรักษา และลดหนี้ทางเทคนิค (technical debt) เมื่อความต้องการเปลี่ยนแปลงไป

หลักการ DRY ใช้ได้กับโค้ดฝั่งแบ็กเอนด์เท่านั้นหรือไม่?

ไม่ใช่ หลักการ DRY ครอบคลุมทั่วทั้งสแต็กวิศวกรรมซอฟต์แวร์ ซึ่งรวมถึงตรรกะฝั่งฟรอนต์เอนด์, สกีมาฐานข้อมูล, ข้อกำหนด API (API contracts), การกำหนดค่าการปรับใช้ (deployment configurations) และเอกสารทางเทคนิค ทุกส่วนที่มีการนำเสนอกฎเกณฑ์ทางธุรกิจต้องปฏิบัติตามหลักการแหล่งความจริงแหล่งเดียว (single-source-of-truth) เพื่อป้องกันความคลาดเคลื่อนในการซิงโครไนซ์

หลักการ DRY มีความเกี่ยวข้องกับหลักการ KISS และ YAGNI อย่างไร?

DRY, KISS (Keep It Simple, Stupid) และ YAGNI (You Aren't Gonna Need It) เป็นหลักการที่ส่งเสริมซึ่งกันและกัน ในขณะที่ DRY ช่วยลดความซ้ำซ้อนขององค์ความรู้ KISS และ YAGNI จะช่วยป้องกันการออกแบบที่ซับซ้อนเกินจำเป็น (over-engineering) โดยปรามไม่ให้นักพัฒนาสร้าง abstraction ที่ซับซ้อนและก่อนเวลาอันควร ก่อนที่จะเกิดความซ้ำซ้อนขึ้นจริง

ความแตกต่างระหว่างความซ้ำซ้อนที่จำเป็น (essential duplication) และความซ้ำซ้อนโดยบังเอิญ (accidental duplication) คืออะไร?

ความซ้ำซ้อนที่จำเป็น (Essential duplication) เกิดขึ้นเมื่อโค้ดที่เหมือนกันแสดงถึงองค์ความรู้ทางธุรกิจเดียวกันและต้องเปลี่ยนแปลงไปพร้อมกันเสมอ ส่วนความซ้ำซ้อนโดยบังเอิญ (Accidental duplication) เกิดขึ้นเมื่อบล็อกโค้ดแยกกันสองบล็อกมีไวยากรณ์คล้ายกันโดยบังเอิญ แต่แสดงถึงแนวคิดของโดเมนที่แตกต่างกันซึ่งจะพัฒนาแยกจากกันอย่างอิสระ

กฎสามส่วน (Rule of Three) ในการปรับโครงสร้างโค้ด (refactoring) คืออะไร?

Rule of Three คือหลักการคร่าวๆ ทางวิศวกรรม (heuristic) ที่ระบุว่าโค้ดควรถูกเขียนขึ้นหนึ่งครั้ง ทำซ้ำเมื่อมีกรณีการใช้งานครั้งที่สองเกิดขึ้น และจะถูกรีแฟกเตอร์เป็น abstraction ที่ใช้ร่วมกันก็ต่อเมื่อพบการนำไปใช้งานที่เหมือนกันเป็นครั้งที่สามเท่านั้น ซึ่งจะช่วยป้องกันการสร้าง abstraction ที่เปราะบางและก่อนเวลาอันควร

ความซ้ำซ้อนของโค้ดส่งผลกระทบต่อความปลอดภัยของซอฟต์แวร์อย่างไร?

ความซ้ำซ้อนของโค้ดสร้างมาตรการความปลอดภัยที่ไม่สอดคล้องกันทั่วทั้งแอปพลิเคชัน หากช่องโหว่ได้รับการแพตช์ในรูทีนที่ซ้ำกันจุดหนึ่งแต่มองข้ามในอีกจุดหนึ่ง เอนด์พอยต์ที่เปิดเผยอยู่ก็จะยังคงเสี่ยงต่อการถูกโจมตี ซึ่งบั่นทอนการตรวจสอบความถูกต้อง การตรวจสอบข้อมูลขาเข้า (input validation) และมาตรฐานการเข้ารหัสลับ (cryptographic standards)

หลักการ DRY สามารถนำไปใช้ข้ามไมโครเซอร์วิสได้หรือไม่?

ใช้ได้ แต่ต้องนำไปใช้อย่างระมัดระวัง แม้ว่าฟังก์ชันยูทิลิตีและข้อกำหนด API (API contracts) จะสามารถแชร์ผ่านแพ็กเกจภายในได้ แต่การแชร์เอนทิตีโดเมนหลัก (core domain entities) ข้ามไมโครเซอร์วิสสามารถสร้างความเชื่อมโยงทางสถาปัตยกรรมที่แน่นหนาเกินไป (tight architectural coupling) ซึ่งส่งผลเสียต่อความเป็นอิสระของเซอร์วิสและการปรับใช้ (deployment) แบบอิสระ

เครื่องมือ static analysis ใดบ้างที่สามารถตรวจจับความซ้ำซ้อนของโค้ดใน CI pipeline ได้?

เครื่องมือวิเคราะห์โค้ดแบบสแตติกยอดนิยมสำหรับการระบุความซ้ำซ้อน ได้แก่ SonarQube, jscpd (Copy/Paste Detector), PMD และ CodeClimate เครื่องมือเหล่านี้จะตรวจสอบโครงสร้างไวยากรณ์ (syntax trees) ในหลากหลายภาษาโปรแกรม และสามารถสั่งให้การบิลด์ continuous integration ล้มเหลวได้หากเกินเกณฑ์ขีดจำกัดความซ้ำซ้อนที่กำหนด

ขั้นตอนสุดท้าย

มาวางแผนโปรเจกต์ดิจิทัลของคุณวันนี้

เปลี่ยนความต้องการด้านเว็บไซต์ ซอฟต์แวร์ E-Commerce แอปมือถือ การเชื่อมต่อ SEO หรือ GEO ให้เป็นขอบเขตงานที่ชัดเจน

หลักการ DRY: วิธีหลีกเลี่ยงการเขียนโค้ดซ้ำซ้อน | Webizm