“按原样” 软件架构






4.59/5 (17投票s)
本文介绍了从源代码和.NET程序集中自动构建架构图和度量的软件。讨论了Visual C++、Python、Linux内核、Boost和.NET的示例。
引言
是否曾想过架构图与源代码之间是如何关联的?是否曾查看过源代码并真的感到困惑?是否曾看过文件依赖图,但鉴于到处都是混乱的代码而不知其意义?那么,您可能需要一个架构开发环境(ADE)。市面上有许多ADE(Structure101、Lattix、NDepend等),但本文将介绍另一个;但为什么还要编写另一个解决方案呢?答案是对现有解决方案所绘制的图表不满意。
ADE与一般的依赖图的区别在于,它关联了一个层次结构,而这个层次结构不一定与文件系统路径、命名空间或其他源代码工件的层次结构相同。最初,该软件使用了与Visual C++项目关联的过滤器文件(有一个示例,随后被重构);之后,该软件被泛化为仅扫描目录,然后读取make命令的输出——在这两种情况下,用户都必须手动指定一组过滤器。这些泛化中的第一个可以用来显示软件本身的结构,后者是为了处理Linux内核的源代码而实现的。
该软件实现是用Python编写的,它使用插件来提供功能。这些插件通过处理JSON文件来加载和执行。输出选项有:
- doxygen - 为Doxygen/DoxyPress创建输入
- track - 将度量附加到文件
- dependencies - 保存包含来自其他输入选项的状态的JSON文件
输入选项有:
- visualstudio - 读取Microsoft Visual Studio解决方案文件,用于C/C++源代码和过滤器
- generic - 遍历目录结构以查找源代码
- maken - 读取“make -n”或“make V=1”的输出
- dependencies - 读取包含状态的已保存JSON文件
- 例如:来自.NET程序集的类和接口,按命名空间过滤。
支持不同的编程语言,包括C/C++、C#和Python,以及额外的处理。各种示例的源代码可在存储库中找到。
背景理论
图由节点和连接它们的边组成,在有向图中,边有方向。图不一定表示为图形,而可以表示为称为结构矩阵的矩阵。如果缺乏循环,则可以按行选择行的顺序,使得非零项位于对角线的一侧。
假设一个图有N
个节点、E
条边和P
个部分(图的不同部分之间没有边连接到其他部分),则环状复杂度可以定义为E + P - N
。例如,在严格分层的架构中,E + P - N = 0
;对于完全混乱的代码,边的数量是N
的三角形数的两倍,即E + P - N = N * N
。因此,0 <= (E + P - N) / N <= N
。请注意,此公式在将节点拆分为两个由仅一条边连接的节点等代码转换下是不变的。
使用Doxygen可视化
结构矩阵
首先,脚本计算包含文件的过滤器的结构矩阵,并尝试按行排序,使矩阵成为下三角矩阵。请注意,列的顺序与行的顺序相同,对角线元素用斜杠表示。
Linear\Algebra \ Linear\Solvers 1\ Tests\Utils \ Tests\Linear 111\ FEA\Core\Elements 1 \ 1 FEA\Core\Fields 1 1\ FEA\Assembly 1 11\ FEA\Core\SetOfElements 11 \ FEA\Surface 1 1\ FEA\Equations 1 11 11\ FEA\Mesh 1 11 \ FEA\FileIO 1 11 11 1\ Tests\Classes 1 11 11111\ FEA\Solver 11 111 11 \ Tests\App 1 11 1 11111\ App 1 11 11 1 \
在这种情况下,FEA组中存在一个循环会阻止矩阵被排序为下三角形式。
度量表
然后,脚本将过滤器组合成一个树结构。例如,创建FEA\Core
,其有3个子节点,分别为Elements
、Fields
和SetOfElements
。
然后,它计算每个树节点的N
、E + P - N
和(E + P - N) / N
值,并创建一个按(E + P - N) / N
排序的表。
Graphs
树的每个分支都会在HTML文档中创建一个新的部分。例如,在顶层,脚本为GraphViz创建一个输入文件来创建第一个图。每个节点都超链接到文档中相应的节。脚本还计算出E + P - N
在这种情况下为1
。
第二个图显示了FEA子系统,并且架构中存在一个问题。FEA\Surface
对FEA\Core
的依赖创建了一个图中的循环,即存在循环依赖。HTML文档中相应的节还显示E + P - N
的值为8
,并且还指出FEA
本身依赖于线性代数库。
最后,在第三个图中,我们展示了一个叶节点,即由文件之间的相互依赖关系组成的节点。请注意,这不像您通常期望显示的混乱代码,因为我们只显示了少量文件。
跟踪度量
正如已指出的,E + P - N
的值对于简单图等于0
,因此跟踪此度量在所有从源代码生成的图上的总和非常有用。脚本跟踪此值以及最大值,以及两者按N
归一化的值。
脚本plot.py绘制跟踪的值。对于示例所基于的代码,输出在complexity.txt中给出。需要注意的是,注释提到了删除不必要的文件包含以及更改过滤器。
从上述图表中可以看出,重构代码的效果并不是度量的单调递减。它们的作用在于监测长期趋势。
讨论
Visual C++示例
在示例的FEA::Core::Elements(原始,重构后)过滤器中进行了大量的重构。
ElementShape
从IntegrationRule
中分离出来,然后单独分组,而不在上一级创建循环依赖。也就是说,SOLID的单一责任原则得到了应用。
在ElementHandler
中还有一个工厂,它被分离成ElementFactory
,并再次移入自己的组,而不在上一级创建循环依赖。其效果是为ElementHandler
带来一个清晰的继承树。也就是说,图现在与设计意图一致。应该注意的是,这个特定的错误可能已经被归类为反模式。
应用程序自己的源代码
在Python源代码中,使用了不必要的(在动态类型的情况下)导入语句,以便确定正确的依赖结构。
Linux内核
作为压力测试,该软件在Linux内核4.6源代码上运行。显然,没有预先存在的过滤器,而是有大量的代码文件以及将包含目录拆分为包含通用头文件和源文件的经典方式。后一点很重要,因为需要重新分发包含目录的全部内容,以便确定内核源代码的结构。
Boost 1.61库
选择这个C++库进行说明是因为它曾经是一个仅限头文件的库,当只想使用一个小的头文件时,由于需要大量的依赖关系,它会造成很多麻烦。在现代版本中,它使用一些宏来定义头文件,因此在读取源代码时必须相应地替换这些宏。
Mono.Cecil库
选择这个.NET库作为示例,仅仅是因为读取其程序集的工具使用了它。由于该工具不读取源文件,它会创建一个树,叶子代表接口和类,分支代表命名空间。
结论
开发了一个用于生成互相关联的分层架构图和相关度量的工具,并已证明该工具能够更清晰地理解特定Visual C++项目的状态。到目前为止,该代码库已被重构为更灵活的状态。该工具因其插件架构而高度可扩展,并且已泛化为可用于Python源代码、Linux内核和通用输入。
对于给出的主要示例,通过利用Visual Studio作为用于将文件分组的GUI,而不是单独指定一组过滤器,从而增强了可持续性。
历史
- 2016/05/10:首次发布
- 2016/05/24:更新脚本以超链接到源代码,更改复杂性度量并生成度量表。添加了YouTube演示链接。
- 2016/05/29:添加了用于跟踪和处理Linux内核的脚本。修改了主代码,以便文件和文件组可以存在于同一级别。从zip文件中删除了HTML输出,并将读者指向在线内容。随后根据新数据增强了讨论。
- 2016/06/16:重构了工具,将其上传到GitHub,并重写了文章的开头和结尾。
- 2016/07/07:添加了关于Boost和.NET的讨论