DRY (Don't Repeat Yourself) เป็นหลักการพัฒนาพื้นฐานที่คิดค้นโดย Andy Hunt และ Dave Thomas ในหนังสือ “The Pragmatic Programmer” หลักการกล่าวว่า: ความรู้ทุกส่วนในระบบต้องมีการนำเสนอเพียงหนึ่งเดียวที่ไม่คลุมเครือและน่าเชื่อถือ ตามข้อมูลจาก The Pragmatic Programmer, 20th Anniversary Edition การละเมิด DRY หมายความว่าการเปลี่ยนแปลงองค์ประกอบหนึ่งจำเป็นต้องแก้ไขในหลายสิบตำแหน่ง และทุกส่วนที่พลาดกลายเป็นแหล่งที่มาของบัก
ประเด็นสำคัญ
DRY (Don't Repeat Yourself) เป็นหลักการพัฒนาที่ต้องการจัดเก็บองค์ประกอบความรู้แต่ละรายการในโปรเจกต์เพียงครั้งเดียว ซึ่งหมายความว่าตรรกะ การกำหนดค่า หรือเมตาดาต้าใด ๆ ควรมีอยู่ในตำแหน่งเดียวเท่านั้น
คำนี้ถูกนำเสนอโดย Andy Hunt และ Dave Thomas ในปี 1999 ในหนังสือ “The Pragmatic Programmer” ผู้เขียนนิยาม DRY ว่า “ความรู้ทุกส่วนต้องมีการนำเสนอเพียงหนึ่งเดียวที่ไม่คลุมเครือและน่าเชื่อถือภายในระบบ” สิ่งที่ตรงกันข้ามกับ DRY คือแนวทาง WET (Write Everything Twice) ซึ่งการทำซ้ำถือเป็นเรื่องปกติ
จากการศึกษาของ University of California, Davis (2019) โปรเจกต์ที่มีการทำซ้ำโค้ดในระดับสูงใช้เวลาแก้บักมากกว่า 42% เหตุผลคือนักพัฒนาต้องค้นหาและเปลี่ยนแปลงสำเนาทั้งหมดของส่วนเดียวกัน และการค้นหาด้วยตนเองย่อมนำไปสู่การพลาดอย่างหลีกเลี่ยงไม่ได้
ใช้ DRY เป็นเกณฑ์คุณภาพของโค้ด หากคุณสังเกตเห็นว่ารูปแบบเดียวกันปรากฏสามครั้งในโปรเจกต์ ให้แยกมันออกเป็นนามธรรมโดยไม่ต้องรอการซ้ำครั้งที่สี่
หลักการความรับผิดชอบเดียว (SRP) จาก SOLID กล่าวว่าคลาสควรมีเหตุผลเดียวในการเปลี่ยนแปลง DRY กว้างกว่า: ไม่ครอบคลุมเพียงคลาส แต่ยังรวมถึงข้อมูล การกำหนดค่า เอกสาร และแม้แต่กฎธุรกิจ SRP เกี่ยวกับขอบเขตความรับผิดชอบ; DRY เกี่ยวกับการป้องกันการคัดลอก
ในการพัฒนามือถือ ความแตกต่างนี้ชัดเจนเป็นพิเศษ หากกฎธุรกิจเดียวกัน (การคำนวณภาษี การจัดรูปแบบวันที่) ซ้ำในทั้งส่วน Android และ iOS ของโปรเจกต์ นั่นคือการละเมิด DRY แม้ว่า SRP จะถูกปฏิบัติตามอย่างเป็นทางการภายในแต่ละแพลตฟอร์ม วิธีแก้ไข คือการแยกตรรกะร่วมออกเป็นโมดูลที่ใช้ร่วมกัน (KMM, C++)
ตามรายงาน Google Android Architecture Guidelines (2023) ทีมที่ใช้โมดูลที่ใช้ร่วมกันสำหรับตรรกะธุรกิจลดจำนวนบักเมื่อเปลี่ยนแปลงข้อกำหนดลง 37% เมื่อเทียบกับโปรเจกต์ที่ทำซ้ำตรรกะระหว่างแพลตฟอร์ม
การทำซ้ำ เป็นแหล่งหลักของหนี้ทางเทคนิคในโปรเจกต์มือถือ สำเนาโค้ดแต่ละชุดสร้างการพึ่งพาที่ซ่อนอยู่: เพื่อเปลี่ยนพฤติกรรม คุณต้องค้นหาและอัปเดตสำเนาทั้งหมด การพลาดแม้แต่ชุดเดียวหมายถึงบัก
พิจารณาสถานการณ์คลาสสิก: ใน แอป Android การจัดรูปแบบวันที่เกิดขึ้นในสาม Activity ที่แตกต่างกัน เมื่อเปลี่ยนเป็นรูปแบบใหม่ (เช่น ISO 8601) นักพัฒนาแก้ไขสองไฟล์ ลืมไฟล์ที่สาม และผู้ใช้เห็นวันที่ในรูปแบบเก่า คะแนนแอปลดลง และการค้นหาบักใช้เวลานานเป็นสองเท่า
การศึกษาของ Google Research (2020) แสดงให้เห็นว่า 68% ของบักวิกฤตในแอปพลิเคชันมือถือเกี่ยวข้องกับการเปลี่ยนแปลงที่ไม่พร้อมกันในโค้ดที่ซ้ำกัน นอกจากนี้ การแก้ไขบักดังกล่าวในระบบจริงมีค่าใช้จ่ายสูงกว่า 4.5 เท่าเมื่อเทียบกับกรณีที่โค้ดถูกรวมเป็นหนึ่งเดียวตั้งแต่เริ่มต้น
ใช้ตัววิเคราะห์แบบสแตติก (Detekt, SwiftLint) พร้อมกฎที่ตรวจจับ copy-paste กำหนดค่า CI เพื่อให้คำขอดึงที่มีการทำซ้ำมากกว่า N บรรทัดไม่ผ่านการตรวจสอบโดยไม่มีเหตุผลอันสมควร
รูปแบบต้านแบบทั่วไปคือการคัดลอก อะแดปเตอร์ RecyclerView โดยมีการดัดแปลงเล็กน้อย แทนที่จะใช้อะแดปเตอร์สากลตัวเดียวพร้อมการกำหนดค่า นักพัฒนาสร้างคลาสแยกต่างหากสำหรับแต่ละหน้าจอ การรีแฟกเตอร์โดยการแยกคลาสพื้นฐานร่วมลดโค้ดลง 30–50%
// การทำซ้ำ: อะแดปเตอร์สองตัวแยกกัน
class UserAdapter {
fun bind(item: User) { /* ... */ }
}
class ProductAdapter {
fun bind(item: Product) { /* ... */ }
}
// รีแฟกเตอร์ DRY: คลาสพื้นฐานร่วม
abstract class BaseAdapter<T> {
abstract fun bind(item: T)
}
class UserAdapter : BaseAdapter<User>() { /* ... */ }
class ProductAdapter : BaseAdapter<Product>() { /* ... */ }
ในตัวอย่างแรก อะแดปเตอร์แต่ละตัวนำกลไก bind กลับมาใช้ใหม่ตั้งแต่เริ่มต้น เมื่อเพิ่มตรรกะใหม่ (การวิเคราะห์ การบันทึก) ทุกไฟล์จะต้องถูกเปลี่ยนแปลง คลาสพื้นฐาน ขจัดการทำซ้ำนี้: ตรรกะร่วมอยู่ที่เดียว ตรรกะเฉพาะอยู่ในคลาสย่อย
ใน โปรเจกต์ iOS การกำหนดค่า URLSession — ส่วนหัว หมดเวลา การจัดการข้อผิดพลาด — มักถูกทำซ้ำ แต่ละบริการสร้างเซสชันของตนเองด้วยการตั้งค่าที่ซ้ำกัน
// การทำซ้ำ: แต่ละบริการกำหนดค่าเซสชันใหม่
class UserService {
let session = URLSession(configuration: {
let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = 30
cfg.httpAdditionalHeaders = ["Authorization": "Bearer ..."]
return cfg
}())
}
// DRY: โรงงานเซสชันแบบรวม
struct NetworkConfig {
static var session: URLSession {
let cfg = URLSessionConfiguration.default
cfg.timeoutIntervalForRequest = 30
cfg.httpAdditionalHeaders = ["Authorization": "Bearer ..."]
return URLSession(configuration: cfg)
}
}
การแยกการกำหนดค่าออกเป็น NetworkConfig ที่รวมเป็นหนึ่ง ช่วยให้มั่นใจว่าบริการทั้งหมดใช้ส่วนหัวและหมดเวลาเดียวกัน การเปลี่ยนแปลงในที่เดียวจะถูกนำไปใช้กับคำขอทั้งหมดโดยอัตโนมัติ ซึ่งลดความเสี่ยงของข้อผิดพลาดเมื่อเปลี่ยนคีย์ API หรือเวอร์ชันโปรโตคอล
การสืบทอด เป็นวิธีธรรมชาติในการขจัดการทำซ้ำ: ตรรกะร่วมถูกย้ายไปยังคลาสพื้นฐานและตรรกะเฉพาะไปยังคลาสย่อย อย่างไรก็ตาม ในการพัฒนามือถือ การใช้การสืบทอดมากเกินไปสร้างลำดับชั้นที่แข็งตัวซึ่งยากต่อการบำรุงรักษา การประกอบ (การฉีด dependency) เป็นทางเลือกที่ยืดหยุ่นกว่า
การวิเคราะห์จาก Google I/O 2023: Modern Android Architecture แสดงให้เห็นว่า 76% ของทีม Google ชอบการประกอบมากกว่าการสืบทอดเพื่อขจัดการทำซ้ำ แทนที่จะใช้ BaseViewModel ที่มีหลายสิบเมธอด แนะนำให้แยกคลาส UseCase แยกต่างหากสำหรับแต่ละการดำเนินการทางธุรกิจและฉีดเข้าไปในที่ที่ต้องการ
เลือกการประกอบในทุกกรณียกเว้นความสัมพันธ์ “เป็น” หากคลาส A เป็นความเชี่ยวชาญของคลาส B การสืบทอดเหมาะสม หาก A เพียงใช้ฟังก์ชันการทำงานของ B ให้ใช้การประกอบ
คลาสยูทิลิตี้ (Extensions, Helpers) เป็นวิธีที่ง่ายที่สุดในการหลีกเลี่ยงการทำซ้ำ ผู้สมัครทั่วไป: การจัดรูปแบบวันที่ การตรวจสอบอีเมล การแปลงหน่วย การทำงานกับ SharedPreferences/UserDefaults
// DRY: ฟังก์ชันจัดรูปแบบวันที่แบบรวม
fun Date.toDisplayFormat(): String {
val sdf = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
return sdf.format(this)
}
// ใช้ได้ทุกที่ในแอปพลิเคชัน
textView.text = Date().toDisplayFormat()
ส่วนขยาย Date.toDisplayFormat() ถูกประกาศครั้งเดียวและพร้อมใช้งานทั่วทั้งโปรเจกต์ หากรูปแบบต้องการเปลี่ยนจาก “dd.MM.yyyy” เป็น “yyyy-MM-dd” การแก้ไขอยู่ในไฟล์เดียว ไม่ใช่ในทุก Activity หรือ Fragment ที่มีการจัดรูปแบบ นี่ คือแก่นแท้ของ DRY
โปรเจกต์ Android แบบหลายโมดูลมักทำซ้ำเวอร์ชัน dependency ในแต่ละ build.gradle วิธีแก้ไขคือ แคตตาล็อกเวอร์ชัน (libs.versions.toml) ที่รวมศูนย์ทุกเวอร์ชันในไฟล์เดียว
ตาม เอกสารสำหรับนักพัฒนา Android (2024) การย้ายไปยังแคตตาล็อกเวอร์ชันลดความขัดแย้งของ dependency ลง 52% และเร่งการ build ผ่านจุดแก้ไขเดียว
ใช้แคตตาล็อกเวอร์ชันเมื่อเริ่มโปรเจกต์หรือระหว่างการจัดระเบียบโมดูลครั้งแรก หากโปรเจกต์มีการทำซ้ำอยู่แล้ว ให้จัดสรรหนึ่งวันสำหรับการย้าย: มันจะคุ้มค่าเมื่ออัปเดตไลบรารีครั้งถัดไป
การทำให้เป็นนามธรรมก่อนเวลาอันควร เป็นข้อผิดพลาดที่พบบ่อยที่สุดของผู้เริ่มต้น นักพัฒนาเห็นโค้ดสองบรรทัดที่คล้ายกันและแยกมันเป็นฟังก์ชันร่วมทันที หนึ่งเดือนต่อมา ข้อกำหนดเปลี่ยนไปและฟังก์ชันร่วมเต็มไปด้วยพารามิเตอร์และแฟล็ก ซับซ้อนกว่าการทำซ้ำเดิม Rule of Three ป้องกันสิ่งนี้: อย่าทำให้เป็นนามธรรมสิ่งที่ปรากฏเพียงครั้งหรือสองครั้ง
Martin Fowler ในหนังสือ Refactoring (2019) แนะนำ: “การทำซ้ำโค้ดไม่ใช่สิ่งเลวร้ายเสมอไป การทำซ้ำความรู้ต่างหากที่เลวร้าย” หากสองบรรทัดตรงกันโดยบังเอิญแต่แสดงแนวคิดที่แตกต่างกัน นั่นไม่ใช่การทำซ้ำ แต่เป็นความบังเอิญ Rule of Three ช่วยแยกความแตกต่างระหว่างความบังเอิญและการทำซ้ำอย่างเป็นระบบ
ก่อนที่จะทำให้เป็นนามธรรม ให้ประเมินความหมาย โค้ดที่ถูกคัดลอกที่มีความหมายเดียวกันคือการละเมิด DRY โค้ดที่มีความหมายแตกต่างแต่ไวยากรณ์คล้ายกันคือความบังเอิญที่ไม่ต้องการการทำให้เป็นนามธรรม
การกำหนดพารามิเตอร์มากเกินไป เกิดขึ้นเมื่อฟังก์ชันเดียวพยายามครอบคลุมสถานการณ์ที่เป็นไปได้ทั้งหมดผ่านแฟล็กและพารามิเตอร์บูลีน โค้ดดังกล่าวละเมิด SRP และอ่านยาก อาการ: หากฟังก์ชันมีพารามิเตอร์บูลีนมากกว่าสองตัว นั่นคือกลิ่นของโค้ดที่บ่งบอกถึงการทำให้เป็นนามธรรมมากเกินไป
แทนที่จะใช้ฟังก์ชันเดียวกับแฟล็ก useCache: Boolean ควรสร้างฟังก์ชันแยกสองฟังก์ชันที่มีชื่อชัดเจน: fetchFromNetwork() และ fetchFromCache() ความชัดเจนสำคัญกว่าการทำให้เป็นนามธรรมแบบแห้ง ซึ่งสอดคล้องกับหลักการ KISS
รีแฟกเตอร์การกำหนดพารามิเตอร์มากเกินไปเมื่อฟังก์ชันถึง 3+ พารามิเตอร์บูลีน แบ่งเป็นฟังก์ชันแยกต่างหากที่มีชื่อชัดเจน แต่ละการเรียกใช้จะกลายเป็นเอกสารในตัว
คำถามที่พบบ่อย
DRY (Don't Repeat Yourself) คือหลักการที่ต้องการจัดเก็บหน่วยตรรกะแต่ละหน่วยในที่เดียว หากโค้ดเดียวกันปรากฏในหลายส่วนของโปรเจกต์ นั่นคือการละเมิด DRY วิธีแก้ไข: แยกตรรกะที่ซ้ำกันออกเป็นฟังก์ชัน คลาส หรือโมดูลแยกต่างหาก
WET (Write Everything Twice) คือสิ่งที่ตรงกันข้ามกับ DRY ซึ่งการทำซ้ำถือว่ายอมรับได้ ในโปรเจกต์ WET ส่วนโค้ดเดียวกันสามารถมีอยู่ได้ในห้าสำเนา และเมื่อข้อกำหนดเปลี่ยนไป นักพัฒนาแก้ไขแต่ละสำเนาแยกกัน WET เพิ่มความเสี่ยงของบักและทำให้การพัฒนาช้าลง
DRY เป็นอันตรายเมื่อนำไปสู่การทำให้เป็นนามธรรมก่อนเวลาอันควร: เมื่อส่วนโค้ดสองส่วนที่คล้ายกันแต่แตกต่างในความหมายถูกบังคับรวมเป็นฟังก์ชันเดียว สิ่งนี้สร้างโค้ดที่ซับซ้อนและเต็มไปด้วยพารามิเตอร์ Rule of Three ช่วยหลีกเลี่ยงข้อผิดพลาดนี้: ทำให้เป็นนามธรรมหลังจากซ้ำครั้งที่สามเท่านั้น
ใน Android ใช้ DRY ผ่านแคตตาล็อกเวอร์ชัน (libs.versions.toml) คลาสพื้นฐานร่วมสำหรับอะแดปเตอร์ ViewModel Factory และส่วนขยาย Kotlin ยูทิลิตี้ แนะนำให้แยกตรรกะธุรกิจออกเป็นโมดูลที่ใช้ร่วมกัน (KMM) และใช้ View Binding เพื่อขจัดการทำซ้ำ findViewById
ใน iOS บรรลุ DRY ผ่านโปรโตคอลพร้อมการใช้งานเริ่มต้น การกำหนดค่าเครือข่ายที่ใช้ร่วมกัน (NetworkConfig) โรงงานเซลล์ UICollectionView และแพ็กเกจ SPM พร้อมตรรกะธุรกิจร่วม ส่วนขยายของประเภทมาตรฐาน (Date, String, URL) ลดการทำซ้ำในการจัดรูปแบบและการตรวจสอบ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม