依赖结构矩阵(DSM)
简介
DSM(依赖结构矩阵)是一种表示和浏览组件之间依赖关系的紧凑方式。对于大多数工程师来说,谈论依赖关系就是谈论如下所示的东西:

DSM 用于表示与图相同的信息。
- 矩阵头部元素表示图框
- 矩阵中非空的单元格对应图中的箭头。
因此,在下面的快照中,从以下开始的耦合 净 到 Foundation 在矩阵中由非空单元格表示,在图中由箭头表示。

为什么要用两种不同的方式——图和 DSM——来表示相同的信息?因为存在权衡:
- 图更直观,但当节点和边的数量增长时可能变得完全无法理解(几十个方框就足以生成过于复杂的图)
- DSM 不太直观,但在表示大型复杂图时非常高效。我们说 DSM 扩展 与图比较。
一旦理解了 DSM 的原理,人们通常更喜欢用 DSM 而不是图来表示依赖关系。这主要是因为 DSM 提供了以下可能性 一眼发现结构模式 这将在本文档的后半部分进行说明。
CppDepend提供 上下文相关帮助 以向用户解释他在 DSM 上看到的内容。CppDepend 的 DSM 依赖于简单的 3 色单元格配色方案:蓝色、绿色和黑色。当鼠标悬停在某行或某列上时,上下文相关帮助会解释此配色方案的含义:

非空的 DSM 单元格包含一个数字。此数字表示该单元格所代表的耦合强度。根据选项的实际值,耦合强度可以用参与耦合的成员/方法/字段/类型或命名空间的数量来表示 单元格权重 除了上下文相关帮助外,DSM 还提供 信息面板 用简单英语描述解释耦合的指标:

CppDepend 的 DSM 提供众多可尝试的选项:
- 它提供了许多深入探索依赖关系的功能(可以打开父列/行,可以展开单元格……)
- 它可以处理方形对称 DSM 和矩形非对称 DSM
- 水平和垂直标题可以绑定,以始终保持方形对称矩阵
- 它带有选项 间接使用,其中单元格显示直接和间接使用
- 垂直标题可以包含层级代码元素
- ...
建议您通过分析代码库中的依赖关系来亲自体验所有这些功能。
在矩阵上识别代码结构模式
如简介中所述,DSM 的特点是能够轻松识别常见的代码结构模式。让我们介绍最常见的场景:
分层代码
DSM使一个明显的模式是 分层结构 (即无环结构)。当矩阵呈三角形,所有蓝色单元格位于左下三角形,所有绿色单元格位于右上三角形时,则表明该结构是完全分层的。换句话说,该结构不包含任何依赖循环。

在快照的右侧,相同的分层结构用图表示。所有箭头都指向相同的从左到右的方向。图的问题在于图布局无法扩展。在这里,我们几乎看不到结构的全貌。如果方框数量翻倍,图将完全无法阅读。另一方面,DSM 表示不会受到影响;我们说它 DSM比图更具扩展性.
附注:有趣的是,大多数图布局算法都依赖于图是无环的这一事实。为了计算带循环的图的布局,这些算法会暂时丢弃一些依赖关系以处理分层图,然后在计算的最后一步重新添加被丢弃的依赖关系。
依赖循环
如果结构包含循环,该循环将在 DSM 上以红色方块显示。我们可以看到,在红色方块内部,绿色和蓝色单元格在对角线两侧混杂。还有一些黑色单元格代表相互直接使用(即 A 使用 B 且 B 使用 A)。

CppDepend 的 DSM 提供独特的选项 间接依赖。A 和 B 之间的间接依赖意味着 A 使用了某个东西,那个东西使用了某个东西,那个东西又使用了某个东西……最终使用了 B。下面显示的是带有循环的同一个 DSM,但处于间接模式。我们可以看到红色方块里只填充了黑色单元格。这只是意味着对于循环中的任意元素 A 和 B,A 和 B 是间接且相互依赖的。

这是用图表示的相同结构。红色箭头显示多个元素相互依赖。但该图无助于突出显示父循环中涉及的所有元素。

请注意,在 CppDepend 中,我们提供了一个按钮来突出显示 DSM 中的循环(如果有)。如果结构是分层的,则此按钮的效果是将矩阵三角化,并使非空单元格尽可能靠近对角线。

高内聚 - 低耦合
高内聚(组件内部)/ 低耦合(组件之间)的理念如今很流行。但如果无法测量和可视化依赖关系,就很难对內聚性和耦合性进行具体评估。DSM 擅长显示高内聚性。在下面的 DSM 中,对角线周围显示出一个明显的方形聚合。这意味着方形中涉及的元素具有高内聚性:它们彼此强烈依赖。此外,由于没有循环,我们可以看到它们是分层的。它们当然是分组到父工件(如命名空间或程序集)中的候选者。
另一方面,正方形周围大多数单元格为空这一事实表明正方形的元素与其他元素之间是低耦合的。

在下面的 DSM 中,我们可以看到 2 个具有高内聚性的组件(上部和下部方块),它们之间的耦合度相当低。

在重构时,拥有这样的指标对于了解是否有机会将粗粒度组件拆分为多个更细粒度的组件非常有用。
职责过多
该 单一职责原则 (SRP) 如今在软件架构师社区中越来越受欢迎。该原则指出: 一个类不应该有超过一个变更理由。解释 SRP 的另一种方式是:一个类不应该使用太多不同的其他类型。如果我们将这个想法扩展到其他层级(程序集、命名空间和方法),那么可以肯定,如果一个代码元素使用了几十个其他不同的代码元素(在同一层级),它就承担了过多的职责。通常使用术语 上帝类 或 上帝组件 用于评定此类代码。
DSM 可以帮助定位承担过多职责的代码元素。这样的代码元素由包含许多蓝色单元格的列和包含许多绿色单元格的行表示。下面的 DSM 展示了这一现象。

热门代码元素
一个流行的代码元素会被许多其他代码元素使用。流行的代码元素是不可避免的(想想 字符串 例如类),但流行的代码元素并不是缺陷。它只是意味着在每个代码库中,都有一些由流行类表示的核心概念。
流行的代码元素由包含许多绿色单元格的列和包含许多蓝色单元格的行表示。下面的 DSM 突出显示了一个流行的代码元素。

需要注意的是,当保持代码结构完全分层时,流行的组件自然会保持在低层级。确实,一个流行的组件实际上不能使用很多东西,因为流行的组件处于低层级,它们不能使用更高层级的东西。这将产生从低层级到高层级的依赖,从而破坏结构的无环特性。
相互依赖
您可以通过右键点击非空单元格并选择菜单来查看 2 个组件之间的耦合 打开此依赖.

如果打开的单元格是黑色的,如上面的快照所示(即 A 和 B 相互依赖),那么生成的矩形矩阵将同时包含绿色和蓝色单元格(最终也可能包含黑色单元格),如下面的快照所示。

在这种情况下,您经常会注意到绿色或蓝色单元格的不足(这里 3 个蓝色单元格对应 1 个绿色单元格)。这是因为即使两个代码元素相互依赖,它们之间通常也存在自然的层级顺序。例如,考虑 System.Threading 命名空间和 System.String 类。它们相互依赖;彼此都依赖对方。但矩阵显示 线程 更依赖于 字符串 比相反方向更多(蓝色单元格远多于绿色单元格)。这证实了以下直觉 线程 比更高层 字符串.

