Stop Circular Dependencies With SDP
循環参照はサイレントキラーです。ビルドエラーを引き起こすことはありません。インポート時にランタイム例外を発生させることもありません。その代わりに、数週間後に本番環境で現れる、微妙な undefined 値を引き起こします。
モジュールAがBからインポートし、BがAからインポートし返すときにサイクルが発生します。
JavaScriptはこれらのインポートを静かに解決します。モジュールBが要求したときにモジュールAがまだロード中である場合、JavaScriptは空のオブジェクトを返します。その結果、コードは後になって失敗し、スタックトレースは間違った場所を指し示します。
これは dependency-cruiser で解決できます。このツールはファイルをスキャンし、依存関係グラフをマッピングします。TypeScriptやモノレポにも対応しています。
セットアップ方法:
- devDependenciesに追加します:
yarn add -D dependency-cruiser package.jsonにスクリプトを追加します:"depcruise": "depcruise packages --config .dependency-cruiser.js"- サイクルを検知するための
.dependency-cruiser.jsファイルを作成します。
しかし、サイクルを見つけるのは戦いの半分に過ぎません。サイクルが形成されるのを防がなければなりません。
Stable Dependencies Principle (SDP) を活用しましょう。このルールは、安定性の方向に向かって依存しなければならないと定めています。
安定性は構造的な特性です。多くの他のモジュールがそのモジュールに依存しているとき、そのモジュールは安定しています。安定したモジュールを変更すると、多くの人に影響を与えます。このコストが、そのモジュールを安定させているのです。
不安定性(Instability)の公式を使用します: I = Fan-Out / (Fan-In + Fan-Out)
• I = 0 は、モジュールが最大限に安定していることを意味します。すべてがそのモジュールに依存しており、そのモジュールは何にも依存していません。 • I = 1 は、モジュールが最大限に不安定であることを意味します。それは多くのものに依存しており、何もそのモジュールに依存していません。
ルール:あるモジュールの不安定性スコアは、それがインポートするすべてのモジュールのスコアよりも高くなければなりません。依存の矢印は、より低い不安定性の方向に向いている必要があります。
低レベルの utils (I ≈ 0) は、高レベルの pages (I ≈ 1) からインポートしてはいけません。
この原則を dependency-cruiser を使って自動化されたルールに変換できます。単にサイクルをチェックするのではなく、方向性をチェックします:
utilsがfeaturesからインポートするのを防ぐ。featuresがpagesからインポートするのを防ぐ。
これにより、サイクルを生み出す構造的な条件を阻止できます。バグに対処するのではなく、バグを未然に防ぐことができるようになります。
