使用 SDP 杜绝循环依赖
循环依赖是“沉默的杀手”。它们不会导致构建错误,也不会在导入时引发运行时异常。相反,它们会产生微妙的 undefined 值,并在几周后的生产环境中才显现出来。
当模块 A 从 B 导入,而 B 又从 A 导入时,就会发生循环。
JavaScript 会静默地解析这些导入。如果模块 B 请求模块 A 时,模块 A 仍在加载中,JavaScript 会返回一个空对象。这会导致你的代码在稍后运行失败,而堆栈跟踪(stack trace)指向的位置却是错误的。
你可以使用 dependency-cruiser 来解决这个问题。该工具可以扫描你的文件并映射你的依赖图。它支持 TypeScript 和 monorepos。
如何进行设置:
- 将其添加到开发依赖中:
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 表示模块是最大程度不稳定的。它依赖于很多东西,而没有任何东西依赖于它。
规则:一个模块的不稳定性分数必须高于它所导入的每一个模块的分数。你的依赖箭头必须指向不稳定性更低的方向。
你的底层工具函数 (I ≈ 0) 绝不应该从高层页面 (I ≈ 1) 导入。
你可以使用 dependency-cruiser 将这一原则转化为自动化规则。与其仅仅检查循环,不如检查依赖方向:
- 防止
utils从features导入。 - 防止
features从pages导入。
这从根本上阻止了产生循环的结构性条件。你不再是消极地应对 Bug,而是开始主动预防它们。
