หลักการและวิธีการทางสถาปัตยกรรม — คือชุดของกฎและคำแนะนำที่ช่วยนักพัฒนาในการสร้างโค้ดที่บำรุงรักษาได้ ปรับขนาดได้ และเข้าใจง่าย ตาม TIOBE Index (2025) โปรเจกต์ที่ปฏิบัติตามหลักการทางสถาปัตยกรรมมีข้อบกพร่องร้ายแรงน้อยกว่า 40% ในบทความนี้ เราจะกล่าวถึง SOLID, GRASP, DRY, KISS, YAGNI และหลักการอื่นๆ รวมถึงพูดถึงหนี้ทางเทคนิคและ Code Smell
ประเด็นสำคัญ
หลักการทางสถาปัตยกรรม — คือรากฐานของโค้ดที่มีคุณภาพ SOLID เป็นคำย่อที่ Robert Martin («ลุงบ๊อบ») นำเสนอ ซึ่งอธิบายหลักการออกแบบเชิงวัตถุห้าประการ การปฏิบัติตาม SOLID ทำให้โค้ดยืดหยุ่นมากขึ้น ทดสอบได้ง่ายขึ้น และทนทานต่อการเปลี่ยนแปลง การละเมิดหลักการทางสถาปัตยกรรมเป็นสาเหตุหลักของหนี้ทางเทคนิค
มาดูแต่ละหลักการกัน Single Responsibility Principle (SRP) — แต่ละคลาสควรมีเหตุผลในการเปลี่ยนแปลงเพียงอย่างเดียว Open/Closed Principle (OCP) — คลาสควรเปิดสำหรับการขยายแต่ปิดสำหรับการแก้ไข Liskov Substitution Principle (LSP) — ออบเจ็กต์ของชนิดย่อยควรสามารถแทนที่ออบเจ็กต์ของชนิดฐานได้โดยไม่ทำให้ตรรกะเสียหาย Interface Segregation Principle (ISP) — อินเทอร์เฟซเฉพาะทางหลายอันดีกว่าอินเทอร์เฟซทั่วไปอันเดียว Dependency Inversion Principle (DIP) — พึ่งพาสิ่งที่เป็นนามธรรม ไม่ใช่การนำไปใช้ที่เป็นรูปธรรม
ตามการวิเคราะห์ของ SonarQube (2025) การละเมิดหลักการ SOLID เกิดขึ้นใน 68% ของโปรเจกต์เชิงพาณิชย์ ปัญหาที่พบบ่อยที่สุดคือการละเมิด SRP (35%) และ ISP (22%) ที่ IT Sectr เรานำ SOLID ไปใช้ในขั้นตอนการตรวจสอบสถาปัตยกรรม — ซึ่งช่วยระบุปัญหาก่อนที่มันจะกลายเป็นหนี้ทางเทคนิค
SRP (หลักการความรับผิดชอบเดียว) — สำคัญที่สุดและในขณะเดียวกันก็เป็นหลักการ SOLID ที่ถูกละเมิดบ่อยที่สุด กล่าวว่า: คลาสควรมีเหตุผลในการเปลี่ยนแปลงเพียงอย่างเดียว หากคลาสทำมากเกินไป จะยากต่อการทดสอบ แก้ไข และทำความเข้าใจ
การละเมิดทั่วไปคือคลาสที่ประมวลผลข้อมูล บันทึกลงฐานข้อมูล และส่งอีเมลแจ้งเตือนในเวลาเดียวกัน ตัวอย่างด้านล่างแสดงการละเมิด SRP ใน Kotlin และวิธีแก้ไข
// การละเมิด SRP — คลาสทำสามสิ่งที่แตกต่างกัน
class UserService {
fun registerUser(email: String, name: String) {
// 1. การตรวจสอบข้อมูล
if (!email.contains("@")) throw IllegalArgumentException("Invalid email")
// 2. บันทึกลงฐานข้อมูล
val user = User(email, name)
database.save(user)
// 3. ส่งการแจ้งเตือน
emailService.sendWelcomeEmail(email, name)
}
}
// การแก้ไข — แบ่งเป็นสามคลาส
class UserRegistrationService {
fun register(email: String, name: String) {
UserValidator().validate(email)
val user = User(email, name)
UserRepository().save(user)
NotificationService().sendWelcome(user)
}
}
ในเวอร์ชันที่แก้ไขแล้ว แต่ละคลาสรับผิดชอบงานของตนเอง: UserValidator — รับผิดชอบการตรวจสอบ, UserRepository — การบันทึก, NotificationService — การแจ้งเตือน สิ่งนี้ทำให้โค้ดสามารถทดสอบได้และนำกลับมาใช้ใหม่ได้ — คุณสามารถเปลี่ยนการนำฐานข้อมูลไปใช้ได้โดยไม่ต้องเปลี่ยนตรรกะการตรวจสอบ
GRASP (General Responsibility Assignment Software Patterns) — หลักการทางสถาปัตยกรรมเก้าประการสำหรับการกระจายความรับผิดชอบระหว่างออบเจ็กต์ อธิบายโดย Craig Larman ต่างจาก SOLID ตรงที่ GRASP ตอบคำถาม «คลาสใดควรมีเมธอดนี้?» รูปแบบหลัก: Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, Protected Variations
Law of Demeter (LoD, หลักการเชื่อมต่อน้อยที่สุด) — กฎง่ายๆ: ออบเจ็กต์ควรสื่อสารกับเพื่อนบ้านที่อยู่ติดกันเท่านั้น ไม่ควรเขียน a.getB().getC().doSomething() — สิ่งนี้สร้างการเชื่อมต่อที่แน่นแฟ้นระหว่างคลาส LoD ช่วยเพิ่มความสามารถในการนำกลับมาใช้ใหม่และทำให้การทดสอบง่ายขึ้น
ที่ IT Sectr เราตรวจสอบการปฏิบัติตาม LoD ในระหว่างการตรวจสอบโค้ด หากเมธอด «ผ่าน» ออบเจ็กต์สามชิ้นขึ้นไป นั่นเป็นสัญญาณว่าสถาปัตยกรรมต้องการการทำให้ง่ายขึ้น การละเมิด LoD เป็นหนึ่งใน Code Smell ที่พบบ่อยที่สุดในโปรเจกต์ขนาดใหญ่
DRY (Don't Repeat Yourself), KISS (Keep It Simple, Stupid) และ YAGNI (You Ain't Gonna Need It) — หลักการทางสถาปัตยกรรมพื้นฐานสามประการที่นักพัฒนาทุกคนรู้จัก แม้จะเรียบง่าย แต่การละเมิดก็เกิดขึ้นอยู่เสมอ
DRY — อย่าทำซ้ำโค้ด หากตรรกะเดียวกันปรากฏในสองที่ ให้แยกมันเป็นเมธอดหรือคลาสร่วม การทำซ้ำเป็นแหล่งหลักของบั๊ก: การแก้ไขในที่หนึ่งถูกลืมไม่นำไปใช้ในอีกที่หนึ่ง DRY ไม่ได้หมายความว่าคุณไม่สามารถมีโค้ดที่คล้ายกันได้ — สิ่งสำคัญคือตรรกะทางธุรกิจจะไม่ถูกทำซ้ำ
KISS — ยิ่งง่ายยิ่งดี โซลูชันที่ซับซ้อนที่มีนามธรรมและการสืบทอดมากมายมักเกินจำเป็น เริ่มต้นด้วยโซลูชันที่เรียบง่ายและทำให้ซับซ้อนขึ้นเมื่อจำเป็นเท่านั้น YAGNI — อย่าเขียนโค้ดสำหรับฟังก์ชันการทำงานที่อาจจำเป็น «สักวันหนึ่งในภายหลัง» สิ่งนี้นำไปสู่การพองตัวของฐานโค้ดและเพิ่มความซับซ้อนในการบำรุงรักษา
DRY — ไม่ใช่แค่การไม่มีก็อปปี้-เพสต์เท่านั้น เป็นหลักการที่ว่าความรู้หรือตรรกะทุกส่วนควรมีการแสดงที่เป็นหนึ่งเดียวและไม่คลุมเครือในระบบ การทำซ้ำอาจเป็นแบบชัดแจ้ง (โค้ดที่ถูกคัดลอก) และโดยนัย (ตรรกะเดียวกันในเลเยอร์ต่างกัน)
ที่ IT Sectr เราใช้เมตริกการวิเคราะห์โค้ดเพื่อตรวจจับการทำซ้ำ เครื่องมืออย่าง SonarQube และ Detekt แสดงเปอร์เซ็นต์ของโค้ดที่ซ้ำกัน ค่าที่สูงกว่า 5% เป็นเหตุผลสำหรับการปรับโครงสร้างใหม่ อย่างไรก็ตาม สิ่งสำคัญคือต้องจำไว้ว่า: ไม่ควรบรรลุ DRY โดยแลกกับนามธรรมที่ไม่ถูกต้อง — บางครั้งการปล่อยให้โค้ดสองชิ้นที่คล้ายกันไว้เหมือนเดิมก็ดีกว่าหากการรวมมันเข้าด้วยกันจะทำให้เข้าใจยากขึ้น
Separation of Concerns (SoC) — หลักการทางสถาปัตยกรรมที่ระบบถูกแบ่งออกเป็นส่วนอิสระ (concerns) แต่ละส่วนแก้ไขงานของตัวเอง ตัวอย่างคลาสสิกคือการแบ่งเป็นเลเยอร์: การนำเสนอ ตรรกะทางธุรกิจ การเข้าถึงข้อมูล แต่ละเลเยอร์ขึ้นอยู่กับเลเยอร์ด้านล่างเท่านั้น
Modularity (โมดูลาริตี) — ระดับที่ระบบสามารถแบ่งออกเป็นโมดูลได้ โมดูลคือกลุ่มของคลาสที่เกี่ยวข้องกันอย่างมีตรรกะพร้อมอินเทอร์เฟซที่กำหนดไว้อย่างดี โมดูลควรเชื่อมต่อกันอย่างหลวมๆ (low coupling) และเกาะเกี่ยวกันอย่างแน่นหนา (high cohesion)
Cohesion (การเกาะเกี่ยวกัน) — การวัดว่าองค์ประกอบภายในโมดูลเดียวกันเกี่ยวข้องกันมากน้อยเพียงใด การเกาะเกี่ยวกันสูงเป็นสิ่งที่ดี: คลาสทำสิ่งหนึ่งและทำได้ดี Low coupling (การเชื่อมต่อต่ำ) — การวัดว่าโมดูลเป็นอิสระจากกันมากน้อยเพียงใด การเชื่อมต่อต่ำเป็นสิ่งที่ดี: การเปลี่ยนแปลงในโมดูลหนึ่งไม่ทำให้โมดูลอื่นเสียหาย
สถาปัตยกรรมในอุดมคติคือ การเกาะเกี่ยวกันสูง และการเชื่อมต่อต่ำ ในทางปฏิบัติหมายความว่า: คลาสประกอบด้วยเมธอดที่ทำงานกับข้อมูลเดียวกัน (cohesion) และขึ้นอยู่กับนามธรรมเท่านั้น ไม่ใช่การนำไปใช้ที่เป็นรูปธรรม (coupling) ความไม่สมดุลนำไปสู่วัตถุพระเจ้า (God Object) หรือโค้ดสปาเกตตี
หนี้ทางเทคนิค (Technical Debt) — อุปมาที่ Ward Cunningham นำเสนอ อธิบาย «ดอกเบี้ย» ที่ทีมจ่ายสำหรับการตัดสินใจทางสถาปัตยกรรมที่ไม่เหมาะสมและการละเมิดหลักการทางสถาปัตยกรรม เช่นเดียวกับหนี้ทางการเงิน หนี้ทางเทคนิคสามารถเป็นได้ทั้งโดยเจตนา (เราตัดสินใจทำอย่างรวดเร็ว จะทำใหม่ทีหลัง) และโดยไม่เจตนา (สถาปัตยกรรมที่ไม่ดีเนื่องจากขาดประสบการณ์)
Code Smell — สัญญาณผิวเผินของปัญหาที่ลึกซึ้งในโค้ด คำนี้ได้รับความนิยมโดย Martin Fowler ในหนังสือ «Refactoring» Code Smell ทั่วไป: เมธอดที่ยาว คลาสที่ใหญ่โต สายเรียกที่ยาว การทำซ้ำโค้ด การใช้ความคิดเห็นมากเกินไป (แทนที่จะใช้โค้ดที่ชัดเจน)
ที่ IT Sectr หนี้ทางเทคนิคถูกติดตามใน Jira เป็นงานแยกต่างหาก ในแต่ละสปรินท์ เราจัดสรรเวลา 20% สำหรับการปรับโครงสร้างใหม่และการชำระหนี้ การทำงานอย่างเป็นระบบกับหนี้ทางเทคนิคเป็นวิธีเดียวที่จะหลีกเลี่ยงสถานการณ์ที่การเพิ่มคุณสมบัติใหม่ใช้เวลานานกว่าการพัฒนาจากศูนย์
คำถามที่พบบ่อย
Single Responsibility Principle (SRP) — สำคัญที่สุด เนื่องจากการละเมิดของมันนำไปสู่การละเมิดหลักการอื่นๆ โดยอัตโนมัติ คลาสที่มีหลายความรับผิดชอบยากต่อการทดสอบ ขยาย และบำรุงรักษา เริ่มต้นด้วย SRP — ที่เหลือจะตามมาเอง
Cohesion (การเกาะเกี่ยวกัน) — การเชื่อมต่อภายในโมดูล (ยิ่งสูงยิ่งดี) Coupling (การเชื่อมต่อ) — การเชื่อมต่อระหว่างโมดูล (ยิ่งต่ำยิ่งดี) สถาปัตยกรรมที่ดีมุ่งสู่การเกาะเกี่ยวกันสูงและการเชื่อมต่อต่ำ
ไม่ หลักการเป็นแนวทาง ไม่ใช่กฎหมายที่สมบูรณ์ ในโปรเจกต์ขนาดเล็กหรือต้นแบบ การยึดมั่นต่อ SOLID มากเกินไปอาจนำไปสู่การออกแบบที่มากเกินไป สิ่งสำคัญคือการหาสมดุลระหว่างสถาปัตยกรรมที่ «ดีพอ» และความเร็วในการพัฒนา
ใช้เครื่องมือวิเคราะห์แบบคงที่ (SonarQube, Detekt, ESLint) การตรวจสอบโค้ดและเมตริกโค้ด สัญญาณของหนี้: โค้ดยากต่อการทดสอบ การเปลี่ยนแปลงในที่หนึ่งทำให้อีกที่หนึ่งเสียหาย เวลาในการเพิ่มคุณสมบัติใหม่เพิ่มขึ้นจากสปรินท์สู่สปรินท์ การปรับโครงสร้าง อย่างสม่ำเสมอเป็นวิธีเดียวที่จะควบคุมหนี้
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ