Ngăn chặn Phụ thuộc Vòng với SDP
Phụ thuộc vòng (Circular dependencies) là những kẻ sát nhân thầm lặng. Chúng không gây ra lỗi build. Chúng không gây ra ngoại lệ khi chạy (runtime exceptions) lúc import. Thay vào đó, chúng gây ra những giá trị undefined khó chịu, chỉ xuất hiện trên môi trường production vài tuần sau đó.
Một vòng lặp xảy ra khi Module A import từ B, và B lại import ngược lại từ A.
JavaScript giải quyết các lệnh import này một cách âm thầm. Nếu Module A vẫn đang trong quá trình tải khi Module B yêu cầu nó, JavaScript sẽ trả về một đối tượng rỗng. Mã nguồn của bạn sẽ lỗi sau đó, và stack trace sẽ chỉ sai chỗ.
Bạn có thể khắc phục điều này bằng dependency-cruiser. Công cụ này quét các tệp của bạn và lập bản đồ đồ thị phụ thuộc. Nó hoạt động với TypeScript và monorepos.
Cách thiết lập:
- Thêm vào dev dependencies:
yarn add -D dependency-cruiser - Thêm một script vào
package.json:"depcruise": "depcruise packages --config .dependency-cruiser.js" - Tạo một tệp
.dependency-cruiser.jsđể bắt các vòng lặp.
Nhưng tìm ra một vòng lặp mới chỉ là một nửa cuộc chiến. Bạn phải ngăn chặn chúng hình thành.
Hãy sử dụng Nguyên lý Phụ thuộc Ổn định (Stable Dependencies Principle - SDP). Quy tắc này nói rằng bạn phải phụ thuộc theo hướng của sự ổn định.
Tính ổn định là một thuộc tính cấu trúc. Một module được coi là ổn định khi có nhiều module khác phụ thuộc vào nó. Nếu bạn thay đổi một module ổn định, bạn sẽ ảnh hưởng đến rất nhiều người. Chi phí này chính là thứ làm cho nó trở nên ổn định.
Sử dụng công thức tính độ không ổn định (Instability): I = Fan-Out / (Fan-In + Fan-Out)
• I = 0 nghĩa là một module có tính ổn định tối đa. Mọi thứ đều phụ thuộc vào nó, và nó không phụ thuộc vào bất cứ thứ gì. • I = 1 nghĩa là một module có tính không ổn định tối đa. Nó phụ thuộc vào nhiều thứ, và không có gì phụ thuộc vào nó.
Quy tắc: Điểm không ổn định của một module phải cao hơn điểm của mọi module mà nó import. Các mũi tên của bạn phải hướng về phía có độ không ổn định thấp hơn.
Các tiện ích cấp thấp (low-level utils) (I ≈ 0) không bao giờ được phép import từ các trang cấp cao (high-level pages) (I ≈ 1).
Bạn có thể biến nguyên lý này thành một quy tắc tự động với dependency-cruiser. Thay vì chỉ kiểm tra các vòng lặp, hãy kiểm tra hướng phụ thuộc:
- Ngăn chặn
utilsimport từfeatures. - Ngăn chặn
featuresimport từpages.
Điều này ngăn chặn các điều kiện cấu trúc tạo ra các vòng lặp. Bạn sẽ ngừng việc phải chạy theo sửa lỗi và bắt đầu ngăn ngừa chúng ngay từ đầu.
