Emerge (o emerge-viz ) es una herramienta interactiva de análisis de código para recopilar información sobre la estructura del código fuente, métricas, dependencias y complejidad de los proyectos de software. Puede escanear el código fuente de un proyecto, calcular resultados métricos y estadísticas, generar una aplicación web interactiva con estructuras gráficas (por ejemplo, un gráfico de dependencia o un gráfico de sistema de archivos) y exportar los resultados en algunos formatos de archivo. Actualmente, Emerge tiene soporte de análisis para los siguientes lenguajes: C
, C++
, Groovy
, Java
, JavaScript
, TypeScript
, Kotlin
, ObjC
, Ruby
, Swift
, Python
, Go
. La estructura, la coloración y la agrupación se calculan y se basan en la idea de combinar una simulación de gráfico dirigida por fuerza y la modularidad de Louvain. emerge está escrito principalmente en Python 3 y se prueba en macOS, Linux y navegadores web modernos (es decir, los últimos Safari, Chrome, Firefox, Edge).
emerger (/ɪˈməːdʒ/)
- aparecer saliendo de algo o saliendo de detrás de algo
- Darse a conocer, especialmente como resultado de examinar algo o hacer preguntas sobre ello.
El objetivo principal de este proyecto es crear una herramienta gratuita/de código abierto, que pueda ser utilizada fácilmente por cualquier persona interesada en el desarrollo de software, la arquitectura, las métricas y la visualización para recopilar más información sobre esos temas. Debería facilitar/apoyar la obtención de una mejor comprensión de un proyecto de software determinado mediante el uso de un enfoque exploratorio.
C
, C++
, Groovy
, Java
, JavaScript
, TypeScript
, Kotlin
, ObjC
, Ruby
, Swift
, Python
Groovy
, Java
, Kotlin
, Swift
git-based
(SLOC, Whitespace Complexity, Change Coupling)SwiftUI
y Composable
.git-based
por ejemplo, rotación de códigoLa forma más sencilla de utilizar emerge en un contenedor Docker precompilado. El único requisito previo es tener un motor Docker. Por ejemplo, el escritorio Docker.
Prepare su carpeta de trabajo así
config.yml
?export
?source
El comando para ejecutar el análisis es el siguiente:
docker run --rm -v <YOUR_WORKING_FOLDER_PATH>:/tmp/emerge achtelik/emerge:2.0.0 /tmp/emerge/config.yml
El último parámetro es la ruta al config.yml dentro del contenedor Docker.
⚡Puedes ignorar el error de Pyperclip al final de la ejecución.
Si utiliza la sugerencia anterior, preste atención a que la ruta analyses.source_directory
y export.directory
deben comenzar con /tmp/emerge
. Esto es necesario porque su análisis se ejecuta dentro del contenedor Docker.
Por ejemplo:
---
project_name: java_project_example
loglevel: info
analyses:
- analysis_name: full java check
source_directory: /tmp/emerge/source
.
.
.
export:
- directory: /tmp/emerge/export
.
.
.
El contenedor Docker en sí es independiente de la ruta. Siéntase libre de utilizar sus propias rutas de configuración de proyecto y montaje de volumen.
Básicamente hay dos formas de instalar emerge. Si está familiarizado con pip
(se recomienda un entorno virtual usando pyenv
, virtualenv
y virtualenvwrapper
, pero no es necesario), simplemente puede instalar la última versión de emerge con los siguientes pasos.
La forma recomendada sería utilizar un entorno virtual; puede hacerlo utilizando el siguiente ejemplo:
pyenv install 3.10.0
pyenv virtualenv 3.10.0 venv-3.10.0
pyenv activate venv-3.10.0
Simplemente puedes instalar emerge usando pip
.
En Ubuntu 20.04+, asegúrese de que los paquetes graphviz
y graphviz-dev
estén instalados, es decir
apt-get install graphviz graphviz-dev
Instálelo como un paquete nuevo con:
pip install emerge-viz
o si ya está instalado, simplemente actualiza con:
pip install -U emerge-viz
y luego simplemente ejecútelo así:
(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]
Puede crear una configuración de proyecto ad hoc simple desde la línea de comando y luego simplemente ajustar las rutas de origen/exportación necesarias.
(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
y luego simplemente ajuste las rutas necesarias ( analyses/source_directory
y 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 %
Después de esto, simplemente puede iniciar un escaneo
(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
Ahora simplemente copie la ruta file://
mencionada anteriormente a cualquier navegador web moderno y exponga interactivamente su código base configurado.
Puede clonar este repositorio e instalarlo siguiendo estas instrucciones:
git clone https://github.com/glato/emerge.git
graphviz
brew install graphviz
Si encuentra el siguiente error en una Mac Apple Silicon
pygraphviz/graphviz_wrap.c:2711:10: fatal error: ' graphviz/cgraph.h ' file not found
# include "graphviz/cgraph.h"
^~~~~~~~~~~~~~~~~~~
1 error generated.
debe ejecutar el siguiente comando una vez para actualizar los directorios de inclusión de pygraphviz para el nuevo entorno homebrew
pip install --global-option=build_ext --global-option= " -I $( brew --prefix graphviz ) /include/ " --global-option= " -L $( brew --prefix graphviz ) /lib/ " pygraphviz
Vea el problema en contexto aquí.
Compruebe que tiene instalado el último Python 3 en su macOS. Recomiendo instalar/usar Python 3 de Homebrew. Cree un entorno virtual Python 3 (opcionalmente dentro de la estructura del proyecto)
cd emerge
pip3 install virtualenv
virtualenv -p python3 venv
Instale los paquetes necesarios y cree un entorno virtual Python 3 (opcionalmente dentro de la estructura del proyecto)
apt-get install python3-venv python3-dev graphviz graphviz-dev
cd emerge
python3 -m venv venv
source venv/bin/activate
Instale todas las dependencias requeridas para el proyecto con pip
pip install -r requirements.txt
Instale el paquete de ruedas, luego instale todas las dependencias requeridas para el proyecto con pip
pip install wheel
pip install -r requirements.txt
Ejecute lo siguiente desde la raíz del proyecto clonado:
python -m unittest discover -v -s ./emerge -p "test_*.py"
de lo contrario, ejecute el script run_tests.py
:
python run_tests.py
Si tuvo algún problema al ejecutar las pruebas, consulte esta solución.
emerge
como una herramienta independiente (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]
Intentemos rápidamente ejecutar emerge en su propia base de código.
python emerge.py -c configs/emerge.yaml
Esto debería producir un resultado similar:
... 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
Ahora simplemente copie la ruta file://
mencionada anteriormente a cualquier navegador web moderno y exponga interactivamente el código base emergente.
s
para seleccionar y resaltar o anular la selección de un nodo específicor
f
Y ahora hagamos esto más interesante...
Si desea utilizar emerge en otros proyectos, puede simplemente copiar o personalizar una de las plantillas de configuración existentes desde el directorio emerge/configs
.
Para una ejecución rápida, debería ser suficiente ajustar source_directory
, directory
en 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
Después de personalizar una configuración actual (por ejemplo, config/c-template.yaml
) o crear la suya propia, simplemente ejecute emerge nuevamente con esta nueva configuración.
python emerge.py -c configs/c-template.yaml
Después del escaneo, el resultado del escaneo (incluida su aplicación web interactiva) se puede encontrar en el directorio que creó y configuró en el parámetro de configuración export
-> directory
, como se ve en los registros anteriores.
Una configuración YAML completa que contiene análisis de archivos y entidades tiene el siguiente 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
A veces puede tener sentido excluir dependencias habituales de la plataforma o dependencias que no contribuyen mucho a la comprensión de un proyecto. Un buen punto de partida para, por ejemplo, un proyecto de Android podría ser la siguiente sección ignore_dependencies_containing
:
ignore_dependencies_containing :
- android
- java
- javax
o para un proyecto de iOS, la siguiente sección ignore_entities_containing
a menudo tiene sentido, por ejemplo, no considerar las vistas previas de SwiftUI para la salida del gráfico:
ignore_entities_containing :
- _Previews
La configuración de yaml se define básicamente en los siguientes niveles:
llave | valor/descripción |
---|---|
project_name | un nombre de proyecto para todos los análisis, escaneos y exportaciones |
loglevel | establezca un nivel de registro: error (silencioso, solo errores), info (incluye error ) le brinda registros básicos sobre el flujo de control, debug (incluye info ) producirá muchos registros de depuración |
analyses | una serie de análisis que se pueden configurar individualmente, por lo que un proyecto puede contener uno o varios análisis. |
llave | valor/descripción |
---|---|
analysis_name | un nombre de análisis específico |
source_directory | el directorio de origen donde debe comenzar el análisis recursivo del archivo |
git_directory | el directorio de repositorio de git, si se deben incluir las métricas de git |
git_commit_limit | ¿Cuántas confirmaciones de la última confirmación se deben extraer? predeterminado: 150 |
git_exclude_merge_commits | ¿Deberían excluirse las confirmaciones de fusión de la extracción de todas las métricas? predeterminado: true |
ignore_files_containing | excluir del análisis los nombres de archivos que contengan las subcadenas dadas |
ignore_directories_containing | excluir del escaneo los nombres de directorio que contienen las subcadenas dadas |
only_permit_languages | Los valores posibles incluyen: java, kotlin, objc, swift, ruby, groovy, javascript, c: evita explícitamente que se escanee cualquier otro idioma además del que haya configurado aquí. |
only_permit_file_extensions | permita explícitamente las siguientes extensiones de archivo que establezca aquí, por ejemplo, .java |
only_permit_files_matching_absolute_path | Sólo se permite la siguiente lista de rutas absolutas de archivos para el análisis de archivos, por ejemplo, [/Users/user1/source/file1.java] . Los archivos deben seguir source_directory |
ignore_dependencies_containing | ignore todas las dependencias incluidas en esta lista de subcadenas, por ejemplo java.util |
ignore_dependencies_matching | ignore todas las dependencias que coincidan con cualquiera de las expresiones regulares en esta lista de subcadenas, por ejemplo ^java.util. |
ignore_entities_containing | ignore todas las entidades incluidas en esta lista de subcadenas, por ejemplo, NotRelevantClass |
ignore_entities_matching | ignore todas las entidades que coincidan con cualquiera de las expresiones regulares en esta lista de subcadenas, por ejemplo ^Test |
import_aliases | defina una lista de alias de importación, es decir, reemplace subcadenas dentro de una ruta de dependencia completa, por ejemplo, "@foo": src/foo reemplazará cualquier alias @foo por src/foo |
override_resolve_dependencies | si es compatible con el analizador de idiomas, fuerce la resolución de todas las dependencias de esta lista |
override_do_not_resolve_dependencies | si es compatible con el analizador de idiomas, fuerce que NO se resuelvan todas las dependencias de esta lista (es decir, se traten como una dependencia global) |
file_scan | realizar un análisis de archivos, contiene las métricas que deben aplicarse en cada archivo fuente |
entity_scan | realizar un escaneo de entidades, contiene las métricas que deben aplicarse en cada entidad (por ejemplo, en cada clase) |
export | contiene todos los formatos de exportación que deben crearse como salida |
appconfig | contiene cualquier parámetro de configuración de aplicación configurable |
llave | valor/descripción |
---|---|
dependency_graph | cree una estructura de gráfico de dependencia basada en archivos fuente, se agregarán métricas adicionales a los nodos del gráfico |
source_lines_of_code | aplicar una métrica de líneas de código fuente a cada archivo, crear una métrica general |
number_of_methods | aplicar una serie de métodos de métrica a cada archivo, crear una métrica general |
fan_in_out | aplicar una métrica gráfica de entrada y salida a cada archivo, crear una métrica general |
louvain_modularity | aplicar una métrica de modularidad de lovaina a cada archivo, crear una métrica general |
tfidf | aplique una métrica tfidf a cada archivo y extraiga palabras clave semánticas relevantes |
ws_complexity | aplicar una métrica de complejidad de espacios en blanco a cada archivo |
git_metrics | incluya algunas métricas basadas en git e intente aplicarlas a cada archivo |
llave | valor/descripción |
---|---|
dependency_graph | cree una estructura de gráfico de dependencia basada en entidades extraídas de archivos, se agregarán métricas adicionales a los nodos del gráfico |
inheritance_graph | cree una estructura de gráfico de herencia basada en entidades extraídas de archivos, se agregarán métricas adicionales a los nodos del gráfico |
complete_graph | cree una estructura de gráfico completa (unión de dependencia/gráfico de herencia) basada en entidades extraídas de archivos; se agregarán métricas adicionales a los nodos del gráfico |
source_lines_of_code | aplicar una métrica de líneas de código fuente a cada entidad, crear una métrica general |
number_of_methods | aplicar una serie de métodos de métrica a cada entidad, crear una métrica general |
fan_in_out | aplicar una métrica gráfica de entrada y salida a cada entidad, crear una métrica general |
louvain_modularity | aplicar una métrica de modularidad de Lovaina a cada entidad, crear una métrica general |
tfidf | aplicar una métrica tfidf a cada entidad y extraer palabras clave semánticas relevantes |
llave | valor/descripción |
---|---|
directory | el directorio de salida para todos los formatos de exportación especificados |
graphml | crear un archivo GraphML que contenga la estructura del gráfico y los resultados de las métricas asignados a los nodos del gráfico. |
tabular_file | crear un archivo de texto con formato tabular que contenga cada resultado de métrica y estadística |
tabular_console | imprimir una salida con formato tabular en la consola que contiene cada resultado de métrica y estadística |
tabular_console_overall | imprimir una salida con formato tabular en la consola que contiene solo resultados generales de métricas y estadísticas |
json | crear un archivo JSON que contenga cada resultado de métrica y estadística |
d3 | cree una aplicación web Bootstrap/D3 en la subcarpeta force-graph-html para un análisis visual e interactivo/exploratorio adicional |
llave | valor/descripción |
---|---|
radius_fan_out | factor de multiplicación del radio del nodo para la métrica de distribución, valor predeterminado: 0.1 |
radius_fan_in | factor de multiplicación del radio del nodo para la métrica de fan-in, valor predeterminado: 0.1 |
radius_louvain | factor de multiplicación del radio del nodo para la métrica de lovaina, predeterminado: 0.02 |
radius_sloc | factor de multiplicación del radio del nodo para la métrica sloc, predeterminado: 0.005 |
radius_number_of_methods | factor de multiplicación del radio del nodo para la métrica del número de métodos, valor predeterminado: 0.05 |
heatmap_sloc_active | ¿Debería incluirse la métrica de sloc en el cálculo de la puntuación del mapa de calor? predeterminado: true |
heatmap_fan_out_active | ¿Debería incluirse la métrica de distribución en el cálculo de la puntuación del mapa de calor? predeterminado: true |
heatmap_sloc_weight | factor de peso de la métrica de sloc dentro del cálculo de la puntuación del mapa de calor, predeterminado: 1.5 |
heatmap_fan_out_weight | factor de ponderación de la métrica de distribución dentro del cálculo de la puntuación del mapa de calor, predeterminado: 1.7 |
heatmap_score_base | Umbral de puntuación mínima para la asignación de color del mapa de calor, predeterminado: 10 |
heatmap_score_limit | Umbral de puntuación máxima para la asignación de colores del mapa de calor, predeterminado: 300 |
Emerge admite las siguientes extensiones de archivo y tipos de escaneo por idioma, mientras que file_scan
simplemente calcula métricas y asigna nodos dentro de estructuras gráficas a archivos escaneados entity_scan
intenta extraer entidades más detalladas de archivos, por ejemplo, clases o estructuras.
Extensión de archivo | analizador de idiomas | Archivos | Entidades |
---|---|---|---|
.java | Java | ✅ | ✅ |
.swift | Rápido | ✅ | ✅ |
.c / .h / .hpp | do | ✅ | |
.cpp / .h / .hpp | C++ | ✅ | |
.groovy | maravilloso | ✅ | ✅ |
.js / .jsx | javascript | ✅ | |
.ts / .tsx | Mecanografiado | ✅ | |
.k | Kotlin | ✅ | ✅ |
.m / .h | Objetivo-C | ✅ | |
.rb | Rubí | ✅ | |
.py | Pitón | ✅ | |
.go | Ir | ✅ |
La interpretación de dichos gráficos a menudo puede ser muy subjetiva y depender del proyecto. Los siguientes ejemplos deberían ayudar a reconocer ciertos patrones a través de indicadores y sugerencias.
La magia de descubrir la modularidad radica en aplicar un algoritmo de detección comunitaria, por ejemplo, la optimización de Louvain, a un gráfico dirigido por la fuerza, de modo que tanto las distancias como el color influyen en el resultado. El siguiente ejemplo incluye varios indicadores para una base de código modular.
En el primer ejemplo de la izquierda se pueden observar múltiples grupos de colores coherentes que muestran un acoplamiento bajo a cierta distancia (= generado por el gráfico dirigido por la fuerza).
En el segundo ejemplo de la derecha, se representa el mismo gráfico con los cascos de grupo activados. En este ejemplo, los cascos muestran una superposición mínima o nula. Estas sugerencias pueden ser indicadores de una buena arquitectura de software, por ejemplo en términos de modularidad , abstracción e interfaces bien definidas.
"UNA GRAN BOLA DE LODO está estructurada al azar, es extensa, descuidada, con cinta adhesiva y alambre para atar, una jungla de códigos de espagueti" (B. Foote, J. Yoder, 1997). Este tipo de gráfico suele representar una arquitectura menos óptima. Para verificar este tipo de jungla de código espagueti , uno simplemente puede habilitar la representación del casco para que todos los grupos determinen finalmente: después de todo, solo hay un grupo grande.
A veces puede ayudar a comprender mejor la complejidad de una arquitectura de software si se ignoran las dependencias irrelevantes.
ignore_dependencies_containing
(o ignore_dependencies_matching
si prefieres expresiones regulares). Con una métrica de distribución comparativamente activada, se reconoce una mayor dispersión, algunos nodos centrales distantes y clústeres más claros. Todas estas son posibles pistas sobre la arquitectura real (a menudo más comprensible) que se esconde debajo.