01 Modularity là gì & vì sao quan trọng?
Modularity = cách nhóm logic mã nguồn có liên quan với nhau (lớp trong OOP, hàm trong lập trình hàm — package ở Java, namespace ở .NET). Đây vừa là cơ chế tổ chức, vừa là không gian tên (namespace) để tách bạch các tài sản phần mềm.
Vũ khí chống entropy. Mọi hệ thống đều có xu hướng trượt về hỗn loạn (entropy). Kiến trúc sư phải chủ động bỏ năng lượng để duy trì tính mô-đun, nếu không hệ thống sẽ tự tan rã theo từng thay đổi nhỏ ngẫu nhiên.
Nguyên tắc tổ chức
Giúp quản lý hệ thống phức tạp, giữ trật tự & nhất quán cho cơ sở mã.
Đặc tính ngầm định
Gần như không yêu cầu kinh doanh nào nhắc tới, nhưng thiếu nó thì code không bền.
Đo để kiểm soát
KTS cần đo modularity liên tục để giữ cấu trúc khỏi hỏng vì thay đổi tùy tiện.
Ví dụ xuyên suốt: hệ Hỏi–đáp tài liệu (RAG)
Suốt trang này, ta soi các khái niệm qua một hệ RAG thật: Python lo pipeline (chunk → embed → truy hồi → sinh câu trả lời), PHP lo web/API và gọi sang qua HTTP. Chính ranh giới HTTP đó là "khoảng cách module" lớn nhất — nơi connascence mạnh trở nên nguy hiểm.
flowchart LR
subgraph PHP["PHP · Web / API (Laravel)"]
W["Upload + câu hỏi"]
end
subgraph PY["Python · RAG pipeline (FastAPI)"]
direction TB
I["Ingest: load → chunk → embed"]:::m
Q["Retrieve → Generate"]:::m
E["Embedder"]:::m
S[("Vector Store")]:::db
I --> E --> S
Q --> E
Q --> S
end
W -->|"POST /ingest"| I
W -->|"POST /query · CoN·CoT·CoA·CoV"| Q
classDef m fill:#dbeee8,stroke:#0f7d72,color:#1c1a14;
classDef db fill:#e2edf3,stroke:#2f6d93,color:#1c1a14;
02 Đo lường modularity
Ba nhóm thước đo: Cohesion (bên trong module), Coupling (giữa các module), và các metric dẫn xuất tổng hợp.
Cohesion — module "thuộc về nhau" tới mức nào
Đo mức độ các phần trong một module thực sự liên quan. Thang 7 mức, từ tốt nhất → tệ nhất:
| Mức cohesion | Ý nghĩa | Chất lượng |
|---|---|---|
| Functional | Mọi phần đều thiết yếu cho một chức năng duy nhất. | ★★★★★ |
| Sequential | Đầu ra của phần này là đầu vào của phần kia. | ★★★★★ |
| Communicational | Các phần cùng thao tác trên một dữ liệu / cùng góp vào một đầu ra. | ★★★★★ |
| Procedural | Các phần phải chạy theo một thứ tự nhất định. | ★★★★★ |
| Temporal | Liên quan vì cùng thời điểm (vd cùng khởi tạo lúc startup). | ★★★★★ |
| Logical | Liên quan về logic nhưng chức năng khác nhau (vd StringUtils). | ★★★★★ |
| Coincidental | Tệ nhất — chung module chỉ vì… cùng một file. | ★★★★★ |
Cohesion là metric kém chính xác hơn coupling — mức độ kết dính của một module thường tùy thuộc đánh giá của từng KTS.
RAG Gom code theo chức năng, đừng gom theo "tiện tay":
# ✓ Functional — mọi thứ phục vụ ĐÚNG một việc: text → vector
class Embedder:
def embed(self, text): ...
def embed_batch(self, texts): ...
# ✗ Coincidental — gom hàm chẳng liên quan chỉ vì "cho tiện"
# helpers.py
def clean_html(s): ...
def parse_pdf(path): ...
def post_to_slack(msg): ...
Embedder (functional) khỏi helpers.py (coincidental) giúp tái dùng & test dễ hơn hẳn.Coupling — kết nối giữa các thành phần
Afferent (Ca)
Số kết nối đến một thành phần — bao nhiêu nơi phụ thuộc vào nó.
mẹo: a → đứng trước e → incomingEfferent (Ce)
Số kết nối đi ra từ thành phần tới các phần khác.
mẹo: e → exit → outgoing
flowchart LR
A1[Lớp A]:::in --> C
A2[Lớp B]:::in --> C
A3[Lớp D]:::in --> C
C{{Thành phần C}}:::core --> E1[Lớp X]:::out
C --> E2[Lớp Y]:::out
classDef in fill:#dbeee8,stroke:#0f7d72,color:#1c1a14;
classDef out fill:#e2edf3,stroke:#2f6d93,color:#1c1a14;
classDef core fill:#14233b,stroke:#14233b,color:#f3ede0;
Ba metric dẫn xuất
Abstractness
Tỉ lệ phần tử trừu tượng (interface, abstract class) trên tổng số. Toàn bộ code trong một main() → A ≈ 0.
Instability
Tỉ lệ coupling đi ra trên tổng coupling. I cao = dễ vỡ khi thay đổi vì phụ thuộc nhiều thứ khác.
Distance
Khoảng cách tới "chuỗi chính" — cân bằng lý tưởng giữa trừu tượng & ổn định. Càng gần 0 càng khỏe.
Distance from the Main Sequence
03 Connascence — sự đồng biến
Hai thành phần là "đồng biến" nếu thay đổi ở cái này buộc phải sửa cái kia để hệ thống vẫn đúng.
Meilir Page-JonesConnascence tinh chỉnh khái niệm coupling: không chỉ hỏi "có ghép nối không?" mà hỏi "ghép nối theo kiểu gì?". Chia làm hai nhóm: tĩnh (thấy qua đọc mã) và động (chỉ lộ ra lúc chạy).
Static Đồng biến tĩnh — phát hiện qua phân tích mã
Tên
Nhiều nơi phải thống nhất tên một thực thể (vd tên method). Phổ biến & dễ chịu nhất — đổi tên cả hệ thống nay rất dễ.
Kiểu
Thống nhất về kiểu của thực thể — quen thuộc trong ngôn ngữ định kiểu tĩnh.
Ý nghĩa
Thống nhất ý nghĩa của giá trị cụ thể — kinh điển là "magic number" như 1 = TRUE.
Vị trí
Thống nhất thứ tự các giá trị — vd thứ tự tham số truyền vào hàm.
Thuật toán
Thống nhất một thuật toán — vd hàm băm chạy giống hệt ở cả client & server.
void updateSeat(String name, String seatLocation) { ... }
// Cả hai tham số đều là String → trình biên dịch KHÔNG báo lỗi,
// nhưng truyền sai THỨ TỰ thì ngữ nghĩa sai hoàn toàn:
updateSeat("14D", "Ford, N"); // name = "14D"? chỗ ngồi = "Ford, N"?
RAG CoA xuyên service — cách chuẩn hoá text lúc index phải khớp lúc query, dù ở hai ngôn ngữ:
def normalize(t):
return t.lower().strip() # bỏ hoa, gập khoảng trắng
store.add(embed(normalize(doc))) # vector nằm trong "không gian" đã chuẩn hoá
$q = trim($question); // ✗ KHÁC: quên ->lower()
$res = $rag->query($q); // câu hỏi & tài liệu lệch chuẩn → truy hồi sai
Dynamic Đồng biến động — chỉ lộ lúc chạy
Thứ tự thực thi
Thứ tự gọi quan trọng — phải setRecipient() trước send().
Thời điểm
Thời điểm chạy quan trọng — kinh điển là race condition giữa hai luồng.
Giá trị
Nhiều giá trị phải đổi cùng nhau — 4 góc hình chữ nhật, hay transaction phân tán.
Danh tính
Nhiều thành phần phải trỏ tới cùng một thực thể — vd cùng một hàng đợi phân tán.
email = new Email();
email.setRecipient("foo@example.com");
email.setSender("me@me.com");
email.send();
email.setSubject("whoops"); // ✗ quá muộn — phải set TRƯỚC khi send()
04 Ba thuộc tính của connascence
Ba "núm xoay" giúp KTS dùng connascence một cách khôn ngoan — luôn cân nhắc Strength & Locality cùng nhau.
Strength
Đo bằng độ dễ refactor. Tĩnh thường yếu hơn (dễ sửa) → ưu tiên hơn động.
Locality
Khoảng cách giữa các module. Đồng biến mạnh chấp nhận được nếu ở gần nhau, nhưng là "code smell" khi ở xa.
Degree
Ảnh hưởng tới bao nhiêu lớp. Đồng biến động không quá tệ nếu chỉ vài module — nhưng code luôn phình to.
Thang độ mạnh — kim chỉ nam refactor
Đổi từ dạng mạnh → yếu để cải thiện coupling. Càng ít chấm càng yếu & càng nên ưu tiên:
| Loại connascence | Nhóm | Độ mạnh (yếu → mạnh) |
|---|---|---|
| Name (CoN) | Tĩnh | ★★★★★ |
| Type (CoT) | Tĩnh | ★★★★★ |
| Meaning (CoM) | Tĩnh | ★★★★★ |
| Position (CoP) | Tĩnh | ★★★★★ |
| Algorithm (CoA) | Tĩnh | ★★★★★ |
| Execution / Timing / Values / Identity | Động | ★★★★★ |
RAG Hạ độ mạnh CoM → CoN: thay "magic number" trạng thái tài liệu bằng hằng số có tên:
# ✗ CoM — "1" nghĩa là gì? phải nhớ quy ước ngầm
if doc.status == 1: # 1 = đã index? 2 = đang chạy?
retrieve(doc)
# ✓ CoN — đặt tên; đổi ở một chỗ, mọi nơi theo tên
class DocStatus(IntEnum):
PENDING, INDEXED, FAILED = 0, 1, 2
if doc.status == DocStatus.INDEXED:
retrieve(doc)
Ví dụ refactor kinh điển: chuyển CoM → CoN bằng cách thay "magic number" bằng một hằng số có tên.
05 Cải thiện modularity bằng connascence
Ba quy tắc của Page-Jones
Giảm tổng thể
Chia hệ thống thành các phần được đóng gói để giảm tổng mức đồng biến.
Giảm phần vượt biên
Tối thiểu hóa đồng biến băng qua ranh giới đóng gói.
Tối đa nội bộ
Cho phép đồng biến cao bên trong một ranh giới — ở gần thì không sao.
Hai quy tắc của Jim Weirich
Rule of Degree
- Chuyển các dạng đồng biến mạnh thành các dạng yếu hơn.
Rule of Locality
- Khoảng cách giữa các thành phần càng tăng → dùng dạng đồng biến càng yếu.
Locality qua ví dụ: đừng để PHP "biết quá nhiều" về Python
RAG Cùng một kiểu ghép nối, nhưng băng qua HTTP (xa) thì phải hạ về dạng yếu:
// ✗ Mạnh & xuyên ranh giới: vị trí tham số (CoP), PHP tự embed (CoA/CoV)
$res = $http->post('/query', [$vector, 5, 'cosine', true]);
// ✓ Hợp đồng mỏng, field có tên (CoN/CoT) — để Python lo embed
$res = $http->post('/query', [
'question' => $q, // gửi câu hỏi THÔ
'top_k' => 5,
]);
06 Ghi nhớ nhanh
Modularity là vũ khí chống entropy — phải chủ động bỏ năng lượng duy trì, không thì hệ thống tự tan rã.
Cân bằng trừu tượng & ổn định — dùng "Distance from Main Sequence" để tránh Zone of Pain (cứng nhắc) và Zone of Uselessness (mơ hồ).
Ưu tiên tĩnh hơn động — tinh chỉnh phụ thuộc runtime thành phụ thuộc mã nguồn để dễ bảo trì.
Càng xa càng phải yếu — thành phần ở xa chỉ nên biết nhau qua thông tin tối thiểu (Tên/Kiểu).
Mọi thứ đều đánh đổi — tăng tái sử dụng thường kéo theo tăng coupling; việc của KTS là tìm điểm cân bằng.