Emerge ( oumerge-viz ) est un outil interactif d'analyse de code permettant de recueillir des informations sur la structure du code source, les métriques, les dépendances et la complexité des projets logiciels. Vous pouvez analyser le code source d'un projet, calculer des résultats de métriques et des statistiques, générer une application Web interactive avec des structures graphiques (par exemple un graphique de dépendances ou un graphique de système de fichiers) et exporter les résultats dans certains formats de fichiers. Emerge prend actuellement en charge l'analyse syntaxique pour les langages suivants : C
, C++
, Groovy
, Java
, JavaScript
, TypeScript
, Kotlin
, ObjC
, Ruby
, Swift
, Python
, Go
. La structure, la coloration et le clustering sont calculés et basés sur l'idée de combiner une simulation de graphe dirigé par force et la modularité de Louvain. Emerge est principalement écrit en Python 3 et est testé sur macOS, Linux et les navigateurs Web modernes (c'est-à-dire les derniers Safari, Chrome, Firefox, Edge).
émerger (/ɪˈməːdʒ/)
- apparaître en sortant de quelque chose ou en sortant de derrière quelque chose
- se faire connaître, notamment après avoir examiné quelque chose ou posé des questions à ce sujet
L'objectif principal de ce projet est de créer un outil gratuit/open source, qui peut facilement être utilisé par toute personne intéressée par le développement de logiciels, l'architecture, les métriques et la visualisation pour recueillir plus d'informations sur ces sujets. Il devrait faciliter/aider à mieux comprendre un projet logiciel donné en utilisant une approche exploratoire.
C
, C++
, Groovy
, Java
, JavaScript
, TypeScript
, Kotlin
, ObjC
, Ruby
, Swift
, Python
Groovy
, Java
, Kotlin
, Swift
git-based
(SLOC, Whitespace Complexity, Change Coupling)SwiftUI
et Composable
git-based
, par exemple le taux de désabonnement du codeLe moyen le plus simple d'utiliser Emerge dans un conteneur Docker pré-construit. Le seul prérequis est d’avoir un moteur Docker. Par exemple le Docker Desktop.
Préparez votre dossier de travail comme ceci
config.yml
?export
?source
La commande pour exécuter l'analyse est la suivante :
docker run --rm -v <YOUR_WORKING_FOLDER_PATH>:/tmp/emerge achtelik/emerge:2.0.0 /tmp/emerge/config.yml
Le dernier paramètre est le chemin d'accès au config.yml dans le conteneur Docker.
⚡Vous pouvez ignorer l'erreur Pyperclip à la fin de l'exécution.
Si vous utilisez la suggestion ci-dessus, faites attention au fait que vos chemins analyses.source_directory
et export.directory
doivent commencer par /tmp/emerge
. Cela est nécessaire car votre analyse s'exécute dans le conteneur Docker.
Par exemple:
---
project_name: java_project_example
loglevel: info
analyses:
- analysis_name: full java check
source_directory: /tmp/emerge/source
.
.
.
export:
- directory: /tmp/emerge/export
.
.
.
Le conteneur Docker lui-même est indépendant du chemin. N'hésitez pas à utiliser vos propres chemins de montage de volume et de configuration de projet.
Fondamentalement, il existe deux manières d’installer Emerge. Si vous êtes familier avec pip
(un environnement virtuel utilisant pyenv
, virtualenv
et virtualenvwrapper
est recommandé, mais pas nécessaire), vous pouvez simplement installer la dernière version d'emerge en suivant les quelques étapes suivantes.
La méthode recommandée serait d'utiliser un environnement virtuel, vous pouvez le faire en utilisant l'exemple suivant :
pyenv install 3.10.0
pyenv virtualenv 3.10.0 venv-3.10.0
pyenv activate venv-3.10.0
Vous pouvez simplement installer émerger en utilisant pip
.
Sur Ubuntu 20.04+, assurez-vous que les packages graphviz
et graphviz-dev
sont installés, c'est-à-dire
apt-get install graphviz graphviz-dev
Soit installer en tant que nouveau package avec :
pip install emerge-viz
ou s'il est déjà installé, mettez simplement à jour avec :
pip install -U emerge-viz
puis exécutez-le simplement comme ceci :
(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]
Vous pouvez créer une configuration de projet simple ad hoc à partir de la ligne de commande, puis ajuster simplement les chemins source/exportation nécessaires.
(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
puis ajustez simplement les chemins nécessaires ( analyses/source_directory
et 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 %
Après cela, vous pouvez simplement lancer une analyse en
(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
Maintenant, copiez simplement le chemin file://
mentionné ci-dessus dans n'importe quel navigateur Web moderne et explorez de manière interactive votre base de code configurée.
Vous pouvez cloner ce référentiel et l'installer en suivant ces instructions :
git clone https://github.com/glato/emerge.git
graphviz
brew install graphviz
Si vous rencontrez l'erreur suivante sur un Mac Apple Silicon
pygraphviz/graphviz_wrap.c:2711:10: fatal error: ' graphviz/cgraph.h ' file not found
# include "graphviz/cgraph.h"
^~~~~~~~~~~~~~~~~~~
1 error generated.
vous devez exécuter la commande suivante une fois pour mettre à jour les répertoires d'inclusion pygraphviz pour le nouvel environnement homebrew
pip install --global-option=build_ext --global-option= " -I $( brew --prefix graphviz ) /include/ " --global-option= " -L $( brew --prefix graphviz ) /lib/ " pygraphviz
Voir le problème dans son contexte ici.
Vérifiez que la dernière version de Python 3 est installée sur votre macOS. Je recommande d'installer/utiliser Python 3 depuis Homebrew. Créer un environnement virtuel Python 3 (éventuellement dans la structure du projet)
cd emerge
pip3 install virtualenv
virtualenv -p python3 venv
Installez les packages requis et créez un environnement virtuel Python 3 (éventuellement dans la structure du projet)
apt-get install python3-venv python3-dev graphviz graphviz-dev
cd emerge
python3 -m venv venv
source venv/bin/activate
Installez toutes les dépendances requises pour le projet avec pip
pip install -r requirements.txt
Installez le package wheel, puis installez toutes les dépendances requises pour le projet avec pip
pip install wheel
pip install -r requirements.txt
Exécutez ce qui suit à partir de la racine du projet cloné :
python -m unittest discover -v -s ./emerge -p "test_*.py"
sinon exécutez le script run_tests.py
:
python run_tests.py
Si vous rencontrez des difficultés lors de l'exécution des tests, vérifiez cette solution.
emerge
comme un outil autonome (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]
Essayons rapidement d'exécuter Emmerge sur sa propre base de code
python emerge.py -c configs/emerge.yaml
Cela devrait produire un résultat similaire :
... 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
Maintenant, copiez simplement le chemin file://
mentionné ci-dessus dans n'importe quel navigateur Web moderne et explorez de manière interactive la base de code Emerge.
s
pour sélectionner et mettre en surbrillance ou désélectionner un nœud spécifiquer
f
Et maintenant, rendons cela plus intéressant...
Si vous souhaitez utiliser Emerge sur d'autres projets, vous pouvez simplement copier ou personnaliser l'un des modèles de configuration existants à partir du répertoire emerge/configs
.
Pour une exécution rapide, il devrait suffire d'ajuster source_directory
, directory
dans 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
Après avoir personnalisé une configuration actuelle (par exemple config/c-template.yaml
) ou créé la vôtre, exécutez simplement Emmerge avec cette nouvelle configuration.
python emerge.py -c configs/c-template.yaml
Après l'analyse, le résultat de votre analyse (y compris votre application Web interactive) se trouve dans le répertoire que vous avez créé et défini dans le paramètre de configuration export
-> directory
, comme indiqué dans les journaux ci-dessus.
Une configuration YAML complète contenant à la fois l'analyse des fichiers et des entités a le format suivant :
---
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
Parfois, il peut être judicieux d’exclure les dépendances habituelles de la plateforme ou celles qui ne contribuent pas beaucoup à la compréhension d’un projet. Un bon point de départ, par exemple pour un projet Android, pourrait être la section ignore_dependencies_containing
suivante :
ignore_dependencies_containing :
- android
- java
- javax
ou pour un projet iOS, la section ignore_entities_containing
suivante a souvent du sens, par exemple pour ne pas prendre en compte les aperçus SwiftUI pour la sortie du graphique :
ignore_entities_containing :
- _Previews
La configuration yaml est essentiellement définie aux niveaux suivants :
clé | valeur/description |
---|---|
project_name | un nom de projet pour toutes les analyses, scans et exportations |
loglevel | définir un niveau de journalisation : error (silencieux, uniquement les erreurs), info (inclut error ) vous donne des journaux de base sur le flux de contrôle, debug (inclut info ) produira de nombreux journaux de débogage |
analyses | un ensemble d'analyses qui peuvent être configurées individuellement, ainsi un projet peut contenir une ou plusieurs analyses. |
clé | valeur/description |
---|---|
analysis_name | un nom d'analyse spécifique |
source_directory | le répertoire source dans lequel l'analyse récursive des fichiers doit démarrer |
git_directory | le répertoire du dépôt git, si les métriques git doivent être incluses |
git_commit_limit | combien de commits du dernier commit doivent être extraits ? par défaut : 150 |
git_exclude_merge_commits | les validations de fusion devraient-elles être exclues de l’exploration de toutes les métriques ? par défaut : true |
ignore_files_containing | exclure les noms de fichiers de l'analyse qui contiennent les sous-chaînes données |
ignore_directories_containing | exclure les noms de répertoires de l'analyse qui contiennent les sous-chaînes données |
only_permit_languages | les valeurs possibles incluent : java, kotlin, objc, swift, ruby, groovy, javascript, c - empêche explicitement toute autre langue d'analyser en plus de celle que vous avez définie ici |
only_permit_file_extensions | autorisez explicitement les extensions de fichiers suivantes que vous définissez ici, par exemple .java |
only_permit_files_matching_absolute_path | seule la liste suivante de chemins de fichiers absolus est autorisée pour l'analyse de fichiers, par exemple [/Users/user1/source/file1.java] . Les fichiers doivent suivre source_directory |
ignore_dependencies_containing | ignorez toutes les dépendances incluses dans cette liste de sous-chaînes, par exemple java.util |
ignore_dependencies_matching | ignorez toutes les dépendances correspondant à l'une des expressions régulières de cette liste de sous-chaînes, par exemple ^java.util. |
ignore_entities_containing | ignorer chaque entité incluse dans cette liste de sous-chaînes, par exemple NotRelevantClass |
ignore_entities_matching | ignorer chaque entité correspondant à l'une des expressions régulières de cette liste de sous-chaînes, par exemple ^Test |
import_aliases | définir une liste d'alias d'importation, c'est-à-dire remplacer les sous-chaînes dans un chemin de dépendance complet, par exemple "@foo": src/foo remplacera tout alias @foo par src/foo |
override_resolve_dependencies | si pris en charge par l'analyseur de langage, forcer la résolution de chaque dépendance de cette liste |
override_do_not_resolve_dependencies | si pris en charge par l'analyseur de langage, forcer chaque dépendance de cette liste à NE PAS être résolue (c'est-à-dire traitée comme une dépendance globale) |
file_scan | effectuer une analyse de fichier, contient les métriques qui doivent être appliquées à chaque fichier source |
entity_scan | effectuer une analyse d'entité, contient les métriques qui doivent être appliquées à chaque entité (par exemple sur chaque classe) |
export | contient tous les formats d'exportation qui doivent être créés en tant que sortie |
appconfig | contient tous les paramètres de configuration d'application configurables |
clé | valeur/description |
---|---|
dependency_graph | créer une structure de graphique de dépendance basée sur les fichiers sources, des métriques supplémentaires seront ajoutées aux nœuds du graphique |
source_lines_of_code | appliquer une métrique de lignes de code source à chaque fichier, créer une métrique globale |
number_of_methods | appliquer un certain nombre de méthodes de métrique à chaque fichier, créer une métrique globale |
fan_in_out | appliquer une métrique de graphique d'entrée/sortie à chaque fichier, créer une métrique globale |
louvain_modularity | appliquer une métrique de modularité Louvain à chaque fichier, créer une métrique globale |
tfidf | appliquer une métrique tfidf à chaque fichier et extraire les mots-clés sémantiques pertinents |
ws_complexity | appliquer une métrique de complexité des espaces à chaque fichier |
git_metrics | inclure des métriques basées sur git et essayer de les appliquer à chaque fichier |
clé | valeur/description |
---|---|
dependency_graph | créer une structure de graphique de dépendance basée sur les entités extraites des fichiers, des métriques supplémentaires seront ajoutées aux nœuds du graphique |
inheritance_graph | créer une structure de graphique d'héritage basée sur les entités extraites des fichiers, des métriques supplémentaires seront ajoutées aux nœuds du graphique |
complete_graph | créer une structure de graphe complète (union de graphe de dépendances/d'héritage) basée sur les entités extraites des fichiers, des métriques supplémentaires seront ajoutées aux nœuds du graphe |
source_lines_of_code | appliquer une métrique de lignes de code source à chaque entité, créer une métrique globale |
number_of_methods | appliquer un certain nombre de méthodes de métrique à chaque entité, créer une métrique globale |
fan_in_out | appliquer une métrique de graphique de répartition/diffusion à chaque entité, créer une métrique globale |
louvain_modularity | appliquer une métrique de modularité louvaine à chaque entité, créer une métrique globale |
tfidf | appliquer une métrique tfidf à chaque entité et extraire des mots-clés sémantiques pertinents |
clé | valeur/description |
---|---|
directory | le répertoire de sortie pour tous les formats d'exportation spécifiés |
graphml | créer un fichier graphML contenant la structure du graphique et les résultats des métriques mappés aux nœuds du graphique |
tabular_file | créer un fichier texte au format tabulaire contenant tous les résultats métriques et statistiques |
tabular_console | imprimer une sortie au format tabulaire sur la console qui contient tous les résultats métriques et statistiques |
tabular_console_overall | imprimer une sortie au format tabulaire sur la console qui contient uniquement les résultats globaux des mesures et des statistiques |
json | créer un fichier JSON contenant tous les résultats de métriques et de statistiques |
d3 | créer une application Web Bootstrap/D3 dans le sous-dossier force-graph-html pour une analyse visuelle et interactive/exploratoire plus approfondie |
clé | valeur/description |
---|---|
radius_fan_out | Facteur de multiplication du rayon du nœud pour la métrique de sortance, par défaut : 0.1 |
radius_fan_in | Facteur de multiplication du rayon du nœud pour la métrique de répartition, par défaut : 0.1 |
radius_louvain | facteur de multiplication du rayon du nœud pour la métrique de Louvain, par défaut : 0.02 |
radius_sloc | Facteur de multiplication du rayon du nœud pour la métrique sloc, par défaut : 0.005 |
radius_number_of_methods | Facteur de multiplication du rayon du nœud pour la métrique du nombre de méthodes, par défaut : 0.05 |
heatmap_sloc_active | la métrique sloc doit-elle être incluse dans le calcul du score de la carte thermique ? par défaut : true |
heatmap_fan_out_active | la métrique de diffusion doit-elle être incluse dans le calcul du score de la carte thermique ? par défaut : true |
heatmap_sloc_weight | facteur de pondération de la métrique sloc dans le calcul du score de la carte thermique, par défaut : 1.5 |
heatmap_fan_out_weight | facteur de pondération de la métrique de répartition dans le calcul du score de la carte thermique, par défaut : 1.7 |
heatmap_score_base | seuil de score minimum pour le mappage des couleurs de la carte thermique, par défaut : 10 |
heatmap_score_limit | seuil de score maximum pour le mappage des couleurs de la carte thermique, par défaut : 300 |
Emerge prend en charge les extensions de fichiers et les types d'analyse suivants par langue, tandis qu'un file_scan
calcule simplement les métriques et mappe les nœuds dans les structures graphiques aux fichiers analysés et entity_scan
essaie d'extraire des entités plus fines à partir de fichiers, par exemple des classes ou des structures.
Extension de fichier | Analyseur de langue | Fichiers | Entités |
---|---|---|---|
.java | Java | ✅ | ✅ |
.swift | Rapide | ✅ | ✅ |
.c / .h / .hpp | C | ✅ | |
.cpp / .h / .hpp | C++ | ✅ | |
.groovy | Groovy | ✅ | ✅ |
.js / .jsx | Javascript | ✅ | |
.ts / .tsx | Manuscrit | ✅ | |
.k | Kotlin | ✅ | ✅ |
.m / .h | Objectif-C | ✅ | |
.rb | Rubis | ✅ | |
.py | Python | ✅ | |
.go | Aller | ✅ |
L'interprétation de tels graphiques peut souvent être très subjective et dépendre du projet. Les exemples suivants devraient aider à reconnaître certains modèles grâce à des indicateurs et des indices.
La magie de la découverte de la modularité réside dans l'application d'un algorithme de détection de communauté, par exemple l'optimisation de Louvain, à un graphe orienté force, de sorte que les distances et la coloration influencent le résultat. L'exemple suivant comprend plusieurs indicateurs pour une base de code modulaire.
Dans le premier exemple de gauche, vous pouvez repérer plusieurs clusters colorés cohérents qui montrent un faible couplage à une certaine distance (= généré par le graphe dirigé par la force).
Dans le deuxième exemple de droite, le même graphique est rendu avec des coques de cluster activées. Sur cet exemple, les coques présentent peu ou pas de chevauchement. De tels indices peuvent être des indicateurs d'une bonne architecture logicielle, par exemple en termes de modularité , d'abstraction et d'interfaces bien définies.
"UNE GRANDE BOULE DE BOUE est structurée au hasard, tentaculaire, bâclée, avec du ruban adhésif et du fil de fer, une jungle de codes spaghetti" (B. Foote, J. Yoder, 1997). Ce type de graphique représente souvent une architecture moins optimale. Pour vérifier ce genre de jungle de code spaghetti , on peut simplement activer le rendu de coque pour tous les clusters pour finalement déterminer : il n'y a qu'un seul gros cluster après tout.
Parfois, il peut être utile de mieux comprendre la complexité d’une architecture logicielle si les dépendances non pertinentes sont ignorées.
ignore_dependencies_containing
(ou ignore_dependencies_matching
si vous préférez les expressions régulières). Avec une métrique de sortance relativement activée, on reconnaît plus de diffusion, certains nœuds hub distants et des clusters plus clairs. Tous ces éléments sont des indices possibles sur l’architecture réelle (= souvent plus compréhensible) en dessous.