Sổ tay Kiến trúc Phần mềm
02 Phần I · Nền tảng Chương 3 & 7

Tính mô-đun & Connascence

Modularity & Connascence

Modularity là đặc tính kiến trúc ngầm định — hiếm khi được ghi trong yêu cầu, nhưng quyết định một cơ sở mã có sống được lâu dài hay không. Đo được nó mới giữ được nó.

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

organizing principle

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

implicit characteristic

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

measure to govern

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;
            
Ranh giới HTTP (PHP→Python) là khoảng cách lớn nhất: mọi connascence mạnh băng qua đây — cùng cách chuẩn hoá text (CoA), cùng số chiều vector (CoV) — đều rủi ro. Mục tiêu: chỉ để CoN/CoT (field có tên) đi qua.

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ĩaChất lượng
FunctionalMọ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.
CommunicationalCá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.
ProceduralCác phần phải chạy theo một thứ tự nhất định.
TemporalLiên quan vì cùng thời điểm (vd cùng khởi tạo lúc startup).
LogicalLiên quan về logic nhưng chức năng khác nhau (vd StringUtils).
CoincidentalTệ 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":

Python · Functional ✓ vs Coincidental ✗
# ✓ 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): ...
Tách 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)

incoming coupling

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 → incoming

Efferent (Ce)

outgoing coupling

Số kết nối đi ra từ thành phần tới các phần khác.

mẹo: eexit → 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;
            
Mũi tên vào C = afferent (Ca=3) · mũi tên ra khỏi C = efferent (Ce=2).

Ba metric dẫn xuất

Abstractness

A = Σmᵃ / Σmᶜ

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

I = Cₑ / (Cₑ + Cₐ)

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

D = | A + I − 1 |

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

Zone of Pain Zone of Uselessness Main Sequence (D = 0) class khỏe mạnh Instability (I) → Abstractness (A) → 0 1 0 1
Zone of Pain: quá cụ thể & ổn định (khó sửa). Zone of Uselessness: quá trừu tượng mà không ai dùng. Mục tiêu: nằm gần đường chéo.

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-Jones

Connascence 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

Connascence of Name (CoN)

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

Connascence of Type (CoT)

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

Connascence of Meaning (CoM)

Thống nhất ý nghĩa của giá trị cụ thể — kinh điển là "magic number" như 1 = TRUE.

Vị trí

Connascence of Position (CoP)

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

Connascence of Algorithm (CoA)

Thống nhất một thuật toán — vd hàm băm chạy giống hệt ở cả client & server.

CoP — đúng kiểu, sai ngữ nghĩa
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"?
Connascence of Position: vị trí tham số là "hợp đồng ngầm" giữa nơi gọi và nơi định nghĩa.

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ữ:

Python · chuẩn hoá khi index tài liệu
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á
PHP · chuẩn hoá khi gửi câu hỏi
$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
Hai phía phải dùng đúng một thuật toán (CoA) — mà lệch nhau thì không lỗi nào báo, chỉ thấy kết quả kém. Tốt nhất: chuẩn hoá ở một nơi (Python), PHP gửi text thô.

Dynamic Đồng biến động — chỉ lộ lúc chạy

Thứ tự thực thi

Connascence of Execution (CoE)

Thứ tự gọi quan trọng — phải setRecipient() trước send().

Thời điểm

Connascence of Timing

Thời điểm chạy quan trọng — kinh điển là race condition giữa hai luồng.

Giá trị

Connascence of Values (CoV)

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

Connascence of Identity (CoI)

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.

CoE — sai thứ tự thực thi
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()
KTS khó phát hiện đồng biến động hơn vì thiếu công cụ phân tích lời gọi runtime (so với call graph tĩnh).

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

độ mạnh

Đo bằng độ dễ refactor. Tĩnh thường yếu hơn (dễ sửa) → ưu tiên hơn động.

Locality

tính cục bộ

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

mức độ / quy mô

Ả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 connascenceNhó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:

Python · status của tài liệu
# ✗ 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:

PHP → Python qua HTTP
// ✗ 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,
]);
Trong một hàm Python, truyền theo vị trí (CoP) chẳng sao. Nhưng xa tới mức xuyên HTTP thì CoP/CoA/CoV thành rủi ro — kéo về CoN/CoT. Đúng Rule of Locality.

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.

NguồnChương 3 (Modularity) & Chương 7 (Scope of Architecture Characteristics), Fundamentals of Software Architecture — Mark Richards & Neal Ford, O'Reilly 2020.