Emerge (或emerge-viz )是一种交互式代码分析工具,用于收集有关软件项目的源代码结构、指标、依赖性和复杂性的见解。您可以扫描项目的源代码,计算指标结果和统计数据,生成具有图形结构(例如依赖关系图或文件系统图)的交互式Web应用程序,并以某些文件格式导出结果。 Emerge 目前支持以下语言的解析: C
、 C++
、 Groovy
、 Java
、 JavaScript
、 TypeScript
、 Kotlin
、 ObjC
、 Ruby
、 Swift
、 Python
、 Go
。结构、着色和聚类的计算基于力导向图模拟和 Louvain 模块化相结合的思想。 emerge 主要用 Python 3 编写,并在 macOS、linux 和现代 Web 浏览器(即最新的 Safari、Chrome、Firefox、Edge)上进行了测试。
出现(/ɪˈməːdʒ/)
- 从某物中出来或从某物后面出现
- 出名,尤其是通过检查某事或提出相关问题而出名
该项目的主要目标是创建一个免费/开源工具,任何对软件开发、架构、指标和可视化感兴趣的人都可以轻松使用该工具,以收集有关这些主题的更多见解。它应该促进/支持通过使用探索性方法更好地理解给定的软件项目。
C
、 C++
、 Groovy
、 Java
、 JavaScript
、 TypeScript
、 Kotlin
、 ObjC
、 Ruby
、 Swift
、 Python
Groovy
、 Java
、 Kotlin
、 Swift
git-based
指标(SLOC、空白复杂度、变更耦合)SwiftUI
和Composable
声明性 UI 实体的提取git-based
指标可视化,例如代码流失在预构建的 Docker 容器中使用 emerge 的最简单方法。唯一的先决条件是拥有 Docker 引擎。例如 Docker 桌面。
像这样准备你的工作文件夹
config.yml
?export
?source
运行分析的命令是:
docker run --rm -v <YOUR_WORKING_FOLDER_PATH>:/tmp/emerge achtelik/emerge:2.0.0 /tmp/emerge/config.yml
最后一个参数是 Docker 容器内 config.yml 的路径。
⚡您可以忽略运行结束时的 Pyperclip 错误。
如果您使用上面的建议,请注意您的analyses.source_directory
和export.directory
路径必须以/tmp/emerge
开头。这是必要的,因为您的分析是在 Docker 容器内运行的。
例如:
---
project_name: java_project_example
loglevel: info
analyses:
- analysis_name: full java check
source_directory: /tmp/emerge/source
.
.
.
export:
- directory: /tmp/emerge/export
.
.
.
Docker 容器本身是路径独立的。请随意使用您自己的卷安装和项目配置路径。
基本上有两种安装emerge的方法。如果您熟悉pip
(建议使用pyenv
、 virtualenv
和virtualenvwrapper
的虚拟环境,但不是必需的),您可以简单地通过以下几个步骤安装最新版本的 emerge 。
推荐的方法是使用虚拟环境,您可以使用以下示例来执行此操作:
pyenv install 3.10.0
pyenv virtualenv 3.10.0 venv-3.10.0
pyenv activate venv-3.10.0
您可以简单地使用pip
安装 emerge 。
在 Ubuntu 20.04+ 上,请确保安装了graphviz
和graphviz-dev
软件包,即
apt-get install graphviz graphviz-dev
使用以下命令安装新包:
pip install emerge-viz
或者如果已经安装,只需更新:
pip install -U emerge-viz
然后像这样简单地执行它:
(emerge) user@host ~ % emerge
usage: emerge [-h] [-c YAMLCONFIG] [-v] [-d] [-e] [-a LANGUAGE]
? Welcome to emerge x.y.z (yyyy-mm-dd hh:mm:ss)
options:
-h, --help show this help message and exit
-c YAMLCONFIG, --config YAMLCONFIG
set yaml config file
-v, --verbose set logging level to INFO
-d, --debug set logging level to DEBUG
-e, --error set logging level to ERROR
-a LANGUAGE, --add-config LANGUAGE
add a new config from a template, where LANGUAGE is one of [JAVA, SWIFT, C, CPP, GROOVY, JAVASCRIPT,
TYPESCRIPT, KOTLIN, OBJC, RUBY, PY, GO]
您可以从命令行创建一个简单的临时项目配置,然后只需调整必要的源/导出路径
(emerge) user@host tmp % pwd
/Users/user1/tmp
(emerge) user@host tmp % emerge -a java
✅ created config file from template: /Users/user1/tmp/java-template.yaml
然后只需调整必要的路径( analyses/source_directory
和export/directory
):
(emerge) user@host tmp % cat java-template.yaml
---
project_name: java_project_example
loglevel: info
analyses:
- analysis_name: full java check
source_directory: /Users/user1/emerge/project/source
only_permit_languages:
- java
only_permit_file_extensions:
- .java
file_scan:
- number_of_methods
- source_lines_of_code
- dependency_graph
- fan_in_out
- louvain_modularity
- tfidf
entity_scan:
- dependency_graph
- source_lines_of_code
- number_of_methods
- fan_in_out
- louvain_modularity
- tfidf
export:
- directory: /Users/user1/emerge/project/export
- graphml
- json
- tabular_file
- tabular_console_overall
- d3
(emerge) user@host tmp %
之后,您可以简单地开始扫描
(emerge) user@host tmp % emerge -c java-template.yaml
2021-12-04 21:18:15 analysis I starting to analyze java_project_example
2021-12-04 21:18:15 analysis I ⏩ performing analysis 1/1: full java check
2021-12-04 21:18:15 analysis I starting to create filesystem graph in full java check
2021-12-04 21:18:15 analysis I ⏩ starting scan at directory: ...
...
...
...
2021-12-04 21:18:27 analysis I ✅ all your generated/exported data can be found here: /Users/user1/tmp/java
2021-12-04 21:18:27 analysis I ✅ copy the following path to your browser and start your web app: file:///Users/user1/tmp/java/html/emerge.html
2021-12-04 21:18:27 analysis I ✅ total runtime of analysis: 00:00:10 + 154 ms
现在只需将上述file://
路径复制到任何现代 Web 浏览器并以交互方式公开您配置的代码库
您可以按照以下说明克隆此存储库并安装它:
git clone https://github.com/glato/emerge.git
graphviz
包 brew install graphviz
如果您在 Apple Silicon Mac 上遇到以下错误
pygraphviz/graphviz_wrap.c:2711:10: fatal error: ' graphviz/cgraph.h ' file not found
# include "graphviz/cgraph.h"
^~~~~~~~~~~~~~~~~~~
1 error generated.
您需要运行以下命令一次来更新新的自制环境的 pygraphviz 包含目录
pip install --global-option=build_ext --global-option= " -I $( brew --prefix graphviz ) /include/ " --global-option= " -L $( brew --prefix graphviz ) /lib/ " pygraphviz
请在此处查看该问题的上下文。
检查您的 macOS 上是否安装了最新的 Python 3。我建议安装/使用 Homebrew 中的 Python 3。创建 Python 3 虚拟环境(可选在项目结构内)
cd emerge
pip3 install virtualenv
virtualenv -p python3 venv
安装所需的包并创建 Python 3 虚拟环境(可以选择在项目结构中)
apt-get install python3-venv python3-dev graphviz graphviz-dev
cd emerge
python3 -m venv venv
source venv/bin/activate
使用 pip 安装项目所需的所有依赖项
pip install -r requirements.txt
安装wheel包,然后使用pip安装项目所需的所有依赖项
pip install wheel
pip install -r requirements.txt
从克隆的项目根目录执行以下命令:
python -m unittest discover -v -s ./emerge -p "test_*.py"
否则执行脚本run_tests.py
:
python run_tests.py
如果您在执行测试时遇到任何问题,请检查此解决方法。
emerge
作为独立工具运行 (emerge) user@host emerge % python emerge.py
usage: emerge.py [-h] [-c YAMLCONFIG] [-v] [-d] [-e] [-a LANGUAGE]
? Welcome to emerge x.y.z (yyyy-mm-dd hh:mm:ss)
options:
-h, --help show this help message and exit
-c YAMLCONFIG, --config YAMLCONFIG
set yaml config file
-v, --verbose set logging level to INFO
-d, --debug set logging level to DEBUG
-e, --error set logging level to ERROR
-a LANGUAGE, --add-config LANGUAGE
add a new config from a template, where LANGUAGE is one of [JAVA, SWIFT, C, CPP, GROOVY, JAVASCRIPT,
TYPESCRIPT, KOTLIN, OBJC, RUBY, PY, GO]
让我们快速尝试在自己的代码库上运行emerge
python emerge.py -c configs/emerge.yaml
这应该产生类似的输出:
... analysis I starting to analyze emerge
... analysis I ⏩ performing analysis 1/1: self-check
... analysis I starting to create filesystem graph in self-check
... analysis I ⏩ starting scan at directory: .
... ...
... analysis I the following statistics were collected in self-check
+-------------------------------------+-------------------+
| statistic name | value |
+-------------------------------------+-------------------+
| scanning_runtime | 00:00:00 + 61 ms |
| scanned_files | 32 |
| skipped_files | 176 |
| parsing_hits | 313 |
| parsing_misses | 141 |
| extracted_file_results | 32 |
| file_results_creation_runtime | 00:00:00 + 538 ms |
| number-of-methods-metric-runtime | 00:00:00 + 4 ms |
| source-lines-of-code-metric-runtime | 00:00:00 + 11 ms |
| louvain-modularity-metric-runtime | 00:00:00 + 161 ms |
| fan-in-out-metric-runtime | 00:00:00 + 4 ms |
| total_runtime | 00:00:00 + 786 ms |
+-------------------------------------+-------------------+
... analysis I the following overall metrics were collected in self-check
+----------------------------------------------+----------------------------+
| metric name | value |
+----------------------------------------------+----------------------------+
| avg-number-of-methods-in-file | 13.0 |
| avg-sloc-in-file | 151.41 |
| total-sloc-in-files | 4845 |
| louvain-communities-dependency-graph | 3 |
| louvain-modularity-dependency-graph | 0.21 |
| louvain-biggest-communities-dependency-graph | 0.49, 0.46, 0.05, 0.0, 0.0 |
| avg-fan-in-dependency-graph | 5.55 |
| avg-fan-out-dependency-graph | 5.55 |
| max-fan-in-dependency-graph | 29 |
| max-fan-in-name-dependency-graph | typing |
| max-fan-out-dependency-graph | 19 |
| max-fan-out-name-dependency-graph | emerge/appear.py |
+----------------------------------------------+----------------------------+
... analysis I ✅ all your generated/exported data can be found here: /Users/user1/tmp/python
... analysis I ✅ copy the following path to your browser and start your web app: file:///Users/user1/tmp/python/html/emerge.html
... analysis I ✅ total runtime of analysis: 00:00:00 + 786 ms
现在只需将上述file://
路径复制到任何现代 Web 浏览器,然后以交互方式公开 emerge 代码库
s
选择并突出显示或取消选择特定节点r
重置当前活动的节点选择f
淡出所有未选定的节点,以更加突出显示当前选定的节点现在让我们让这变得更有趣......
如果您想在其他项目上使用emerge,您可以从emerge/configs
目录中简单地复制或自定义现有配置模板之一。
为了快速运行,调整export
中的source_directory
directory
应该足够了。
---
project_name : c-example-project
loglevel : info
analyses :
- analysis_name : check_c_files
source_directory : /Users/user1/emerge/project/source/github/linux-5.8.5/crypto
only_permit_languages :
- c
only_permit_file_extensions :
- .c
- .h
ignore_dependencies_containing :
- string.h
ignore_dependencies_matching :
- ^test_(.*).h$
file_scan :
- number_of_methods
- source_lines_of_code
- dependency_graph
- louvain_modularity
- fan_in_out
- tfidf
export :
- directory : /Users/user1/emerge/project/export
- graphml
- json
- tabular_file
- tabular_console_overall
- d3
自定义当前配置(例如config/c-template.yaml
)或创建自己的配置后,只需使用此新配置再次运行 emerge
python emerge.py -c configs/c-template.yaml
扫描后,您的扫描输出(包括交互式 Web 应用程序)可以在您创建的目录中找到,并在配置参数export
-> directory
中设置,如上面的日志所示。
包含文件和实体扫描的完整 YAML 配置具有以下格式:
---
project_name : java_project_example
loglevel : info
analyses :
- analysis_name : check_java_files_and_classes
source_directory : /Users/user1/emerge/project/source
only_permit_languages :
- java
only_permit_file_extensions :
- .java
ignore_dependencies_containing :
- java.util
file_scan :
- number_of_methods
- source_lines_of_code
- dependency_graph
- fan_in_out
- louvain_modularity
- tfidf
entity_scan :
- dependency_graph
- source_lines_of_code
- number_of_methods
- fan_in_out
- louvain_modularity
- tfidf
export :
- directory : /Users/user1/emerge/project/export
- graphml
- json
- tabular_file
- tabular_console_overall
- d3
有时,排除平台常用的依赖项或对理解项目没有多大贡献的依赖项是有意义的。例如,对于Android 项目来说,以下ignore_dependencies_containing
部分是一个很好的起点:
ignore_dependencies_containing :
- android
- java
- javax
或者对于iOS 项目,以下ignore_entities_containing
部分通常是有意义的,例如不考虑图形输出的 SwiftUI 预览:
ignore_entities_containing :
- _Previews
yaml配置基本上定义在以下几个层面:
钥匙 | 值/描述 |
---|---|
project_name | 所有分析、扫描和导出的项目名称 |
loglevel | 设置日志级别: error (无提示,仅错误), info (包括error )为您提供有关控制流的基本日志, debug (包括info )将产生大量调试日志 |
analyses | 一系列可以单独配置的分析,因此一个项目可以包含一对多分析。 |
钥匙 | 值/描述 |
---|---|
analysis_name | 具体分析名称 |
source_directory | 递归文件扫描应开始的源目录 |
git_directory | git repo 目录(如果应包含 git 指标) |
git_commit_limit | 应该挖掘上次提交的多少提交?默认值: 150 |
git_exclude_merge_commits | 是否应该从挖掘所有指标中排除合并提交?默认值: true |
ignore_files_containing | 从扫描中排除包含给定子字符串的文件名 |
ignore_directories_containing | 从扫描中排除包含给定子字符串的目录名称 |
only_permit_languages | 可能的值包括:java、kotlin、objc、swift、ruby、groovy、javascript、c - 明确阻止除您在此处设置的语言之外的任何其他语言扫描 |
only_permit_file_extensions | 明确允许您在此处设置的以下文件扩展名,例如.java |
only_permit_files_matching_absolute_path | 文件扫描只允许使用以下绝对文件路径列表,例如[/Users/user1/source/file1.java] 。文件应遵循source_directory |
ignore_dependencies_containing | 忽略此子字符串列表中包含的每个依赖项,例如java.util |
ignore_dependencies_matching | 忽略与此子字符串列表中的任何正则表达式匹配的每个依赖项,例如^java.util. |
ignore_entities_containing | 忽略此子字符串列表中包含的每个实体,例如NotRelevantClass |
ignore_entities_matching | 忽略与此子字符串列表中的任何正则表达式匹配的每个实体,例如^Test |
import_aliases | 定义导入别名列表,即替换完整依赖路径中的子字符串,例如"@foo": src/foo 将用src/foo 替换任何@foo 别名 |
override_resolve_dependencies | 如果语言解析器支持,则强制解析此列表中的每个依赖项 |
override_do_not_resolve_dependencies | 如果语言解析器支持,则强制不解析此列表中的每个依赖项(即视为全局依赖项) |
file_scan | 执行文件扫描,包含应应用于每个源文件的指标 |
entity_scan | 执行实体扫描,包含应应用于每个实体(例如每个类)的指标 |
export | 包含应创建为输出的任何导出格式 |
appconfig | 包含任何可配置的应用程序配置参数 |
钥匙 | 值/描述 |
---|---|
dependency_graph | 基于源文件创建依赖图结构,附加指标将添加到图节点 |
source_lines_of_code | 将源代码行指标应用于每个文件,创建总体指标 |
number_of_methods | 将多种方法指标应用于每个文件,创建总体指标 |
fan_in_out | 将扇入/扇出图形指标应用于每个文件,创建总体指标 |
louvain_modularity | 将 louvain 模块化度量应用于每个文件,创建一个整体度量 |
tfidf | 对每个文件应用 tfidf 指标并提取相关语义关键字 |
ws_complexity | 对每个文件应用空白复杂度指标 |
git_metrics | 包含一些基于 git 的指标并尝试将它们应用到每个文件 |
钥匙 | 值/描述 |
---|---|
dependency_graph | 基于从文件中提取的实体创建依赖关系图结构,附加指标将添加到图节点 |
inheritance_graph | 基于从文件中提取的实体创建继承图结构,附加指标将添加到图节点 |
complete_graph | 基于从文件中提取的实体创建完整的图结构(依赖/继承图的联合),附加指标将添加到图节点 |
source_lines_of_code | 将源代码行指标应用于每个实体,创建总体指标 |
number_of_methods | 将多种方法指标应用于每个实体,创建总体指标 |
fan_in_out | 将扇入/扇出图形指标应用于每个实体,创建总体指标 |
louvain_modularity | 将 louvain 模块化度量应用于每个实体,创建一个整体度量 |
tfidf | 将 tfidf 指标应用于每个实体并提取相关的语义关键字 |
钥匙 | 值/描述 |
---|---|
directory | 所有指定导出格式的输出目录 |
graphml | 创建一个 graphML 文件,其中包含映射到图节点的图结构和度量结果 |
tabular_file | 创建一个表格格式的文本文件,其中包含每个指标和统计结果 |
tabular_console | 将表格格式的输出打印到控制台,其中包含每个指标和统计结果 |
tabular_console_overall | 将表格格式的输出打印到控制台,其中仅包含总体指标和统计结果 |
json | 创建一个包含每个指标和统计结果的 JSON 文件 |
d3 | 在子文件夹force-graph-html 中创建一个Bootstrap/D3 Web应用程序,以进行进一步的可视化和交互式/探索性分析 |
钥匙 | 值/描述 |
---|---|
radius_fan_out | 扇出指标的节点半径倍增因子,默认值: 0.1 |
radius_fan_in | 扇入指标的节点半径倍增因子,默认: 0.1 |
radius_louvain | louvain 度量的节点半径倍增因子,默认值: 0.02 |
radius_sloc | sloc 度量的节点半径倍增因子,默认值: 0.005 |
radius_number_of_methods | 方法数量度量的节点半径乘法因子,默认值: 0.05 |
heatmap_sloc_active | sloc 指标是否应该包含在热图得分计算中?默认值: true |
heatmap_fan_out_active | 扇出指标是否应该包含在热图得分计算中?默认值: true |
heatmap_sloc_weight | 热图得分计算中 sloc 指标的权重因子,默认值: 1.5 |
heatmap_fan_out_weight | 热图得分计算中扇出指标的权重因子,默认值: 1.7 |
heatmap_score_base | 热图颜色映射的最小分数阈值,默认: 10 |
heatmap_score_limit | 热图颜色映射的最大分数阈值,默认: 300 |
Emerge 支持以下每种语言的文件扩展名和扫描类型,而file_scan
只是计算指标并将图形结构中的节点映射到扫描的文件, entity_scan
尝试从文件中提取更细粒度的实体,例如类或结构。
文件扩展名 | 语言解析器 | 文件 | 实体 |
---|---|---|---|
.java | 爪哇 | ✅ | ✅ |
.swift | 迅速 | ✅ | ✅ |
.c / .h / .hpp | C | ✅ | |
.cpp / .h / .hpp | C++ | ✅ | |
.groovy | 格罗维 | ✅ | ✅ |
.js / .jsx | JavaScript | ✅ | |
.ts / .tsx | 打字稿 | ✅ | |
.k | 科特林 | ✅ | ✅ |
.m / .h | Objective-C | ✅ | |
.rb | 红宝石 | ✅ | |
.py | Python | ✅ | |
.go | 去 | ✅ |
对此类图表的解释通常非常主观且依赖于项目。以下示例应有助于通过指示器和提示识别某些模式。
揭示模块化的魔力在于将社区检测算法(例如 Louvain 优化)应用于力导向图,以便距离和颜色都会影响结果。以下示例包含模块化代码库的多个指标。
在左侧的第一个示例中,您可以发现多个相干的彩色簇,它们在一定距离内显示出低耦合(= 由力导向图生成)。
在右侧的第二个示例中,使用激活的集群外壳呈现相同的图形。在此示例中,船体显示出很少的重叠或没有重叠。这些提示可以作为良好软件架构的指标,例如在模块化、抽象和定义良好的接口方面。
“一个大泥球结构随意,蔓延,草率,胶带和捞线,意大利面条代码丛林”(B. Foote,J. Yoder,1997)。这种图通常代表不太理想的架构。为了验证这种意大利面条式的代码丛林,只需对所有集群启用外壳渲染即可最终确定:毕竟只有一个大集群。
有时,如果忽略不相关的依赖关系,可以帮助更好地理解软件架构的复杂性。
ignore_dependencies_containing
(如果您更喜欢正则表达式,则使用ignore_dependencies_matching
)配置性地删除不相关的依赖项。通过相对激活的扇出指标,人们可以识别出更多的散射、一些遥远的中心节点和更清晰的集群。所有这些都是潜在的真实(=通常更容易理解)架构的线索。