Environnement de développement
Un environnement de développement est un ensemble d'outils, de processus, et de règles appliqués à un projet. Ils définissent la manière dont un projet est, e.g. :
- structuré
- construit, et exécuté
- testé, et validé
- intégré, et déployé
- documenté
- etc.
Ainsi, avant toute étape de développement, il y a une étape pour convenir de l'environnement de développement du projet, puis le mettre en place. Cette étape correspond à l'initialisation du projet.
C'est à cette étape qu'une version minimale du projet est créée, de sorte à valider le bon fonctionnement de l'environnement de développement. Le dépôt Git est ainsi créé avec divers scripts et outils permettant d'exécuter, tester, déployer, documenter, etc. le projet. À ce moment, le code du projet ne fait rien de plus que d'écrire "Hello World" dans la console, une page Web, ou une fenêtre graphique, dans le seul but de vérifier que l'environnement de développement est bien opérationnel.
Ce n'est qu'une fois l'environnement de développement mis en place et validé, que la phase de développement pourra alors débuter.
La gestion des tâches
La gestion des tâches est un exemple de processus à définir dans l'environnement de développement. Il va de l'expression du besoin ou de la demande, à sa résolution finale.
Expression d'une demande
L'expression d'une demande peut se faire de manière plus ou moins formelle. Usuellement, elle va s'effectuer sous la forme d'un ticket (issue), afin d'en permettre le suivi. Il convient alors de définir les différents types de demandes ainsi que leur format, i.e. les informations nécessaires à leur résolution, e.g. :
- déclaration d'incidents/bugs.
- demande de support.
- demande de fonctionnalité.
Les informations demandées peuvent être, e.g. dans le cas d'une déclaration de bug, la version concernée du projet, les étapes pour reproduire le bug, le résultat obtenu, le résultat attendu, etc. Tout ce qui facilitera la compréhension ainsi que la résolution de la demande.
Une fois la demande créée, elle est usuellement "triée" (triage) en l'associant à diverses catégories, e.g :
- le type de la demande : e.g. Bug, Regression, Suggestion, Feature Request, etc. ;
- le domaine de la demande : eg. Domain: X
- le rejet de la demande : e.g. Won't Fix, Working as Intended, Not a Defect, Out of Scope, Duplicate ;
- l'acceptation de la demande : e.g. Help Wanted, Planning, Milestone: X ;
- L'impact, la priorité, l'effort : e.g. Impact: X, Priority: X, Effort: X.
Planification de la demande
Une fois la demande acceptée, il convient d'en planifier la résolution. C'est à dire le moment où cette demande sera traitée, en fonction de la disponibilité des différentes ressources, des autres demandes en cours, et de leurs priorités/importances/impacts/efforts relatifs.
La feuille de route (roadmap), défini les futures (sous-)étapes du projet appelées jalons (milestone). Ils peuvent être des :
- jalons de version : une future release du projet, ou une contrainte temporelle.
- jalons d'évolution : un ensemble cohérent de modifications du projet (e.g. bugfix, ajout de fonctionnalités, etc.).
Les différentes demandes sont ainsi affectées à différentes personnes et jalons, ce qui définira qui les traitera et quand. La progression du projet peut ainsi être visualisée par le taux des demandes traitées au sein des différents jalons. Il est aussi possible de pondérer les demandes par une difficulté, temps estimé de traitement, etc. afin d'obtenir des taux de progression plus pertinents.
Traitement de la demande
Le processus de traitement d'une demande correspond usuellement à une succession d'étapes allant de la conception de la solution à sa validation finale, passant par son implémentation, vérification, intégration, déploiement, documentation, etc. Ces différentes étapes doivent être explicitées à la fois pour avoir un processus clair que les différents développeurs puissent suivre, mais aussi afin de pouvoir suivre l'évolution de chaque demande.
À cette fin, on utilise généralement le Kanban, un tableau composé de plusieurs colonnes correspondant aux différentes étapes de résolutions d'une demande, sur lesquelles ont déplace des posts-its correspondant chacun à une demande différente. Cela permet ainsi d'obtenir visuellement la progression des différentes demandes au sein du projet.
Documenter
La mémoire humaine est peu fiable (e.g. altérations, oublis), et difficile à transmettre proprement (e.g. déformations, incomplétudes). Le départ d'un collaborateur peut alors provoquer des pertes d'informations, et l'arrivée d'un nouveau nécessite la mobilisation de ressources afin de le former.
Documenter fixe les informations par écrit, permettant de les formaliser, conserver, et les transmettre. La transmission en est aussi bien moins coûteuse, nécessitant simplement de l'écrire, au lieu d'avoir à la répéter à toute personne qui en aurait l'utilité.
On peut distinguer 4 types de documentations dans le cadre d'un projet :
- la documentation du dépôt : les informations essentielles, sous forme de fichiers Markdown, e.g. README.md, INSTALL.md, CONTRIBUTING.md, LICENCE.md, ROADMAP.md, CHANGELOGS.md.
- la documentation API : décrit les API, générés automatiquement e.g. avec Sphinx, Doxygen, etc.
- la documentation développeur : indique comment bien utiliser les API.
- la documentation utilisateur : renseigne sur la manière d'utiliser le projet.
💡 Les tickets/issues ainsi que les commits sont aussi une forme d'informations. Afin de faciliter le suivi, il est d'usage de référencer, dans un commit, les issues qu'il adresse.
Markdown
⚠ Bien qu'il soit possible d'ajouter des balises HTML dans un fichier Markdown, cela s'accompagne de différentes limitations. Il est ainsi bien souvent préférable d'écrire la documentation directement directement en HTML.
Documentation du code
La documentation API liste et décrit les possibilités de l'API (classes, méthodes, etc). La documentation développeur donne des recommendations, et indique la bonne manière de faire pour un objectif précis. Par exemple, une documentation développeur indiquera, sous la forme de pseudo-tutoriels, comment créer, configurer, et ajouter un élément. Une documentation API listera les méthodes des différentes interfaces (e.g. , , ). La documentation API se focalise donc sur le contenu des API, quand la documentation développeur s'intéresse à leur bonne utilisation.
⚠ Les commentaires au sein du code ne doivent pas être une répétition du code, ils doivent être utilisés soit pour indiquer une nuance importante, pour référencer un issue, ou pour générer la documentation API.
Automatisation
Les différentes opérations (e.g. déployer, exécuter) doivent être partageables et reproductibles. Les différents processus doivent respecter et forcer les conventions définies au sein de l'équipe.
Automatiser les différentes opérations et processus permet de résoudre (en partie) ces problèmes. Par exemple un simple script permet d'effectuer simplement une série d'opérations complexes, tout en garantissant une exécution homogène au sein de l'équipe. Cette automatisation peut être de 4 natures :
- manuelle : exécuté manuellement.
- réactive : s'exécute suite à un événement.
- planifié : s'exécute à moment donné.
- procédurale : guide à travers une liste d'étapes.
Manuelle
Les scripts permettent de regrouper, au sein d'un fichier, une successions d'opérations, pouvant ensuite être exécutés. Comme ce sont des fichiers, ils peuvent aisément être diffusés au sein d'une équipe, e.g. par le biais d'un dépôt Git. Ces scripts peuvent être locaux à un projet, e.g. dans un dossier du dépôt, ou réutilisés à travers plusieurs projets, e.g. regroupés dans un dépôt dédié.
Par exemple, un script peut lancer un programme Python en fixant une série de paramètres, de sorte à ce que l'ensemble de l'équipe l'exécute de la même manière. Il peut ainsi servir à, e.g. définir afin d'éviter que les dossiers ne polluent le dossier du dépôt.
Les scripts ou lignes de commandes peuvent alors être appelés directement, par le biais d'alias (e.g. Shell, Git), mais aussi via des lanceurs ou raccourcis claviers. Un lanceur s'affiche dans la liste de vos applications, et possède quelques informations descriptives, dont une icône. Ils sont définis par un fichier dans :
Réactive
La réactivité consiste exécuté un code donné lorsqu'un événement associé survient. C'est le principe derrière la programmation orientée événement (Event-Driven Programming) en développement Web. À ne pas confondre avec la programmation réactive qui se base sur des systèmes de signaux, réagissant à des flux de données. Ces événements peuvent être de différentes natures et origines, e.g. des événements :
- de modifications sur le système de fichier
- liés au dépôt Git/Github
Écouter le système de fichiers
Un exemple classique de réactivité, est d'exécuter du code lorsque les fichiers sont modifiés, e.g. une re-compiler, exécuter les tests, synchroniser de fichiers, etc. On distingue alors :
- le watch mode : effectue une action donnée à chaque modifications, e.g. reconstruire le projet.
- le hot reload met à jour un processus en cours d'exécution, sans l'arrêter.
En développement Web, il est ainsi classique d'utiliser des outils comme Live Server sur VSCode, (ou la commande ) afin d'obtenir un site Web de développement dont les pages se rafraîchissent automatiquement à la modifications des fichiers.
Pour cela, on utilise des outils qui vont écouter un dossier donner, et nous informer lorsqu'une modification survient :
Git Hooks
Il est aussi possible de réagir avant et après certains événements d'un dépôt Git, e.g. pour :
- avant un commit, formatter le code, s'assurant ainsi de son uniformité (e.g. espaces vs tabulations) ;
- avant d'accepter des commits, les valider, e.g. tests, linters, etc.
- etc.
Pour réagir à un événements, il suffit alors de créer un hooks, un simple fichier exécutable du même nom que l'évent qu'on souhaite écouter. Si l'exécutable retourne une erreur, l'action est généralement annulée, e.g. :
- : e.g. pour interdire la création du commit si e.g. ne passe pas les tests.
- : e.g. pour vérifier si le message de commit respecte un format donné.
- (serveur) : e.g. pour rejeter les commits reçus si e.g. ne passent pas les tests.
- (serveur) : e.g. pour déployer le code automatiquement.
⚠ Il ne peut n'y avoir qu'un exécutable par événement. Ainsi, si on souhaite exécuter plusieurs scripts pour un même événement, il faudra créer un exécutable principal qui appellera les différents scripts.
💡 Par défaut, les hooks se trouvent dans le dossier , ils ne sont donc ni versionnés, ni partagés, chaque clone du dépôt ayant ses propres hooks. La configuration Git permet d'en changer le dossier, permettant ainsi de les ajouter au versionnage.
Github CI/CD
De nombreux services Web (e.g. Github, Gitlab, Gitea, etc) permettent d'héberger des serveurs Git, avec une gestion par le biais d'une interface Web. Il fournissent usuellement des outils dit de CI/CD dont l'utilisation varie en fonction du service Web. Dans le cadre de ce cours, nous utiliserons Github.
Ces outils fournissent des fonctionnalités plus avancées que les Git Hooks, permettant non seulement de rejeter des commits après exécution de tests, mais aussi de :
- construire les différents builds à distribuer (e.g. pour Linux, Windows).
- mettre à jour le site Web associé au dépôt, via les Git Pages.
- automatiser certaines actions sur les issues et pull requests.
- etc.
Pour cela les actions à effectuer sont décrit sous la forme de fichiers YAML (), contenant des propriétés dont la structure hiérarchique est définie par l'indentation :
Dictionnaire
Liste
Ces fichiers sont alors stockés dans le dossier du dépôt Git, puis "exécutés" par Github à chaque événement sur le dépôt. Pour cela, il faut définir :
Quand s'exécuter :
Que faire :
Les étapes (steps) peuvent être de deux natures différentes :
Commande Shell :
Action prédéfinie :
💡 La première des étapes est usuellement de cloner le dépôt :
Chaque tâche (job) s'exécute dans son propre conteneur isolé, elles ne partagent ainsi pas le même contexte d'exécution. Une première manière de partager de petites données, est :
Dans le producteur de la valeur:
Dans le consommateur de la valeur:
- ajouter une entrée à .
- assigner un identifiant à l'étape.
- déclarer l'exportation.
- Déclarer la dépendance.
- Utiliser la valeur.
Une autre manière de partager des données est d'utiliser des artefacts, une sorte d'archive qui pourra être téléversé puis téléchargé par les tâches :
Dans le producteur:
Dans le consommateur:
⚠ Certaines actions nécessitent des droits particuliers, e.g. pour mettre à jour les Git Pages :
Planifié
Certaines tâches peuvent être planifiées, comme des mises à jours, sauvegardes, synchronisations de données, nightly builds, générations de rapports, etc. Pour cela on utilise CRON (chronos) afin de planifier des exécutions régulières de tâches données. La crontab (chronos table) est un fichier où chaque ligne correspond à la planification d'une tâche, sous la forme .
Le moment est défini par 5 paramètres :
- minute (0-59) ;
- heure (0-23) ;
- jour du mois (1-31) ;
- mois (1-12) ;
- jour de la semaine (1-7).
On peut ainsi indiquer un moment précis, e.g. "les premiers janvier à 0H00" : . Mais d'autres notations existent :
- "tous les" e.g. heures.
- "toutes les 5" e.g. heures.
- "de 1 à 5" e.g. heures.
- "à 1 et 5" e.g. heures.
Pour manipuler la crontab, on utilise les commandes suivantes
- : afficher.
- : éditer (recommandé)
- : remplacer.
⚠ Il est important de planifier ses tâches au bon moment. Pour des tâches lentes et peu critiques, on les planifie usuellement la nuit, de sorte à avoir un impact minimal. En revanche, pour des tâches plus critiques, qui peuvent échouer, on les planifiera de sorte à pouvoir réagir immédiatement de sorte à pouvoir corriger le problème au plus tôt. Ainsi on évite ce genre d'opérations le vendredi après-midi peu avant le week-end.
CRON est cependant peu approprié pour des postes de travail. En effet, si l'ordinateur n'est pas allumé au moment indiqué, la tâche ne s'exécutera pas. Ainsi, si les mises à jours sont prévues toutes les jours à 3h, elle ne seront pas effectuées si l'ordinateur est éteint à ce moment.
Ainsi, on utilisera alors anacron (ana- à rebours) qui permet d'exécuter les tâches de manières périodiques même si l'ordinateur était éteint au moment où elles auraient dû être exécutées. Pour cela on place tout simplement des exécutables dans les dossiers :
L'année dernière, nous avions aussi vu activant un service au démarrage de l'ordinateur. Il est aussi possible d'exécuter des commandes à l'ouverture de la session dans les paramètres de la distribution (e.g. ).
Procédures
Il n'est pas suffisant de définir des procédures, il faut aussi s'assurer de leur respect. Il est alors important de mettre en place les outils techniques afin de guider les intervenants dans les différentes étapes, automatiser ce qui peut l'être, et s'assurer de la validité des opérations.
Par exemple, définir des templates pour les issues (e.g. bug, feature request, etc), structurant les issues, garantissant la présence des différents éléments requis, et facilitant le travail de lecture des tickets. En fonction du template choisi, des étiquettes peuvent être attribuées, facilitant les recherches et tries futurs. Sur de gros dépôts, l'IA peut alors être utilisée pour rechercher des issues similaires, générer un code minimal reproduisant l'erreur, etc.
L'intégration des différents outils entre eux aide aussi. Par exemple, les issues peuvent être assignées à des milestone ou à une roadmap, qu'on peut ensuite visualiser à travers d'un kanban. L'issue peut aussi être automatiquement fermée lorsqu'un commit la référence, e.g. .
Lors d'un Pull Request (PR), des tests peuvent être exécutés, et leur résultat affiché sur la PR. Des demandes de reviews peuvent être envoyés, avant l'acceptable du PR.
Certaines conventions peuvent être imposée par des outils comme des linters et formatteurs. Le CI/CD peut permettre d'automatiser certaines actions sur certaines branches, correspondant e.g. à une étape de validation donnée. Automatiser le passage d'une issue d'un état à un autre.