Nguyên tắc và phương pháp kiến trúc — là tập hợp các quy tắc và khuyến nghị giúp các nhà phát triển tạo ra mã nguồn có thể bảo trì, mở rộng và dễ hiểu. Theo TIOBE Index (2025), các dự án tuân theo nguyên tắc kiến trúc có ít hơn 40% lỗi nghiêm trọng. Trong bài viết này, chúng tôi sẽ phân tích SOLID, GRASP, DRY, KISS, YAGNI và các nguyên tắc khác, cũng như thảo luận về nợ kỹ thuật và Code Smell.
Những điểm chính
Nguyên tắc kiến trúc — là nền tảng của mã nguồn chất lượng. SOLID là từ viết tắt do Robert Martin («Chú Bob») giới thiệu, mô tả năm nguyên tắc thiết kế hướng đối tượng. Tuân theo SOLID làm cho mã nguồn linh hoạt hơn, dễ kiểm tra hơn và chống chịu tốt hơn với thay đổi. Vi phạm nguyên tắc kiến trúc là một trong những nguyên nhân chính gây ra nợ kỹ thuật.
Hãy xem xét từng nguyên tắc. Single Responsibility Principle (SRP) — mỗi lớp chỉ nên có một lý do để thay đổi. Open/Closed Principle (OCP) — các lớp mở cho việc mở rộng nhưng đóng cho việc sửa đổi. Liskov Substitution Principle (LSP) — các đối tượng của kiểu con phải có thể thay thế các đối tượng của kiểu cơ sở mà không phá vỡ logic. Interface Segregation Principle (ISP) — nhiều giao diện chuyên biệt tốt hơn một giao diện chung. Dependency Inversion Principle (DIP) — phụ thuộc vào sự trừu tượng, không phụ thuộc vào các triển khai cụ thể.
Theo phân tích SonarQube (2025), vi phạm nguyên tắc SOLID xảy ra ở 68% các dự án thương mại. Các vấn đề phổ biến nhất là vi phạm SRP (35%) và ISP (22%). Tại IT Sectr, chúng tôi triển khai SOLID ở giai đoạn đánh giá kiến trúc — điều này giúp phát hiện vấn đề trước khi chúng trở thành nợ kỹ thuật.
SRP (Nguyên tắc Trách nhiệm Đơn nhất) — quan trọng nhất và đồng thời là nguyên tắc SOLID bị vi phạm thường xuyên nhất. Nó nói rằng: một lớp chỉ nên có một lý do để thay đổi. Nếu một lớp làm quá nhiều việc, nó sẽ khó kiểm tra, sửa đổi và hiểu.
Một vi phạm điển hình là một lớp đồng thời xử lý dữ liệu, lưu trữ vào cơ sở dữ liệu và gửi thông báo email. Ví dụ dưới đây cho thấy vi phạm SRP trong Kotlin và cách khắc phục.
// Vi phạm SRP — lớp làm ba việc khác nhau
class UserService {
fun registerUser(email: String, name: String) {
// 1. Xác thực dữ liệu
if (!email.contains("@")) throw IllegalArgumentException("Invalid email")
// 2. Lưu vào cơ sở dữ liệu
val user = User(email, name)
database.save(user)
// 3. Gửi thông báo
emailService.sendWelcomeEmail(email, name)
}
}
// Khắc phục — chia thành ba lớp
class UserRegistrationService {
fun register(email: String, name: String) {
UserValidator().validate(email)
val user = User(email, name)
UserRepository().save(user)
NotificationService().sendWelcome(user)
}
}
Trong phiên bản đã sửa, mỗi lớp chịu trách nhiệm cho nhiệm vụ riêng của mình: UserValidator — chịu trách nhiệm xác thực, UserRepository — lưu trữ, NotificationService — thông báo. Điều này làm cho mã nguồn có thể kiểm tra và tái sử dụng — bạn có thể thay thế triển khai cơ sở dữ liệu mà không thay đổi logic xác thực.
GRASP (General Responsibility Assignment Software Patterns) — chín nguyên tắc kiến trúc để phân bổ trách nhiệm giữa các đối tượng, được mô tả bởi Craig Larman. Không giống như SOLID, GRASP trả lời câu hỏi «lớp nào nên chứa phương thức này?». Các mẫu chính: Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, Protected Variations.
Law of Demeter (LoD, nguyên tắc kết nối tối thiểu) — một quy tắc đơn giản: một đối tượng chỉ nên giao tiếp với các láng giềng trực tiếp của nó. Không nên viết a.getB().getC().doSomething() — điều này tạo ra sự kết nối chặt chẽ giữa các lớp. LoD cải thiện khả năng tái sử dụng và đơn giản hóa việc kiểm tra.
Tại IT Sectr, chúng tôi kiểm tra việc tuân thủ LoD trong quá trình Đánh giá Mã nguồn. Nếu một phương thức «đi qua» ba đối tượng trở lên, đó là tín hiệu cho thấy kiến trúc cần được đơn giản hóa. Vi phạm LoD là một trong những Code Smell phổ biến nhất trong các dự án lớn.
DRY (Don't Repeat Yourself), KISS (Keep It Simple, Stupid) và YAGNI (You Ain't Gonna Need It) — ba nguyên tắc kiến trúc cơ bản mà mọi nhà phát triển đều biết. Mặc dù đơn giản, việc vi phạm chúng vẫn thường xuyên xảy ra.
DRY — đừng lặp lại mã nguồn. Nếu cùng một logic xuất hiện ở hai nơi, hãy trích xuất nó thành một phương thức hoặc lớp chung. Sự trùng lặp là nguồn gốc chính của lỗi: việc sửa ở một nơi bị quên không áp dụng ở nơi khác. DRY không có nghĩa là bạn không thể có mã tương tự — điều quan trọng là logic nghiệp vụ không bị lặp lại.
KISS — càng đơn giản càng tốt. Các giải pháp phức tạp với nhiều sự trừu tượng và kế thừa thường là quá mức cần thiết. Hãy bắt đầu với một giải pháp đơn giản và chỉ làm phức tạp hóa khi cần thiết. YAGNI — đừng viết mã cho chức năng có thể cần «một ngày nào đó sau này». Điều này dẫn đến phình to mã nguồn và tăng độ phức tạp bảo trì.
DRY — không chỉ đơn thuần là không có sao chép-dán. Đó là nguyên tắc mà theo đó mọi phần kiến thức hoặc logic nên có một biểu diễn duy nhất, rõ ràng trong hệ thống. Sự trùng lặp có thể là rõ ràng (mã được sao chép) và ngầm (cùng logic trong các lớp khác nhau).
Tại IT Sectr, chúng tôi sử dụng các chỉ số phân tích mã để phát hiện trùng lặp. Các công cụ như SonarQube và Detekt hiển thị tỷ lệ phần trăm mã bị trùng lặp. Giá trị trên 5% là lý do để tái cấu trúc. Tuy nhiên, điều quan trọng cần nhớ là: không nên đạt được DRY bằng cái giá của những sự trừu tượng sai lầm — đôi khi tốt hơn nên để hai đoạn mã tương tự như cũ nếu việc kết hợp chúng sẽ làm phức tạp việc hiểu.
Separation of Concerns (SoC) — một nguyên tắc kiến trúc trong đó hệ thống được chia thành các phần độc lập (concerns), mỗi phần giải quyết nhiệm vụ riêng của nó. Một ví dụ kinh điển là sự phân chia thành các lớp: trình bày, logic nghiệp vụ, truy cập dữ liệu. Mỗi lớp chỉ phụ thuộc vào lớp bên dưới nó.
Modularity (tính mô-đun) — mức độ mà hệ thống có thể được chia thành các mô-đun. Một mô-đun là một nhóm các lớp có liên quan logic với một giao diện được xác định rõ ràng. Các mô-đun nên được kết nối lỏng lẻo (low coupling) và có tính gắn kết cao (high cohesion).
Cohesion (tính gắn kết) — thước đo mức độ các phần tử trong cùng một mô-đun liên quan với nhau. Tính gắn kết cao là tốt: một lớp làm một việc và làm nó tốt. Low coupling (kết nối thấp) — thước đo mức độ các mô-đun độc lập với nhau. Kết nối thấp là tốt: thay đổi một mô-đun không làm hỏng các mô-đun khác.
Kiến trúc lý tưởng là tính gắn kết cao và kết nối thấp. Trong thực tế, điều này có nghĩa là: một lớp chứa các phương thức làm việc trên cùng một dữ liệu (gắn kết) và chỉ phụ thuộc vào các sự trừu tượng, không phải vào các triển khai cụ thể (kết nối). Sự mất cân bằng dẫn đến «Đối tượng Chúa» (God Object) hoặc «mã spaghetti».
Nợ kỹ thuật (Technical Debt) — một phép ẩn dụ do Ward Cunningham giới thiệu, mô tả «lãi» mà một nhóm phải trả cho các quyết định kiến trúc dưới mức tối ưu và vi phạm các nguyên tắc kiến trúc. Giống như nợ tài chính, nợ kỹ thuật có thể là cố ý (chúng tôi quyết định làm nhanh, sẽ làm lại sau) và vô ý (kiến trúc kém do thiếu kinh nghiệm).
Code Smell — các dấu hiệu bề mặt của các vấn đề sâu trong mã nguồn. Thuật ngữ này được Martin Fowler phổ biến trong cuốn sách «Refactoring». Code Smell điển hình: phương thức dài, lớp lớn, chuỗi gọi dài, trùng lặp mã nguồn, sử dụng quá nhiều comment (thay vì mã nguồn rõ ràng).
Tại IT Sectr, nợ kỹ thuật được theo dõi trong Jira dưới dạng các tác vụ riêng biệt. Mỗi sprint, chúng tôi dành 20% thời gian để tái cấu trúc và trả nợ. Làm việc có hệ thống với nợ kỹ thuật là cách duy nhất để tránh tình huống thêm một tính năng mới mất nhiều thời gian hơn là phát triển nó từ đầu.
Câu hỏi thường gặp
Single Responsibility Principle (SRP) — quan trọng nhất, vì vi phạm nó tự động dẫn đến vi phạm các nguyên tắc khác. Một lớp có nhiều trách nhiệm khó kiểm tra, mở rộng và bảo trì. Hãy bắt đầu với SRP — phần còn lại sẽ theo sau.
Cohesion (tính gắn kết) — sự kết nối bên trong một mô-đun (càng cao càng tốt). Coupling (kết nối) — sự kết nối giữa các mô-đun (càng thấp càng tốt). Kiến trúc tốt hướng đến tính gắn kết cao và kết nối thấp.
Không, các nguyên tắc là hướng dẫn, không phải luật tuyệt đối. Trong các dự án nhỏ hoặc nguyên mẫu, tuân thủ quá mức SOLID có thể dẫn đến thiết kế quá mức. Điều quan trọng là tìm sự cân bằng giữa kiến trúc «đủ tốt» và tốc độ phát triển.
Sử dụng các công cụ phân tích tĩnh (SonarQube, Detekt, ESLint), Đánh giá Mã nguồn và các chỉ số mã nguồn. Dấu hiệu của nợ: mã khó kiểm tra, thay đổi ở một nơi làm hỏng nơi khác, thời gian thêm tính năng mới tăng lên qua từng sprint. Tái cấu trúc thường xuyên là cách duy nhất để kiểm soát nợ.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.