Emerge (ou emerge-viz ) é uma ferramenta interativa de análise de código para coletar insights sobre a estrutura do código-fonte, métricas, dependências e complexidade de projetos de software. Você pode digitalizar o código-fonte de um projeto, calcular resultados de métricas e estatísticas, gerar um aplicativo web interativo com estruturas gráficas (por exemplo, um gráfico de dependência ou um gráfico de sistema de arquivos) e exportar os resultados em alguns formatos de arquivo. Emerge atualmente tem suporte de análise para as seguintes linguagens: C
, C++
, Groovy
, Java
, JavaScript
, TypeScript
, Kotlin
, ObjC
, Ruby
, Swift
, Python
, Go
. A estrutura, coloração e agrupamento são calculados e baseados na ideia de combinar uma simulação gráfica direcionada por força e modularidade Louvain. emerge é escrito principalmente em Python 3 e é testado em macOS, Linux e navegadores modernos (ou seja, Safari, Chrome, Firefox, Edge mais recentes).
emergir (/ɪˈməːdʒ/)
- aparecer saindo de algo ou por trás de algo
- tornar-se conhecido, especialmente como resultado de examinar algo ou fazer perguntas sobre isso
O principal objetivo deste projeto é criar uma ferramenta gratuita/de código aberto, que possa ser facilmente utilizada por qualquer pessoa com interesse em desenvolvimento de software, arquitetura, métricas e visualização para coletar mais insights sobre esses tópicos. Deve facilitar/apoiar a obtenção de uma melhor compreensão de um determinado projeto de software usando uma abordagem exploratória.
C
, C++
, Groovy
, Java
, JavaScript
, TypeScript
, Kotlin
, ObjC
, Ruby
, Swift
, Python
Groovy
, Java
, Kotlin
, Swift
git-based
(SLOC, Whitespace Complexity, Change Coupling)SwiftUI
e Composable
git-based
por exemplo, rotatividade de códigoA maneira mais fácil de usar emerge em um contêiner Docker pré-construído. O único pré-requisito é ter um mecanismo Docker. Por exemplo, o Docker Desktop.
Prepare sua pasta de trabalho assim
config.yml
?export
?source
O comando para executar a análise é:
docker run --rm -v <YOUR_WORKING_FOLDER_PATH>:/tmp/emerge achtelik/emerge:2.0.0 /tmp/emerge/config.yml
O último parâmetro é o caminho para config.yml dentro do contêiner Docker.
⚡Você pode ignorar o erro Pyperclip no final da execução.
Se você usar a sugestão acima, preste atenção que seu caminho analyses.source_directory
e export.directory
deve começar com /tmp/emerge
. Isso é necessário porque sua análise está sendo executada dentro do contêiner Docker.
Por exemplo:
---
project_name: java_project_example
loglevel: info
analyses:
- analysis_name: full java check
source_directory: /tmp/emerge/source
.
.
.
export:
- directory: /tmp/emerge/export
.
.
.
O próprio contêiner Docker é independente do caminho. Sinta-se à vontade para usar sua própria montagem de volume e caminhos de configuração do projeto.
Basicamente, existem duas maneiras de instalar o emerge. Se você estiver familiarizado com pip
(um ambiente virtual usando pyenv
, virtualenv
e virtualenvwrapper
é recomendado, mas não é necessário), você pode simplesmente instalar a versão mais recente do emerge com as etapas a seguir.
A forma recomendada seria usar um ambiente virtual, você pode fazer isso usando o seguinte exemplo:
pyenv install 3.10.0
pyenv virtualenv 3.10.0 venv-3.10.0
pyenv activate venv-3.10.0
Você pode simplesmente instalar o emerge usando pip
.
No Ubuntu 20.04+, certifique-se de que os pacotes graphviz
e graphviz-dev
estejam instalados, ou seja
apt-get install graphviz graphviz-dev
Instale como novo pacote com:
pip install emerge-viz
ou se já estiver instalado, basta atualizar com:
pip install -U emerge-viz
e então simplesmente execute assim:
(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]
Você pode criar uma configuração adhoc de projeto simples a partir da linha de comando e simplesmente ajustar os caminhos de origem/exportação necessários
(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
e então simplesmente ajuste os caminhos necessários ( analyses/source_directory
e 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 %
Depois disso, você pode simplesmente iniciar uma verificação
(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
Agora basta copiar o caminho file://
mencionado acima para qualquer navegador moderno e exportar interativamente sua base de código configurada
Você pode clonar este repositório e instalá-lo seguindo estas instruções:
git clone https://github.com/glato/emerge.git
graphviz
primeiro brew install graphviz
Se você encontrar o seguinte erro em um Apple Silicon Mac
pygraphviz/graphviz_wrap.c:2711:10: fatal error: ' graphviz/cgraph.h ' file not found
# include "graphviz/cgraph.h"
^~~~~~~~~~~~~~~~~~~
1 error generated.
você precisa executar o seguinte comando uma vez para atualizar os diretórios de inclusão do pygraphviz para o novo ambiente homebrew
pip install --global-option=build_ext --global-option= " -I $( brew --prefix graphviz ) /include/ " --global-option= " -L $( brew --prefix graphviz ) /lib/ " pygraphviz
Veja o problema no contexto aqui.
Verifique se você tem o Python 3 mais recente instalado em seu macOS. Eu recomendo instalar/usar Python 3 do Homebrew. Crie um ambiente virtual Python 3 (opcionalmente dentro da estrutura do projeto)
cd emerge
pip3 install virtualenv
virtualenv -p python3 venv
Instale os pacotes necessários e crie um ambiente virtual Python 3 (opcionalmente dentro da estrutura do projeto)
apt-get install python3-venv python3-dev graphviz graphviz-dev
cd emerge
python3 -m venv venv
source venv/bin/activate
Instale todas as dependências necessárias para o projeto com pip
pip install -r requirements.txt
Instale o pacote wheel, depois instale todas as dependências necessárias para o projeto com pip
pip install wheel
pip install -r requirements.txt
Execute o seguinte na raiz do projeto clonado:
python -m unittest discover -v -s ./emerge -p "test_*.py"
caso contrário, execute o script run_tests.py
:
python run_tests.py
Se você tiver algum problema ao executar os testes, verifique esta solução.
emerge
como uma ferramenta autônoma (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]
Vamos tentar executar rapidamente o emerge em sua própria base de código
python emerge.py -c configs/emerge.yaml
Isso deve produzir uma saída semelhante:
... 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
Agora basta copiar o caminho file://
mencionado acima para qualquer navegador moderno e exportar interativamente a base de código emerge
s
para selecionar e destacar ou desmarcar um nó específicor
f
E agora vamos tornar isso mais interessante...
Se quiser usar o emerge em outros projetos, você pode simplesmente copiar ou personalizar um dos modelos de configuração existentes do diretório emerge/configs
.
Para uma execução rápida, basta ajustar source_directory
, directory
em export
.
---
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
Depois de personalizar uma configuração atual (por exemplo, config/c-template.yaml
) ou criar a sua própria, basta executar emerge novamente com esta nova configuração
python emerge.py -c configs/c-template.yaml
Após a verificação, a saída da verificação (incluindo seu aplicativo Web interativo) pode ser encontrada no diretório que você criou e definiu no parâmetro de configuração export
-> directory
, conforme visto nos logs acima.
Uma configuração YAML completa que contém varredura de arquivo e entidade tem o seguinte formato:
---
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
Às vezes pode fazer sentido excluir dependências usuais da plataforma ou dependências que não contribuem muito para a compreensão de um projeto. Um bom ponto de partida para, por exemplo, um projeto Android poderia ser a seguinte seção ignore_dependencies_containing
:
ignore_dependencies_containing :
- android
- java
- javax
ou para um projeto iOS, a seguinte seção ignore_entities_containing
geralmente faz sentido, por exemplo, para não considerar as visualizações do SwiftUI para a saída do gráfico:
ignore_entities_containing :
- _Previews
A configuração do yaml é basicamente definida nos seguintes níveis:
chave | valor/ descrição |
---|---|
project_name | um nome de projeto para todas as análises, verificações e exportações |
loglevel | definir um loglevel: error (silencioso, apenas erros), info (inclui error ) fornece logs básicos sobre o fluxo de controle, debug (inclui info ) produzirá muitos logs de depuração |
analyses | uma série de análises que podem ser configuradas individualmente, portanto, um projeto pode conter uma ou várias análises. |
chave | valor/ descrição |
---|---|
analysis_name | um nome de análise específico |
source_directory | o diretório de origem onde a verificação recursiva de arquivos deve começar |
git_directory | o diretório git repo, se as métricas git devem ser incluídas |
git_commit_limit | quantos commits do último commit devem ser extraídos? padrão: 150 |
git_exclude_merge_commits | os commits de mesclagem devem ser excluídos da mineração de todas as métricas? padrão: true |
ignore_files_containing | excluir nomes de arquivos da verificação que contenham as substrings fornecidas |
ignore_directories_containing | excluir nomes de diretórios da varredura que contenham as substrings fornecidas |
only_permit_languages | os valores possíveis incluem: java, kotlin, objc, swift, ruby, groovy, javascript, c - impede explicitamente a verificação de qualquer outra linguagem além daquela que você definiu aqui |
only_permit_file_extensions | permitir explicitamente as seguintes extensões de arquivo definidas aqui, por exemplo, .java |
only_permit_files_matching_absolute_path | apenas a seguinte lista de caminhos de arquivo absolutos é permitida para a verificação de arquivos, por exemplo, [/Users/user1/source/file1.java] . Os arquivos devem seguir source_directory |
ignore_dependencies_containing | ignore todas as dependências incluídas nesta lista de substrings, por exemplo, java.util |
ignore_dependencies_matching | ignore todas as dependências que correspondam a qualquer uma das expressões regulares nesta lista de substrings, por exemplo ^java.util. |
ignore_entities_containing | ignore todas as entidades incluídas nesta lista de substrings, por exemplo, NotRelevantClass |
ignore_entities_matching | ignore todas as entidades que correspondam a qualquer uma das expressões regulares nesta lista de substrings, por exemplo ^Test |
import_aliases | defina uma lista de aliases de importação, ou seja, substitua substrings dentro de um caminho de dependência completo, por exemplo, "@foo": src/foo substituirá qualquer alias @foo por src/foo |
override_resolve_dependencies | se suportado pelo analisador de linguagem, força a resolução de todas as dependências desta lista |
override_do_not_resolve_dependencies | se suportado pelo analisador de linguagem, força todas as dependências nesta lista a NÃO serem resolvidas (ou seja, tratadas como uma dependência global) |
file_scan | realizar uma verificação de arquivo, contém as métricas que devem ser aplicadas em cada arquivo de origem |
entity_scan | realizar uma varredura de entidade, contém as métricas que devem ser aplicadas em cada entidade (por exemplo, em cada classe) |
export | contém quaisquer formatos de exportação que devem ser criados como saída |
appconfig | contém quaisquer parâmetros de configuração do aplicativo configuráveis |
chave | valor/ descrição |
---|---|
dependency_graph | criar uma estrutura de gráfico de dependência baseada em arquivos de origem, métricas adicionais serão adicionadas aos nós do gráfico |
source_lines_of_code | aplicar uma métrica de linhas de código fonte a cada arquivo, criar uma métrica geral |
number_of_methods | aplicar uma série de métricas de métodos a cada arquivo, criar uma métrica geral |
fan_in_out | aplicar uma métrica gráfica de fan in/fan out a cada arquivo, criar uma métrica geral |
louvain_modularity | aplique uma métrica de modularidade Louvain a cada arquivo, crie uma métrica geral |
tfidf | aplique uma métrica tfidf a cada arquivo e extraia palavras-chave semânticas relevantes |
ws_complexity | aplique uma métrica de complexidade de espaço em branco a cada arquivo |
git_metrics | inclua algumas métricas baseadas em git e tente aplicá-las a cada arquivo |
chave | valor/ descrição |
---|---|
dependency_graph | criar uma estrutura de gráfico de dependência baseada em entidades extraídas de arquivos, métricas adicionais serão adicionadas aos nós do gráfico |
inheritance_graph | criar uma estrutura de gráfico de herança baseada em entidades extraídas de arquivos, métricas adicionais serão adicionadas aos nós do gráfico |
complete_graph | crie uma estrutura gráfica completa (união de gráfico de dependência/herança) com base em entidades extraídas de arquivos, métricas adicionais serão adicionadas aos nós do gráfico |
source_lines_of_code | aplicar uma métrica de linhas de código fonte a cada entidade, criar uma métrica geral |
number_of_methods | aplicar uma série de métricas de métodos a cada entidade, criar uma métrica geral |
fan_in_out | aplicar uma métrica gráfica de fan in/fan out a cada entidade, criar uma métrica geral |
louvain_modularity | aplicar uma métrica de modularidade Louvain a cada entidade, criar uma métrica geral |
tfidf | aplique uma métrica tfidf a cada entidade e extraia palavras-chave semânticas relevantes |
chave | valor/ descrição |
---|---|
directory | o diretório de saída para todos os formatos de exportação especificados |
graphml | crie um arquivo graphML que contenha a estrutura do gráfico e os resultados da métrica mapeados para os nós do gráfico |
tabular_file | crie um arquivo de texto formatado tabular que contenha todos os resultados de métricas e estatísticas |
tabular_console | imprimir uma saída formatada tabular no console que contém todos os resultados de métricas e estatísticas |
tabular_console_overall | imprimir uma saída formatada tabular no console que contém apenas métricas gerais e resultados estatísticos |
json | crie um arquivo JSON que contenha todos os resultados de métricas e estatísticas |
d3 | crie um aplicativo da web Bootstrap/D3 na subpasta force-graph-html para análise visual e interativa/exploratória adicional |
chave | valor/ descrição |
---|---|
radius_fan_out | fator de multiplicação do raio do nó para a métrica de distribuição, padrão: 0.1 |
radius_fan_in | fator de multiplicação do raio do nó para a métrica de fan-in, padrão: 0.1 |
radius_louvain | fator de multiplicação do raio do nó para a métrica Louvain, padrão: 0.02 |
radius_sloc | fator de multiplicação do raio do nó para a métrica sloc, padrão: 0.005 |
radius_number_of_methods | fator de multiplicação do raio do nó para a métrica do número de métodos, padrão: 0.05 |
heatmap_sloc_active | a métrica sloc deve ser incluída no cálculo da pontuação do mapa de calor? padrão: true |
heatmap_fan_out_active | a métrica de distribuição deve ser incluída no cálculo da pontuação do mapa de calor? padrão: true |
heatmap_sloc_weight | fator de peso da métrica sloc no cálculo da pontuação do mapa de calor, padrão: 1.5 |
heatmap_fan_out_weight | fator de peso da métrica de distribuição no cálculo da pontuação do mapa de calor, padrão: 1.7 |
heatmap_score_base | limite mínimo de pontuação para o mapeamento de cores do mapa de calor, padrão: 10 |
heatmap_score_limit | limite máximo de pontuação para o mapeamento de cores do mapa de calor, padrão: 300 |
Emerge suporta as seguintes extensões de arquivo e tipos de varredura por idioma, enquanto um file_scan
simplesmente calcula métricas e mapeia nós dentro de estruturas gráficas para arquivos varridos e entity_scan
tenta extrair entidades mais refinadas de arquivos, por exemplo, classes ou estruturas.
Extensão de arquivo | Analisador de linguagem | Arquivos | Entidades |
---|---|---|---|
.java | Java | ✅ | ✅ |
.swift | Rápido | ✅ | ✅ |
.c / .h / .hpp | C | ✅ | |
.cpp / .h / .hpp | C++ | ✅ | |
.groovy | Legal | ✅ | ✅ |
.js / .jsx | JavaScript | ✅ | |
.ts / .tsx | Texto datilografado | ✅ | |
.k | Kotlin | ✅ | ✅ |
.m / .h | Objetivo-C | ✅ | |
.rb | Rubi | ✅ | |
.py | Pitão | ✅ | |
.go | Ir | ✅ |
A interpretação de tais gráficos pode muitas vezes ser muito subjetiva e dependente do projeto. Os exemplos a seguir devem ajudar a reconhecer certos padrões através de indicadores e dicas.
A magia de descobrir a modularidade reside na aplicação de um algoritmo de detecção de comunidade, por exemplo, otimização de Louvain, a um gráfico direcionado por força, de modo que tanto as distâncias quanto a coloração influenciem o resultado. O exemplo a seguir inclui vários indicadores para uma base de código modular.
No primeiro exemplo à esquerda você pode identificar vários aglomerados coloridos coerentes que mostram um acoplamento baixo por uma certa distância (= gerado pelo gráfico direcionado à força).
No segundo exemplo à direita, o mesmo gráfico é renderizado com clusters ativados. Neste exemplo, os cascos apresentam sobreposição mínima ou nenhuma. Tais dicas podem ser indicadores de uma boa arquitetura de software, por exemplo, em termos de modularidade , abstração e interfaces bem definidas.
"UMA BIG BALL OF MUD é estruturada ao acaso, espalhada, desleixada, com fita adesiva e arame farpado, selva de código espaguete" (B. Foote, J. Yoder, 1997). Esse tipo de gráfico geralmente representa uma arquitetura menos ideal. Para verificar esse tipo de selva de código espaguete , basta ativar a renderização do casco para todos os clusters para finalmente determinar: afinal, existe apenas um grande cluster.
Às vezes, entender melhor a complexidade de uma arquitetura de software pode ajudar se dependências irrelevantes forem ignoradas.
ignore_dependencies_containing
(ou ignore_dependencies_matching
se preferir expressões regulares). Com uma métrica de distribuição comparativamente ativada, reconhece-se mais dispersão, alguns nós centrais distantes e clusters mais claros. Todas essas são pistas possíveis para a arquitetura real (= muitas vezes mais compreensível) subjacente.