Sau khi hoàn thành chương này, người đọc có khả năng:
Định nghĩa Connascence là gì, hiểu nguồn gốc của khái niệm này từ Meilir Page-Jones và cách nó bổ sung góc nhìn cho kiến trúc phần mềm.
Phân biệt rõ Connascence và Coupling: tại sao Coupling cung cấp cái nhìn tổng quan, còn Connascence là công cụ chẩn đoán ở độ phân giải cao hơn.
Nhận diện và phân loại các dạng Connascence phổ biến (Static và Dynamic) trong code TypeScript thực tế.
Đánh giá chất lượng của các dependency dựa trên ba trục chính của Connascence: Strength (Độ mạnh), Degree (Mức độ ảnh hưởng) và Locality (Khoảng cách).
Áp dụng các nguyên tắc refactoring để "hạ cấp" (downgrade) Connascence, làm cho hệ thống linh hoạt và dễ bảo trì hơn.
1. Connascence là gì
Connascence (bắt nguồn từ tiếng Latin con- "cùng nhau" và nascence "sinh ra", nghĩa là "được sinh ra cùng nhau") là một số liệu thiết kế phần mềm do Meilir Page-Jones đưa ra vào năm 1992 trong cuốn sách What Every Programmer Should Know About Object-Oriented Design.
Định nghĩa cốt lõi: Hai thành phần được coi là có Connascence nếu một sự thay đổi ở thành phần này đòi hỏi thành phần kia cũng phải thay đổi để giữ cho toàn bộ hệ thống hoạt động đúng.
Nếu Coupling trả lời câu hỏi "Thành phần A có gọi thành phần B không?", thì Connascence trả lời câu hỏi "Nếu tôi thay đổi chi tiết X ở A, tôi có phải lùng sục để sửa Y ở B không?".
2. Connascence khác Coupling như thế nào
Coupling và Connascence không đối lập nhau, chúng là hai lăng kính ở hai tiêu cự khác nhau để nhìn vào cùng một bản chất:
Coupling cung cấp một cái nhìn vĩ mô (macro-level). Nó giúp chúng ta phân loại các đường nối giữa các module/class (như Data Coupling, Stamp Coupling).
Connascence cung cấp một cái nhìn vi mô (micro-level) và cung cấp một bộ từ vựng cực kỳ chi tiết về bản chất của sự rò rỉ kiến thức giữa các thành phần.
Connascence coi việc hai thành phần "phụ thuộc nhau" là chuyện hiển nhiên, nhưng nó cung cấp công cụ để đánh giá sự phụ thuộc đó là tốt hay xấu dựa trên tính chất, khoảng cách không gian (locality), và số lượng thành phần bị ảnh hưởng (degree).
3. Phân loại Connascence
3.1. Phân loại Connascence (Static vs Dynamic)
Connascence được chia làm hai nhóm chính: Static Connascence (có thể phát hiện bằng cách đọc mã nguồn hoặc qua trình biên dịch) và Dynamic Connascence (chỉ bộc lộ lúc hệ thống đang chạy).
Dưới đây là các dạng sắp xếp theo độ mạnh tăng dần (càng mạnh càng khó bảo trì):
Static Connascence:
Connascence of Name (CoN): Hai thành phần phải đồng ý về tên của một thực thể (ví dụ: tên phương thức, tên biến). Đây là dạng yếu nhất và không thể tránh khỏi.
Connascence of Type (CoT): Hai thành phần phải đồng ý về kiểu dữ liệu (ví dụ: cùng là number hoặc string).
Connascence of Meaning (CoM) / Value: Hai thành phần phải đồng ý về ý nghĩa của các giá trị ma thuật (magic values). Ví dụ: số 0 là trạng thái "Pending", số 1 là "Success".
Connascence of Position (CoP): Hai thành phần phải đồng ý về thứ tự/vị trí. Ví dụ điển hình nhất là danh sách các tham số của một hàm.
Connascence of Algorithm (CoA): Hai thành phần phải đồng ý về một thuật toán. Ví dụ: một bên mã hóa payload bằng MD5, bên kia phải dùng đúng thuật toán MD5 để giải mã.
Dynamic Connascence:
Connascence of Execution (CoE): Trật tự thực thi giữa các hành động quan trọng. Ví dụ: bạn phải gọi hàm init() trước khi gọi hàm process(), nếu không hệ thống sẽ lỗi.
Connascence of Timing (CoTm): Hệ thống chỉ hoạt động đúng nếu các hành động xảy ra trong một khung thời gian cụ thể (thường gặp trong xử lý bất đồng bộ hoặc race conditions).
Connascence of Value (CoV): Các thành phần chia sẻ chung một giá trị thay đổi theo thời gian, nơi sự kiện ở bên này thay đổi trạng thái bên kia (tương đương Common Coupling).
Connascence of Identity (CoI): Hai thành phần phải cùng trỏ vào một instance duy nhất trên bộ nhớ (chứ không phải hai instances có giá trị giống nhau)
3.2. Ba trục đánh giá (Strength, Degree, Locality)
Meilir Page-Jones cung cấp 3 định luật để phân tích và tối ưu hóa Connascence:
Strength: Các dạng Connascence ở cấp thấp (Name, Type) là hoàn toàn bình thường và tốt. Các dạng cấp cao (Position, Algorithm, Execution) là mã độc (code smell) cần phải refactor để "hạ cấp" xuống mức yếu hơn.
Degree: Số lượng các thành phần bị vướng vào một Connascence. Một Connascence of Meaning chia sẻ giữa 2 module thì có thể tạm chấp nhận, nhưng chia sẻ giữa 20 module là một thảm họa.
Locality: Các thành phần càng ở xa nhau (khác file, khác package, khác microservice), thì Connascence giữa chúng càng phải yếu. Một Connascence of Position giữa hai hàm private trong cùng một class là vô hại; nhưng giữa hai service gọi nhau qua REST API thì cực kỳ rủi ro.
3.3. Connascence như một công cụ phân tích dependency ở độ phân giải cao hơn
Thay vì nói "Service A và Service B đang bị tight coupling", sử dụng Connascence cho phép chúng ta nói chính xác: "Service A và Service B đang có Connascence of Algorithm vì chúng tự parse định dạng JWT thủ công, và chúng có Connascence of Position vì Service A phụ thuộc vào thứ tự cột trả về từ file CSV của Service B."
Ngôn ngữ này giúp team trực tiếp nhìn thấy giải pháp (ví dụ: chuyển từ Position sang Name, đưa Algorithm vào một shared library).
4. Trade-offs
Sự bùng nổ của Boilerplate: Việc hạ cấp Connascence of Position sang Name thường yêu cầu tạo thêm các Type/Interface, DTO (Data Transfer Object). Việc hạ cấp Connascence of Meaning yêu cầu tạo thêm hằng số hoặc Enums. Với các hàm nội bộ nhỏ (khoảng cách Locality gần), chi phí sinh ra boilerplate có thể lớn hơn lợi ích bảo trì.
Không thể đạt được Connascence = 0: Một hệ thống hoạt động luôn phải chia sẻ thông tin. Dạng Connascence thấp nhất (Name) là bắt buộc. Cố gắng thiết kế một hệ thống "hoàn toàn không có Connascence" là điều bất khả thi và phản tác dụng.
5. Practical Guidelines
Quy tắc Locality (Khoảng cách): Nếu hai đoạn code nằm sát nhau (cùng một class/hàm), bạn có thể châm chước cho những Connascence mạnh (như Position hay Execution). Nhưng khi hai đoạn code tách biệt về mặt vật lý (khác module, khác service), hãy dùng mọi cách để đưa Connascence về mức Name và Type.
Hạ cấp (Downgrade) bất cứ khi nào có thể:
Position -> Name (Đóng gói thành Object/DTO).
Meaning -> Name (Thay magic strings/numbers bằng Constants/Enums).
Execution -> Type/Name (Sử dụng Builder Pattern hoặc Fluent API để trình biên dịch ép buộc thứ tự thực thi).
Chú ý Connascence of Algorithm trong Microservices: Nếu hệ thống của bạn tự tạo các format giao tiếp hoặc cách hash dữ liệu tùy chỉnh, hãy đẩy chúng vào một thư viện dùng chung (shared package). Nếu thuật toán thay đổi, các service chỉ cần cập nhật version của thư viện thay vì sửa đổi logic bên trong.
6. Summary
Connascence là công cụ phân tích sự phụ thuộc (dependency) ở mức độ vi mô, làm rõ tại sao hai thành phần bị dính lấy nhau và chi phí khi phá vỡ liên kết đó.
Connascence có phổ kéo dài từ Static (Name, Type, Meaning, Position, Algorithm) đến Dynamic (Execution, Timing, Value, Identity).
Đánh giá sự nguy hiểm của Connascence dựa trên ba tiêu chí: Độ mạnh (Strength), Số lượng (Degree) và Khoảng cách (Locality).
Mục tiêu kiến trúc không phải là loại bỏ hoàn toàn Connascence, mà là hạ cấp các Connascence mạnh xuống các Connascence yếu (như Name, Type) và giữ các Connascence mạnh ở khoảng cách (Locality) càng gần nhau càng tốt.