Au sein d’enioka Haute Couture, nous sommes amenés à aider au cadrage technique de projets. Une activité est de sélectionner les briques logicielles sur lesquelles le système développé pour le client sera fondé. Ce socle technique est essentiel pour la pérennité et la maintenabilité du système produit. La plupart des développeurs fondent ces choix sur des mérites purement techniques. Certes il s’agit d’un aspect important, mais la viabilité à long terme d’une brique n’est pas uniquement déterminée par la qualité de son code et de sa conception, les dynamiques sociales de l’équipe ou de la communauté ayant la charge du composant considéré sont aussi cruciales.
Dans cet article, nous revenons sur un travail réalisé à l’été 2019 pour le compte d’un client. Nous devions cadrer l’ensemble du projet, mais nous nous concentrons ici sur l’un des choix réalisés pour la partie interface homme machine. Nous devions notamment sélectionner une bibliothèque de composants React. Après un examen de leurs mérites techniques en termes de structure et d’API, il nous restait deux candidats sérieux : ant-design et Semantic UI. Nous devions alors les comparer en termes de dynamiques communautaires. C’était donc une situation parfaite pour utiliser ComDaAn afin de se faire un avis sur le fonctionnement de ces deux communautés. Nous nous sommes concentrés sur l’activité des développeurs dans les dépôts git.
Nettoyer les données
Afin de pouvoir démarrer l’étude, nous clonons les dépôts des projets concernés dans le répertoire ~/Repositories/React. Avec ComDaAn nous pouvons obtenir un data frame par dépôt représentant son historique complet. Cela se fait en quelques lignes de python.
import comdaan as cd
ant_data = cd.parse_repositories("~/Repositories/React/ant-design")
sem_data = cd.parse_repositories("~/Repositories/React/Semantic-UI-React")
Après exploration des données, soit en examinant les data frames soit en utilisant la visualisation présentée dans la section suivante, on constate qu’elles sont fortement bruitées. En effet, deux problèmes resurgissent. Premièrement dans ces projets les noms des auteurs ne sont pas constants, ceci est particulièrement remarquable dans le cas d’ant-design avec une population essentiellement asiatique utilisant des représentations de leur nom parfois en ASCII mais pas toujours, parfois en utilisant un nom occidentalisé de remplacement, parfois une approximation phonétique… Deuxièmement, on peut constater l’utilisation d’un robot générant des commits sur Semantic UI ce qui donne une mauvaise représentation de l’activité.
L’approche de travail sera alors de générer un identifiant pour chaque auteur créé à partir de leur adresse e-mail et de retirer les commits du robot. Pour cela nous créons un fichier comdaan_ruleset.py que nous plaçons dans ~/Repositories/React. Ce fichier sera automatiquement découvert par ComDaAn et appliqué sur tous les dépôts se trouvant dans ~/Repositories/React.
# Use the user name part of emails, or
# if user name is "me" use the domain name without extension
def email_to_user(s):
parts = s.split("@")
if s.startswith("me@"):
return parts[1].split(".")[0]
else:
return parts[0]
def is_entry_acceptable(entry):
if "author_email" not in entry:
return False
# No need to measure bots activity
if entry["author_name"] == "deweybot":
return False
return True
def postprocess_entry(entry):
entry["author_name"] = email_to_user(entry["author_email"])
Nous pouvons constater dans le code ci-dessus la normalisation des noms d’auteurs à partir de leur adresse e-mail et le rejet des commits générés par deweybot. En relançant notre premier script sans le modifier, l’historique des commits est automatiquement nettoyé. Nous pouvons à présent entrer dans le vif du sujet et analyser l’activité des projets.
Évaluer l’activité des projets
Il nous faut d’abord produire une visualisation dans le temps de chaque contributeur, cela est fait en ajoutant quelques lignes à notre script.
# Activity, all time, for both projects
a = cd.activity(ant_data, "id", "author_name", "date")
cd.display(a, output="ant-activity.html", title="Ant Design Activity All Time")
a = cd.activity(sem_data, "id", "author_name", "date")
cd.display(a, output="semantic-activity.html", title="Semantic UI Activity All Time")
Cette visualisation est affectueusement surnommée “colorful blobs” nous allons voir pourquoi immédiatement.
Sur l’axe des abscisses nous avons le temps et sur l’axe des ordonnées chaque contributeur trié sur la date de son premier commit. A chaque intervalle de temps (ici la semaine) un “blob” est ajouté si le contributeur était actif sur la période. En cas d’activité la couleur est modérée en fonction du niveau d’activité plus la couleur est “intense” plus le contributeur était actif.
Cela nous permet de voir plusieurs informations importantes d’un coup d’œil :
- quels sont les contributeurs les plus actifs, puisque les lignes contenant fréquemment des blobs de couleur intense vont ressortir par rapport aux autres;
- la capacité de recrutement du projet, comme les contributeurs sont triés par date d’entrée l’enveloppe gauche de nos blobs forme une courbe, plus sa pente est forte plus le projet recrute vite;
- la capacité de rétention du projet, à l’intérieur de l’enveloppe gauche plus la surface est dense en blobs plus les contributeurs s’attardent sur le projet.
Dans le cas de notre comparaison cela nous donne déjà quelques points saillants. En effet nous constatons qu’ant-design a un taux de recrutement quasi constant depuis 2017 (ce taux était plus faible avant 2017 comme l’inflexion de la courbe l’indique). Dans le même ordre d’idée nous pouvons voir que la dynamique de recrutement sur Semantic UI est en train de s’éroder depuis environ mi-2018. Nous pouvons aussi constater un meilleur taux de rétention pour ant-design. En effet, la densité de blobs est plus élevée et parmi les contributeurs fraîchement recrutés certains deviennent rapidement très productif.
Il s’agit là de très bons signes en faveur d’ant-design. La différence est tellement prononcée qu’à ce stade il serait tentant de conclure immédiatement. Toutefois nous préconisons d’avoir une évaluation plus complète pour éviter certaines mauvaises surprises.
Évaluer la taille de la communauté
Une autre visualisation intéressante est celle de l’évolution de la taille de la communauté dans le temps. Encore une fois il nous faut ajouter quelques lignes à notre script principal.
# Team Size, all time, for both projects
s = cd.teamsize(ant_data, "id", "author_name", "date")
cd.display(s, output="ant-teamsize.html", title="Ant Design Team Size All Time")
s = cd.teamsize(sem_data, "id", "author_name", "date")
cd.display(s, output="semantic-teamsize.html", title="Semantic UI Size All Time")
Ainsi, nous produisons deux courbes pour chaque projets.
La courbe bleue représente la tendance sur le nombre de commits hebdomadaire tandis que la courbe orange représente la tendance sur le nombre hebdomadaire de participants uniques au projet. En comparant les deux projets nous confirmons un peu plus ce que nous avions constaté lors de l’analyse d’activité. Le projet ant-design via ses taux de recrutement et de rétention voit son nombre de participants hebdomadaire augmenter progressivement. En revanche du côté de Semantic UI nous constatons une lente érosion de l’équipe et de l’activité globale. La valeur de ces courbes est aussi d’aider au repérage d’événements marquants.
Cela n’est pas très flagrant dans le cas d’ant-design mais on peut constater une quasi stagnation du nombre de personnes actives simultanément sur l’année 2017. En regardant à nouveau l’analyse d’activité pour le projet nous constatons effectivement un recrutement de personnes très actives sur 2016 et sur 2018 mais assez peu sur 2017. Nous pouvons donc supposer une reconfiguration de la communauté lors de 2017 et donc il serait intéressant de voir ces changements en explorant plus avant la période 2016 et la période 2018.
Dans le cas de Semantic UI, il paraît plus intéressant de se concentrer sur 2016 année de forte progression de la taille d’équipe et mais aussi sur 2019 pour avoir une idée de la nouvelle structure une fois la phase d’érosion lancée.
Evaluer le réseau des contributeurs
Pour effectuer les explorations mentionnées ci-dessus, il nous faut une possibilité de jauger la structure de la communauté. Nous utilisons alors l’analyse du réseau des contributeurs qui cherche à évaluer les collaborations entre contributeurs. Pour cela nous nous basons sur les artefacts qui ont été touchés par les même personnes sur une période donnée en supposant que pour mener à bien leurs modifications ils ont dû communiquer pour se synchroniser. Il s’agit bien évidemment d’une approximation mais qui tend à fonctionner assez bien en pratique.
ant-design
Concentrons nous tout d’abord sur ant-design sur la période 2016 et la période 2018. Pour cela nous ajoutons deux analyses à notre script principal.
# Network for ant design in 2016
ant_data_2016 = ant_data[(ant_data["date"] >= '2016-01-01') & (ant_data["date"] <= "2016-12-31")].copy()
n = cd.network(ant_data_2016, "author_name", "files")
cd.display(n, output="ant-network-2016.html", title="Ant Design Contributor Network 2016")
# Network for ant design in 2018
ant_data_2018 = ant_data[(ant_data["date"] >= '2018-01-01') & (ant_data["date"] <= "2018-12-31")].copy()
n = cd.network(ant_data_2018, "author_name", "files")
cd.display(n, output="ant-network-2018.html", title="Ant Design Contributor Network 2018")
Nous obtenons alors deux réseaux de contributeurs. Chaque nœud représente un contributeur, il est relié aux contributeurs avec lesquels il a collaboré (avec l’approximation citée précédemment), plus la collaboration est forte et plus le lien est fort. À partir de ces liens nous pouvons alors évaluer si un contributeur est très central ou non. La centralité utilisée dans nos analyse est la fraction du nombre de contributeurs auxquels un contributeur donné est connecté. Plus il est central plus sa couleur sera “intense”.
Sur l’année 2016, il ressort visuellement un réseau qui semble déjà assez dense (en effet, on peut voir grand nombre de nœuds et beaucoup de connexions entre ces nœuds). De plus, nous pouvons très vite identifier les deux contributeurs les plus centraux du réseau “afc163” et “benjytrys”. Il est alors possible d’extrapoler et d’affirmer que ces deux contributeurs sont probablement les mainteneurs de fait du projet. Enfin, autour de ces deux contributeurs il est possible de repérer cinq autres nœuds très centraux. Nous sommes donc en présence d’un projet avec un leadership fort et une équipe de mainteneurs de taille respectable (autour de sept personnes).
Comme nous le suspections, sur l’année 2018 nous constatons une reconfiguration de la communauté (probablement ayant eu lieu courant 2017). Tout d’abord le réseau est encore plus dense, ce qui est un excellent signe nous avions déjà constaté un fort recrutement mais visiblement cela ne s’est pas fait au détriment de la cohésion de la communauté. De plus, l’un des deux contributeurs centraux du réseau a changé, en effet “benjytrys” n’est plus central et a été remplacé par “smith3816”. En regardant les autres contributeurs centraux, seul un est resté en commun. Nous pouvons donc dire que l’équipe de mainteneurs a été quasiment entièrement renouvelée courant 2017 et pourtant la dynamique globale du projet n’a pas été impactée.
Semantic UI
À présent nous pouvons explorer Semantic UI sur la période 2016 et la période 2019. Encore une fois nous ajoutons deux analyses à notre script principal.
# Network for Semantic UI in 2016
sem_data_2016 = sem_data[(sem_data["date"] >= '2016-01-01') & (sem_data["date"] <= "2016-12-31")].copy()
n = cd.network(sem_data_2016, "author_name", "files")
cd.display(n, output="semantic-network-2016.html", title="Semantic UI Contributor Network 2016")
# Network for Semantic UI in 2019
sem_data_2019 = sem_data[(sem_data["date"] >= '2019-01-01') & (sem_data["date"] <= "2019-12-31")].copy()
n = cd.network(sem_data_2019, "author_name", "files")
cd.display(n, output="semantic-network-2019.html", title="Semantic UI Contributor Network 2019")
Nous obtenons à nouveau deux réseaux de contributeurs.
Sur l’année 2016 nous constatons un réseau moins dense que celui d’ant-design. Mais à part cela, la situation semble similaire: deux personnes très centrales (“levithomason” et “jeff.carbonella”) suivies d’environ quatre autres contributeurs centraux (dont un certain “alexander.mcgarret”). Nous sommes en présence d’une équipe de maintenance correcte.
Comme nous pouvions nous y attendre avec la dynamique générale d’érosion le réseau de contributeurs est moins dense en 2019. De plus l’équipe de maintenance s’est réduite autour d’environ deux ou trois personnes. Il n’y a plus de situation de co-mainteneurs comme précédemment, le mainteneur probable étant à présent “alexander.mcgarret”.
Conclusion
Nous sommes arrivés au bout de l’exploration de nos deux projets. Nous avons réalisé trois analyses (activité, taille d’équipe et réseau de contributeurs) qui nous ont donné un grand nombre d’informations sur les projets évalués :
- taux de recrutement et de rétention de la communauté ;
- les contributeurs les plus actifs ;
- les tendances de l’évolution de l’activité générale et de la taille d’équipe ;
- la taille et la densité du réseau de contributeurs ;
- les contributeurs les plus centraux formant probablement les équipes de mainteneurs.
Ce sont des informations intéressantes par projet pour avoir une idée de leur histoire et de leurs difficultés sur le plan communautaire. Toutefois dans notre cadre d’une comparaison afin de sélectionner une brique technique, il paraît évident que le choix le plus pertinent semble être ant-design. En effet, ce dernier montre une dynamique générale de contribution plutôt stable et dispose d’une équipe de maintenance plus qu’honorable. De plus, la communauté a réussi à survivre à un renouvellement quasi complet de l’équipe de maintenance historique tout en conservant globalement ses taux de recrutement et de rétention. Nous sommes donc en présence d’une communauté qui semble résiliente.
Bien évidemment toute cette analyse est fondée uniquement sur les commits et en considérant les contributeurs comme des individus atomisés. Il pourrait être intéressant de vérifier les affiliations des différents contributeurs… information dont nous ne disposons malheureusement pas.
En revanche, un an après notre analyse initiale, nous ne regrettons absolument pas notre choix. En effet les dynamiques identifiées à l’époque se sont confirmées et l’évolution technique d’ant-design a été en conséquence avec la sortie d’une nouvelle version majeure le mois dernier.
Annexe: Script complet
#! /usr/bin/env python
import comdaan as cd
ant_data = cd.parse_repositories("~/Repositories/React/ant-design")
sem_data = cd.parse_repositories("~/Repositories/React/Semantic-UI-React")
# Activity, all time, for both projects
a = cd.activity(ant_data, "id", "author_name", "date")
cd.display(a, output="ant-activity.html", title="Ant Design Activity All Time")
a = cd.activity(sem_data, "id", "author_name", "date")
cd.display(a, output="semantic-activity.html", title="Semantic UI Activity All Time")
# Team Size, all time, for both projects
s = cd.teamsize(ant_data, "id", "author_name", "date")
cd.display(s, output="ant-teamsize.html", title="Ant Design Team Size All Time")
s = cd.teamsize(sem_data, "id", "author_name", "date")
cd.display(s, output="semantic-teamsize.html", title="Semantic UI Size All Time")
# Network for ant design in 2016
ant_data_2016 = ant_data[(ant_data["date"] >= '2016-01-01') & (ant_data["date"] <= "2016-12-31")].copy()
n = cd.network(ant_data_2016, "author_name", "files")
cd.display(n, output="ant-network-2016.html", title="Ant Design Contributor Network 2016")
# Network for ant design in 2018
ant_data_2018 = ant_data[(ant_data["date"] >= '2018-01-01') & (ant_data["date"] <= "2018-12-31")].copy()
n = cd.network(ant_data_2018, "author_name", "files")
cd.display(n, output="ant-network-2018.html", title="Ant Design Contributor Network 2018")
# Network for Semantic UI in 2016
sem_data_2016 = sem_data[(sem_data["date"] >= '2016-01-01') & (sem_data["date"] <= "2016-12-31")].copy()
n = cd.network(sem_data_2016, "author_name", "files")
cd.display(n, output="semantic-network-2016.html", title="Semantic UI Contributor Network 2016")
# Network for Semantic UI in 2019
sem_data_2019 = sem_data[(sem_data["date"] >= '2019-01-01') & (sem_data["date"] <= "2019-12-31")].copy()
n = cd.network(sem_data_2019, "author_name", "files")
cd.display(n, output="semantic-network-2019.html", title="Semantic UI Contributor Network 2019")
