Coupling: "Kẻ thù số 1" trong Thiết kế và Kiến trúc Phần mềm

Phân tích nguyên nhân gốc rễ của các thất bại phần mềm luôn chỉ ra một thủ phạm duy nhất: Coupling (Sự phụ thuộc). Từ Singleton, biến toàn cục cho đến các giả định ngầm và Null reference, bài viết này vạch trần cách Coupling tàn phá source code của bạn.

Tháng 3 15, 2020 - 23:12
 0  2
Coupling: "Kẻ thù số 1" trong Thiết kế và Kiến trúc Phần mềm

Khi phân tích nguyên nhân gốc rễ của mọi thất bại trong phần mềm, chúng ta sẽ luôn tìm thấy một thủ phạm duy nhất dưới nhiều lớp vỏ bọc khác nhau. Kẻ thù luôn ở đó. Nhiều khi nó ngụy trang dưới sự lười biếng, đôi khi là sự đơn giản hóa, và rất thường xuyên khoác lên mình bộ cánh "tối ưu hóa".

Nếu phân tích những sai lầm phổ biến, chúng ta luôn tìm thấy một thủ phạm duy nhất.

Đó chính là Coupling (Sự phụ thuộc).

Mở rộng các tiên đề

Chúng ta sẽ thêm sai lầm duy nhất mà chúng ta phải tránh bằng mọi giá vào danh sách tiên đề này.

Các ví dụ về Coupling

Classes (Lớp)

Các biến toàn cục (global variables) tạo ra một liên kết tham chiếu toàn cầu từ code. Liên kết này không thể dễ dàng bị phá vỡ trừ khi chúng ta kết nối với các interface thay vì chính các class đó và sử dụng nguyên lý đảo ngược phụ thuộc (chữ D trong SOLID).

Việc có các biến toàn cục trong một ngôn ngữ cấu trúc đồng nghĩa với việc bị gắn chặt vào một tham chiếu không thể thay thế, không thể mock (giả lập), hoặc trì hoãn theo thời gian. Trong lập trình hướng đối tượng sử dụng các ngôn ngữ phân loại, vấn đề cũng tương tự.

Đây là một bước lùi so với các ngôn ngữ thuần chức năng (functional languages), nơi có sự cấm đoán rõ ràng bằng cách ngăn chặn các hàm tạo ra tác dụng phụ (side effects).

Nếu chúng ta giữ một quan điểm cực đoan và tối giản:

Mọi hàm/phương thức chỉ nên gọi các đối tượng được tham chiếu bởi các thuộc tính và/hoặc các tham số của chúng.

Settings (Cấu hình)

Đây là những "cái phích cắm" cho phép chúng ta cấu hình phần mềm bằng cách sử dụng các tham chiếu toàn cục tùy ý từ bất kỳ đâu trong code.

Chúng là một trường hợp cụ thể của tham chiếu toàn cục và ngăn cản việc viết unit test chính xác cho hệ thống. Nếu một thứ gì đó cần có khả năng cấu hình, cấu hình đó phải được truyền vào dưới dạng một đối tượng. Bằng cách này, chúng ta có thể thay thế cấu hình trong các bài test và có toàn quyền kiểm soát mà không gây ra tác dụng phụ.

Các giả định ngầm (Hidden assumptions)

Việc phớt lờ một phần nguyên tắc này đồng nghĩa với việc chấp nhận rủi ro mất thông tin trên contract (hợp đồng giao tiếp giữa các module) và mắc sai lầm dưới các cách hiểu khác nhau.

Trong ví dụ trước, chúng ta đại diện cho 10 mét bằng số 10. Trong trường hợp này, chúng ta đang bị "couple" (phụ thuộc) vào giả định ngầm rằng 10 đại diện cho 10 mét.

Các giả định ngầm thường xuất hiện vào những thời điểm tồi tệ nhất của chu trình phát triển.

Tham chiếu đến Null

Đây là một trường hợp đặc biệt của quy tắc trên.

Null không bao giờ nên được sử dụng vì nó vi phạm nguyên tắc bất di bất dịch của chúng ta: nó không có tính song ánh (bijective) với bất kỳ thực thể nào trong thế giới thực (Null chỉ tồn tại trong thế giới của các lập trình viên).

Nếu chúng ta quyết định sử dụng null như một lá cờ (flag) cho một hành vi cụ thể, chúng ta đang buộc quyết định của người triển khai hàm (hoặc set một thuộc tính là null) phụ thuộc vào người gọi nó (hoặc truy cập thuộc tính), và ngữ nghĩa mơ hồ này mang lại vô số vấn đề.

Singletons

Singleton pattern là một mẫu thiết kế gây tranh cãi. Nếu nhìn nhận dưới sự hướng dẫn của quy tắc thiết kế duy nhất của chúng ta, ta sẽ loại bỏ việc sử dụng nó ngay lập tức. Một đối tượng được biểu diễn bởi singleton nếu chỉ có duy nhất một instance của class đó.

Điều này cũng vi phạm nguyên tắc khai báo (declarative), vì tính duy nhất của một khái niệm, nhìn chung, đang bị ghép nối với các vấn đề triển khai (implementation), do đó chúng ta đang vi phạm quy tắc thiết kế duy nhất mà mình tự đặt ra.

Ngoài ra, các singleton thường được tham chiếu thông qua tên class của chúng, do đó chúng ta lại gặp phải tất cả các vấn đề đã đề cập trong đoạn đầu tiên.

If/Case/Switch và những người bạn

Các mệnh đề If chứa đựng một sự phụ thuộc ngầm giữa điều kiện và nơi chúng được đánh giá, vi phạm nguyên lý Đóng/Mở (chữ O trong SOLID).

If (và do đó cả case) nên tránh sử dụng trừ khi các điều kiện này là quy tắc nghiệp vụ (business rules), tức là có liên quan đến song ánh thực tế.

Một quy tắc nghiệp vụ ‘Thưởng cho nhân viên có thâm niên từ 3 năm trở lên’ có thể được thể hiện an toàn bằng mệnh đề If, nhưng các quy tắc như ‘Nếu vị trí của nhân viên là junior thì trả lương 10.000’ thì không nên. Bởi lẽ điều này không phải là bản chất của quy tắc nghiệp vụ mà chỉ là ngẫu nhiên (liên quan đến trạng thái nhân viên), do đó nên được xử lý bằng tính đa hình (polymorphism).

Trong thế giới thực, nhân viên nhận thức được vị trí của họ nhưng thường không nhận thức được "tuổi đời" của họ trong công ty theo cách máy móc như vậy.

Documentation (Tài liệu)

Nếu bạn cần thêm chú thích (comment) vào code, đó là dấu hiệu cho thấy code của bạn chưa đủ tính khai báo (declarative).

Tài liệu mã nguồn (code documentation) thường không được đồng bộ với chính đoạn code đó. Nhiều khi các lập trình viên thay đổi code nhưng không đủ can đảm để thay đổi cả tài liệu đi kèm. Đây là một trường hợp coupling tinh vi khác.

Vài tháng sau, chúng ta đọc lại code và tài liệu, rồi tốn rất nhiều thời gian để hiểu ý nghĩa thực sự của chúng.

Ripple effect (Hiệu ứng gợn sóng)

Nếu chúng ta trung thành với quy tắc thiết kế duy nhất và có một mô hình khai báo (declarative model), chúng ta sẽ kỳ vọng rằng một thay đổi nhỏ trong yêu cầu sẽ tạo ra một thay đổi nhỏ trong mô hình, và cứ thế tiếp diễn. Khi điều này không xảy ra, hiệu ứng gợn sóng đáng sợ sẽ xuất hiện, biến phần mềm trở nên khó đoán và đầy rẫy các lỗi tiềm ẩn cản trở việc bảo trì.

Kết luận

Coupling là cần thiết vì các đối tượng phải biết nhau để cộng tác và giải quyết các vấn đề được đặt ra trong mô phỏng.

Việc tìm ra liên kết nào là tốt và liên kết nào là xấu để tránh hiệu ứng gợn sóng đòi hỏi một chút kinh nghiệm và sự tuân thủ nghiêm ngặt các quy tắc được định nghĩa trong bài viết này.

Phản Ứng Của Bạn Là Gì?

Thích Thích 0
Không Thích Không Thích 0
Yêu Yêu 0
Hài hước Hài hước 0
Giận dữ Giận dữ 0
Buồn Buồn 0
Wow Wow 0
trants I'm a Fullstack Software Developer focusing on Go and React.js. Current work concentrates on designing scalable services, reliable infrastructure, and user-facing experiences.