คัดลอกวาง (copy-paste) คือการปฏิบัติในการคัดลอกชิ้นส่วนโค้ดจากที่หนึ่งไปยังอีกที่หนึ่งโดยไม่ปรับให้เข้ากับบริบทใหม่ ส่วนใหญ่แล้ว นักพัฒนาจะคัดลอกบล็อกจากโมดูลที่มีอยู่ แก้ไขเพียงเล็กน้อย แล้ววางลงในโมดูลใหม่ — พร้อมด้วยบั๊ก ความคิดเห็นที่ล้าสมัย และการพึ่งพาที่ไม่จำเป็น ตาม TIOBE Code Quality Survey (2025) โปรเจกต์ที่มีระดับการคัดลอกวางสูงมีข้อบกพร่องมากกว่าถึงสามเท่าต่อพันบรรทัดของโค้ดเมื่อเทียบกับโปรเจกต์ที่มีนามธรรมแบบรวมศูนย์ การซ้ำซ้อนของโค้ด เป็นแหล่งหลักของหนี้ทางเทคนิค: แต่ละสำเนาต้องการการบำรุงรักษาแยกต่างหาก และการแก้ไขบั๊กในที่เดียวไม่รับประกันว่าบั๊กอื่นจะได้รับการแก้ไข
ประเด็นสำคัญ
การคัดลอกวาง (การเขียนโปรแกรมแบบคัดลอกวาง) คือการย้ายโค้ดที่มีอยู่ไปยังที่ใหม่โดยมีการเปลี่ยนแปลงเล็กน้อยหรือไม่มีเลย คำนี้ใช้ในความหมายเชิงดูถูก: มันบอกเป็นนัยว่านักพัฒนาไม่ได้ออกแบบวิธีแก้ปัญหา แต่กำลังคัดลอกบล็อกสำเร็จรูปโดยอัตโนมัติ ซึ่งมักจะไม่เข้าใจอย่างถ่องแท้ว่ามันทำงานอย่างไร
การคัดลอกวางมีสองประเภท: โดยเจตนา (intentional) และ โดยบังเอิญ (accidental) โดยเจตนา — เมื่อนักพัฒนาจงใจคัดลอกโค้ดโดยมีแผนจะปรับโครงสร้างในภายหลัง (แต่แผนมักถูกละทิ้ง) โดยบังเอิญ — เมื่อการซ้ำซ้อนเกิดขึ้นโดยไม่มีใครสังเกต เช่น นักพัฒนาสองคนเขียนตรรกะเดียวกันอย่างอิสระสำหรับหน้าจอที่แตกต่างกัน
ตามรายงาน SonarQube State of Clean Code (2025) โค้ดที่ซ้ำซ้อนคิดเป็นเฉลี่ย 12–18 เปอร์เซ็นต์ของปริมาณโค้ดทั้งหมดในโปรเจกต์เชิงพาณิชย์ ในขณะเดียวกัน ค่าใช้จ่ายในการแก้ไขบั๊กในโค้ดที่ซ้ำซ้อนสูงกว่าโค้ดที่มีการนำไปใช้เดี่ยวถึง 2.5 เท่า เนื่องจากนักพัฒนาต้องค้นหาและแก้ไขสำเนาทั้งหมด
เครื่องมือหลักในการต่อต้านการคัดลอกวางคือหลักการ DRY (Don't Repeat Yourself) อย่างไรก็ตาม การทำให้ DRY เป็นสิ่งสัมบูรณ์ก็อันตรายเช่นกัน บางครั้งการคัดลอกก็เป็นสิ่งที่สมเหตุสมผลเมื่อสำเนาสองชุดต้องวิวัฒนาการอย่างเป็นอิสระต่อกัน สิ่งสำคัญคือต้องแยกแยะระหว่าง “การซ้ำซ้อนโดยบังเอิญ” (ซึ่งต้องกำจัด) และ “การซ้ำซ้อนที่จำเป็น” (ซึ่งต้องบันทึกเป็นเอกสาร)
อันตรายแรกและสำคัญที่สุดคือ การแพร่กระจายของบั๊ก หากโค้ดต้นฉบับมีข้อบกพร่อง มันจะถูกคัดลอกไปยังตำแหน่งใหม่ทั้งหมดพร้อมกับโค้ด เมื่อพบข้อบกพร่องและแก้ไขในโมดูลต้นฉบับ สำเนาจะยังคงไม่ได้รับการแก้ไข นักพัฒนาอาจไม่รู้ด้วยซ้ำว่าบั๊กนั้นมีอยู่ในห้าไฟล์ที่แตกต่างกัน
อันตรายที่สองคือ วิวัฒนาการที่ไม่เท่าเทียม สำเนาสองชุดของอัลกอริทึมเดียวกันสะสมการแก้ไขที่แตกต่างกันเมื่อเวลาผ่านไป สำเนาหนึ่งเพิ่มการตรวจสอบค่าขอบเขต อีกสำเนาหนึ่งเปลี่ยนรูปแบบเอาต์พุต หลังจากนั้นไม่กี่เดือน ก็ไม่สามารถบอกได้ว่ารุ่นใด “ถูกต้อง” และโปรเจกต์สูญเสียความสอดคล้องของพฤติกรรม
อันตรายที่สามคือ ปริมาณการทดสอบที่เพิ่มขึ้น แต่ละอินสแตนซ์ของการคัดลอกวางต้องการการทดสอบของตัวเอง หากตรรกะทั่วไปถูกแยกออกเป็นฟังก์ชันเดียว ก็สามารถครอบคลุมด้วยชุดทดสอบชุดเดียวและนำกลับมาใช้ใหม่ได้ ด้วยการซ้ำซ้อน แต่ละสำเนาจะต้องถูกทดสอบแยกกัน ซึ่งจะเพิ่มเวลาเรียกใช้ CI และขนาดของฐานการทดสอบที่ต้องบำรุงรักษาเป็นทวีคูณ
อันตรายที่สี่คือ ภาพลวงตาของประสิทธิภาพการทำงาน การคัดลอกวางสร้างความรู้สึกเท็จเกี่ยวกับความรวดเร็ว: นักพัฒนาวางโค้ดอย่างรวดเร็วและเห็นว่าหน้าจอทำงาน แต่ “ความรวดเร็ว” นี้กลายเป็นหนี้ทางเทคนิคที่ต้องชำระคืนพร้อมดอกเบี้ยเมื่อพบข้อบกพร่องในบล็อกที่ซ้ำซ้อนหรือจำเป็นต้องเปลี่ยนแปลงตรรกะทางธุรกิจ
การเข้าใจสาเหตุของการคัดลอกวางช่วยกำหนดมาตรการป้องกันที่เหมาะสม ส่วนใหญ่นักพัฒนาคัดลอกโค้ดไม่ใช่เพราะความเกียจคร้าน แต่เป็นเพราะแรงกดดันจากกำหนดเวลา การขาดความรู้ หรือสถาปัตยกรรมที่ไม่สะดวก
สาเหตุแรกคือ กำหนดเวลา เมื่อต้องสร้างหน้าจอภายในสองวันและมีหน้าจอที่คล้ายกันอยู่แล้ว นักพัฒนาจะคัดลอกหน้าจอนั้นทั้งหมดและเปลี่ยนแปลงเฉพาะสิ่งที่ผู้ใช้เห็น ไม่มีเวลาสำหรับการปรับโครงสร้างและแยกคอมโพเนนต์ที่ใช้ร่วมกัน — ลูกค้าต้องการผลลัพธ์ ผลลัพธ์คือหน้าจอที่สองปรากฏขึ้นพร้อมโค้ดที่ใช้ร่วมกัน 80 เปอร์เซ็นต์ แต่มีประวัติการเปลี่ยนแปลงที่เป็นอิสระ
สาเหตุที่สองคือ การขาดนามธรรมแบบรวมศูนย์ หากโปรเจกต์ไม่มีคอมโพเนนต์ที่ใช้ร่วมกันสำหรับงานทั่วไป (เช่น หน้าจอรายการแบบดึงเพื่อรีเฟรช) นักพัฒนาแต่ละคนจะเขียนการนำไปใช้ของตนเองหรือคัดลอกของเพื่อนร่วมงาน การตัดสินใจทางสถาปัตยกรรมที่ทำเมื่อเริ่มต้นโปรเจกต์ส่งผลโดยตรงต่อปริมาณการคัดลอกวางในอนาคต
สาเหตุที่สามคือ ความกลัวที่จะทำลายโค้ดที่ทำงานได้ นักพัฒนารู้ว่าโมดูลที่มีอยู่ทำงานได้ การปรับโครงสร้างเพื่อแยกโค้ดที่ใช้ร่วมกันอาจส่งผลกระทบต่อฟังก์ชันการทำงานที่มีอยู่ หากความครอบคลุมของการทดสอบต่ำ ความเสี่ยงของการพังจะมีมากกว่าประโยชน์ที่รับรู้ได้ของการปรับโครงสร้าง และนักพัฒนาเลือกเส้นทางที่ปลอดภัย — การคัดลอก
จัดการที่สาเหตุ ไม่ใช่อาการ การลดกำหนดเวลาและการนำการตรวจสอบโค้ดมาใช้จะไม่แก้ปัญหาหากโปรเจกต์ขาดฐานสถาปัตยกรรมที่แข็งแกร่ง ลงทุนเวลาในการสร้างคอมโพเนนต์ที่นำกลับมาใช้ใหม่ได้ตั้งแต่ระยะแรก — นั่นเป็นวิธีเดียวที่จะลดสิ่งล่อใจของการคัดลอกวางในอนาคต
การตรวจจับการคัดลอกวางดำเนินการโดยโปรแกรมวิเคราะห์อัตโนมัติที่เปรียบเทียบชิ้นส่วนโค้ดและระบุการตรงกันที่สูงกว่าเกณฑ์ที่กำหนด เครื่องมือที่ดีที่สุดทำงานในระดับ AST (แผนภูมิไวยากรณ์นามธรรม) และละเว้นการจัดรูปแบบ ชื่อตัวแปร และความคิดเห็น
PMD CPD (Copy-Paste Detector) เป็นเครื่องมือที่พบมากที่สุดสำหรับ Java, Kotlin, Swift, JavaScript, Python และ C++ CPD วิเคราะห์โทเค็นของโค้ดต้นฉบับและค้นหาการซ้ำซ้อนที่ยาวกว่าจำนวนโทเค็นขั้นต่ำที่กำหนด (ค่าเริ่มต้น 100) การกำหนดค่าเกณฑ์เป็นกุญแจสำคัญในการได้ผลลัพธ์ที่มีคุณภาพ: เกณฑ์ต่ำเกินไปจะให้ผลบวกปลอมจำนวนมาก (รูปแบบทั่วไปเช่น import) เกณฑ์สูงเกินไปจะพลาดการซ้ำซ้อนจริง
plugins {
id 'pmd'
}
pmd {
toolVersion = '7.0.0'
ruleSetFiles = files("pmd-rules.xml")
}
tasks.register('cpd') {
doLast {
exec {
workingDir = projectDir
commandLine 'cpd',
'--minimum-tokens', '75',
'--language', 'kotlin',
'--files', 'src/main/kotlin',
'--format', 'xml',
'--failOnViolation', 'true'
}
}
}
SonarQube ฝังตัวตรวจจับการซ้ำซ้อนโดยตรงใน Quality Gate กฎ Duplicated Blocks (%) แสดงเปอร์เซ็นต์ของโค้ดที่ซ้ำซ้อน เกณฑ์ 5 เปอร์เซ็นต์ถือว่าสุขภาพดีสำหรับโปรเจกต์เชิงพาณิชย์ การเกินจะบล็อกการเลื่อนไปยังสาขาที่เผยแพร่ SonarQube ยังจัดกลุ่มการซ้ำซ้อนตามประเภท: การตรงกันแบบแน่นอนและการคัดลอกเชิงโครงสร้าง (พร้อมตัวระบุที่เปลี่ยนชื่อ)
สำหรับ JavaScript และ TypeScript การซ้ำซ้อนจะถูกตรวจจับโดยใช้ ESLint กับปลั๊กอิน eslint-plugin-sonarjs (กฎ no-duplicate-string) และยูทิลิตี้ jscpd ซึ่งรองรับภาษา 150+ ภาษา jscpd สะดวกเป็นพิเศษสำหรับ monorepo: มันค้นหาการซ้ำซ้อนระหว่างแพ็คเกจ ไม่ใช่แค่ภายในโมดูลเดียว
การปรับโครงสร้างการคัดลอกวางสรุปได้เป็นหลักการเดียว: แยกส่วนที่เหมือนกัน และกำหนดพารามิเตอร์ให้กับความแตกต่าง เทคนิคเฉพาะขึ้นอยู่กับขอบเขตของการซ้ำซ้อนและบริบท
กรณีที่ง่ายที่สุดคือ การซ้ำซ้อนภายในคลาสเดียว (เช่น สองเมธอดที่มีตรรกะเดียวกันแต่ประเภทต่างกัน) วิธีแก้ไขคือการทำให้ทั่วไปด้วยเจเนอริกหรือใช้เมธอดซ้ำกับพารามิเตอร์ประเภท หากการซ้ำซ้อนครอบคลุมหลายคลาส — ให้แยกโค้ดทั่วไปออกเป็นคลาสยูทิลิตี้หรือฟังก์ชันส่วนขยาย
กรณีที่ซับซ้อนกว่าคือ การซ้ำซ้อนในระดับหน้าจอหรือโมดูล ในกรณีนี้ การแยกฟังก์ชันเพียงอย่างเดียวไม่ช่วยเพราะโครงสร้าง UI ตรรกะวงจรชีวิต และการผูกข้อมูลทั้งหมดถูกทำซ้ำ วิธีแก้ไขคือสร้างคลาสพื้นฐานหน้าจอร่วมหรือคอมโพเนนต์มุมมองแบบผสม และส่งต่อความแตกต่างผ่านพารามิเตอร์หรือโปรโตคอล
// before - two copies of the same UITableViewController
class UserListController: UITableViewController {
private let viewModel = UserListViewModel()
// 40 lines of code
}
class ProductListController: UITableViewController {
private let viewModel = ProductListViewModel()
// same 40 lines but with Product instead of User
}
// after - generic base class shared
class ListViewController<T: ListViewModel>: UITableViewController {
let viewModel: T
// 40 lines of code - once only
init(viewModel: T) {
self.viewModel = viewModel
super.init(style: .plain)
}
}
กรณีที่ซับซ้อนที่สุดคือ การซ้ำซ้อนระหว่างไมโครเซอร์วิสหรือไลบรารี การแยกโค้ดที่ใช้ร่วมกันอาจนำไปสู่การพึ่งพาแบบวงจรหรือการเชื่อมต่อที่ไม่สมเหตุสมผล ในกรณีเช่นนี้ การคัดลอกวางอาจเป็นการตัดสินใจอย่างมีสติ: สองทีมดูแลบริการอิสระ และไลบรารีที่ใช้ร่วมกันสร้างปัญหามากกว่าที่จะแก้ไข สิ่งสำคัญคือการบันทึกการตัดสินใจดังกล่าวเป็นเอกสารและตรวจสอบเป็นประจำว่าสำเนาได้แยกออกจากกันมากพอที่จะพิสูจน์การรวมเป็นหนึ่งเดียวกันหรือไม่
การป้องกันการคัดลอกวางมีประสิทธิภาพมากกว่าการปรับโครงสร้างโค้ดที่ซ้ำซ้อนอยู่แล้ว มาตรการป้องกันหลักอยู่ที่การจัดระเบียบกระบวนการพัฒนา ไม่ใช่ในเทคโนโลยี
มาตรการแรกคือ การตรวจสอบโค้ดที่เน้นการซ้ำซ้อน รายการตรวจสอบการตรวจสอบควรรวมรายการ: “PR นี้มีโค้ดที่มีอยู่แล้วในโปรเจกต์หรือไม่?” หากผู้ตรวจสอบเห็นการคัดลอกวาง — เขาจะบล็อกการรวมจนกว่าจะแยกคอมโพเนนต์ที่ใช้ร่วมกัน ข้อกำหนดนี้ควรเป็นส่วนหนึ่งของ Definition of Done ของทีม
มาตรการที่สองคือ ไลบรารีคอมโพเนนต์ที่ใช้ร่วมกัน ทุกรูปแบบ UI ที่ปรากฏบนหน้าจอสองหน้าจอขึ้นไปควรถูกแยกออกเป็นโมดูลร่วม สร้างโมดูลที่ใช้ร่วมกันในโปรเจกต์และทำให้เป็นจุดเริ่มต้นที่จำเป็นสำหรับคอมโพเนนต์ UI ทั้งหมด หากไม่มีคอมโพเนนต์ — ให้สร้างก่อน แล้วจึงใช้บนหน้าจอ
มาตรการที่สามคือ ระบบอัตโนมัติใน CI/CD เพิ่มขั้นตอนการตรวจสอบโค้ดที่ซ้ำซ้อนในไปป์ไลน์ (PMD CPD, jscpd, SonarQube) การเกินเกณฑ์จะทำให้การสร้างล้มเหลว นักพัฒนาไม่สามารถรวม PR ที่เพิ่มอัตราส่วนการคัดลอกวางเกินระดับที่อนุญาตได้ ซึ่งจะย้ายความรับผิดชอบจากการตรวจสอบโค้ดไปยังระบบอัตโนมัติและรับประกันว่าจะไม่มีการซ้ำซ้อนใดถูกมองข้าม
ส่งเสริมวัฒนธรรม “การนำไปใช้หนึ่งครั้ง — หนึ่งที่” หากคุณเห็นโอกาสในการนำกลับมาใช้ใหม่ — อย่าเลื่อนการปรับโครงสร้างออกไป การคัดลอกวางที่ถูกทิ้งไว้ “ทีหลัง” จะทวีคูณและกลายเป็นหนี้ทางเทคนิคที่ควบคุมไม่ได้
คำถามที่พบบ่อย
ไม่ มีสถานการณ์ของการซ้ำซ้อนอย่างมีสติ: ไมโครเซอร์วิสที่แตกต่างกันซึ่งต้องวิวัฒนาการอย่างอิสระ โค้ดที่คัดลอกสำหรับการทดลองที่มีแผนลบ; DTO แม่แบบสำหรับ API เวอร์ชันต่างๆ สิ่งสำคัญคือการบันทึกเหตุผลเป็นเอกสารและกำหนด กำหนดเวลาตรวจสอบ สำหรับการปรับโครงสร้าง
การคัดลอกวางคือเมื่อโค้ดสองส่วนทำสิ่งเดียวกันแต่ไม่มีนามธรรมร่วมกัน การนำกลับมาใช้ใหม่อย่างมีสุขภาพดีคือเมื่อโค้ดทั่วไปถูกแยกออกเป็นฟังก์ชัน คลาส หรือโมดูล และความแตกต่างถูกกำหนดพารามิเตอร์ หากการเปลี่ยนตรรกะต้องการการแก้ไขใน สามแห่งขึ้นไป — นั่นคือการคัดลอกวาง
PMD CPD รองรับ Swift และ Objective-C สำหรับ Xcode มีปลั๊กอินเช่น SwiftCop และตัวตรวจจับการซ้ำซ้อนในตัวของ AppCode SonarQube ยังวิเคราะห์โปรเจกต์ Swift โดยแสดงบล็อกที่ซ้ำซ้อนโดยตรงในคำขอดึง
สร้างตั๋วทางเทคนิคสำหรับการปรับโครงสร้างสำเนาขนาดใหญ่แต่ละชุด กำหนดลำดับความสำคัญ: หน้าจอที่เปลี่ยนแปลงบ่อยก่อน หน้าจอที่เสถียรทีหลัง สำหรับ PR ใหม่แต่ละรายการที่แตะโค้ดที่ซ้ำซ้อน ให้จัดสรร 15–20 เปอร์เซ็นต์ ของเวลาสำหรับการรวมอย่างค่อยเป็นค่อยไป
ใช่ ผู้ช่วย AI สมัยใหม่ (GitHub Copilot, Codeium) สามารถวิเคราะห์บริบทและแนะนำการแยกโค้ดที่ใช้ร่วมกันเมื่อตรวจพบรูปแบบที่ซ้ำซ้อน อย่างไรก็ตาม พวกมันไม่ได้แทนที่โปรแกรมวิเคราะห์อัตโนมัติ — ใช้ Copilot สำหรับ การป้องกัน และ CPD / SonarQube สำหรับการตรวจจับ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม