Cassons les idées reçues

“Mon build passe sur le serveur de CI. Parfait, le CI est accomplie.” “Mais ta feature branche existe depuis 1 semaine. En quoi est-ce du CI ?”

Je ne compte plus le nombre de fois avoir eu cette conversation, et misérablement échouer d’informer que sont les origines du Continuous Integration. La pratique du CI est d’intégrer régulièrement, au moins 1 fois par jour, les changements dans la branche principale. Et j’ignore pourquoi, la définition de CI est interprété comme étant un sujet à opinion. Je comprends que les efforts pour mettre en place un serveur de CI sont conséquents, remarquable, et très souvent bien employé (build, test, sonar, analyse de dépendances, etc.). Mais il est nécessaire de comprendre que la pratique de CI est une pratique au-delà de consommer un processus automatique de vérification de code. Autrement dit, voir un cycle de développement qui implique des branches vivant longtemps est contraire au CI.

La définition de la pratique de CI ne devrait pas être une matière de débat. Et tout à chaque fois que j’ai pu échouer à expliquer l’intention initiale du CI, que j’ai pu ressentir un énervement chez mon interlocuteur. J’ignore pourquoi, mais remettre en question l’idée que se font les gens à ce propos est sensible.

Cet article a pour objectif de rappeler ce qu’est la pratique de CI en 1er partie, et pourquoi la confusion est arrivée dans l’industrie informatique en 2nd partie. Certes, des éléments allant plus loin seront évoqués, mais si le sujet vous intéresse vraiment, les 3 sources ci-dessous vous seront beaucoup plus précieuses que ce que je peux apporter dans un blog.

Afin d’apporter une définition, prenons quelques exemples de la littérature.

Building Microservices, Sam Newman, 2015

L’auteur de Building Microservices, 2015, indique qu’il rencontre des équipes persuadées de respecter la pratique de CI, pour argument l’usage d’un serveur de CI, mais qui a ne respecte qu’une seule des 3 conditions.

  1. Est-ce que l’on intègre tout nouveau code dans la branche principale tous les jours ?
  2. Est-ce que des tests existent pour valider le code (et le sont-ils exécutés automatiquement) ?
  3. Quand un build échoue, peut-être à cause des tests, est-ce que la priorité numéro 1 de l’équipe est de le réparer ?

Au cours de ma carrière et comme indique l’introduction, la majorité des équipes avec lesquelles j’ai travaillé ne respecte que la seconde condition. C’est déjà louable, cela revient à faire du ‘Continuous Build’ et ‘Continuous Unit Test’. Mais pas du Continuous Integration.

Aussi, le livre indique aussi que le “CI” est une pratique. Nous reviendrons dessus.

martinfowler.org

Dans son blog, L’auteur affirme cette fois “The essence of it lies in the simple practice of everyone on the team integrating frequently, at least daily, against a controlled source code repository. This practice is called “Continuous Integration” (or in some circles it’s called “Trunk-Based Development”)”

Cette fois, de manière plus simple, le CI est annoncé comme étant la pratique d’intégrer au moins une fois par jour, le code dans le repository.

Il est nécessaire aussi de lire l’article pour comprendre aussi qu’il voulait signifier de pousser le nouveau code dans la branche principale. Pas dans une branche de feature, de fix, ou autre. Juste la branche principale, comme informe l’énoncé entre parenthèse.

Et enfin, une dernière référence, et pas des moindres :

Continuous Integration, Paul Duvall, 2007

Paul Duval informe des pratiques évoqués ci-dessus, et bien d’autre.

L’introduction du livre indique des pratiques similaires aux 2 premières ressources. Entre autre, la pratique de CI permet de recevoir un feedback rapide sur l’intégration du code, de sa qualité, de sa complexité et sa couverture de tests. Que ce soit pour la qualité du code produit, ou bien assurer que projet/application fonctionne correctement dans son ensemble, base de donnée comprise. Datant de 2007, les sujets des applications distribués n’est pas évoqué explicitement, le mot microservice n’existait pas encore. Toutefois, je suis convaincu qu’il aurait intégré qu’une application fonctionne réellement que si les composants ensembles fonctionnent (mais je m’égare, c’est une supposition.)

Cet article n’ayant par pour objectif d’expliquer de bout en bout ce qu’est le CI, le livre Continuous Integration le fait mieux que moi. Maintenant que les origines du CI sont rappelés. Voyons désormais les pratiques erronément appliquées avec le CI, ainsi que pourquoi la confusion est arrivée dans l’industrie informatique.

Toutefois, une note sur les pull requests ainsi que le développment dans la branche principale.

Mais les PRs, c’est bien non ?

Oui… et non. Comme tout en informatique, c’est une question de contexte et de balance.

L’usage des pull requests est une étape de contrôle et de validation. Idéale si nous ne pouvons pas faire confiance aux contributeurs d’un projet. Que ce soit pour un projet open source libre, ou bien par une équipe qui manque d’expérience, la pull request peut être utile.

Si toutefois, vous développez uniquement avec une équipe réduite, que vous vous faites tous confiance, que les tests sont écris avec sérieux, que seuls des états qui compilent et dont les tests sont verts sont commités, alors le cycle de développement ne requière plus d’étape de validation avant de rejoindre la branche principale.

Si je le peux, je propose le modèle Ship, Show et Ask https://martinfowler.com/articles/ship-show-ask.html. Mon seuil de choisir entre ces 3 possibilités sont adaptés à la sensibilité de chacune des équipes que je rejoins.

La pull request a un autre avantage, celui de revue de code, qui permet aux autres membres d’accepter un choix de design, ou de s’assurer qu’un edgy case soit couvert par des tests. Cet avantage est suffisamment conséquent pour assurer une étape de revue de code. Dans une méthodologie sans pull requests, la revue de code peut prendre 3 formes. Via le pair programming, la relecture de l’historique des commits, ainsi que la présentation du développement a l’équipe ultérieurement.

Le pair programming offre une revue en temps réel du code, permettant d’avoir un feedback d’approbation immédiat. Ainsi, le design du code est (in)validé immédiatement, diminuant le code superflu et chaque membre se concentre à du code qui rejoindra très probablement la production.

Les 2 autres formes sont des revues après avoir rejoint la branche principale. Efficace quand même, elles n’apportent pas autant d’avantages que le pair programming.

Si toutefois l’occasion de faire du pair programming était manqué, et que le développement mérite une validation, car elle apporte des changements fondamentaux (mais pas forcément volumineux), alors la pull request est une bonne option. Afin de respecter la pratique de CI, il est nécessaire que l’équipe fasse l’effort de valider la PR rapidement.

Un coucou à tous mes collègues qui se rappelle que je revoit les PRs dans la minute où je reçois le mail de notification. Et un big up pour tous ceux qui ont revu les PRs encore plus vite que moi.

Vous l’aurez compris, en entreprise, les pull requests ne sont pas la méthode de validation à favoriser. D’autres pratiques comme le pair programming offre un meilleur résultat.

Revenons au sujet initiale de l’article : d’où vient la confusion entre la pratique de CI, et serveur de CI ?

Cause de la confusion entre pratique de CI, et serveur de CI

Cette section est une supposition. Aucune preuve ne sera apportée. À ce jour, je lis que peu de sujet de pourquoi la confusion est extrêmement répandue. La littérature sur le sujet se limite à rappeler ce qu’est le CI réellement. Comme indiqué en intro, tout au long de ma carrière, j’étais confronté à ce manque de nuance dans l’usage de CI. Et fréquemment, il m’est arrivé de me demander d’où vient cette confusion. Cette seconde partie regroupe toutes les pistes d’explications que j’ai pu apporter.

Attention toutefois, tout est supposition.

Avec un marteau en main, tous les problèmes ressemblent à des clous

La majorité de mes contacts sont des développeurs, et nous savons tous faire la même chose : développer. Que ce soit dans ce contexte, ou d’autre, nous avons une tendance à coder des outils et des produits très rapidement. Très souvent, le réflexe de démarrer un développement démarre avant de chercher après l’existence d’un outil pour y arriver.

Je comprendrai qu’en lisant la phrase “Le CI a pour objectif d’intégrer le code fréquemment, et de tester automatiquement le code créé”, un filtre mental s’applique pour n’entendre que les mots “tester automatiquement”. Une fois ce filtre activer, il ne reste qu’à installer un serveur de CI, créer des pipelines, voir le résultat, adapter, fine tunner, optimiser, qui sont toutes les étapes qu’un projet agile classique pour améliorer les pipelines de CI.

De plus, grâce à ce serveur, il est possible de tester en continu les branches vivant longtemps. Ce qui donne un sentiment d’aussi remplir la première section de l’énoncé “intégrer le code fréquemment”. Malheureusement, l’objectif initial n’est pas complété.

Il est plus facile de produire un outil une fois que de changer sa manière de travailler de tous les jours

Nous sommes humains, et les humains ont des habitudes. Et elles sont difficiles à changer. Le livre Atomic Habits (James Clear, 2018) affirme que les habitudes sont des actions que nous faisons automatiquement, et qui s’installe, entre autre, suite à des feedbacks positifs de nos actions. Et que nous rejetons un comportement qui nous met dans une situation désagréable.

Dans le cas des feature branches, nous pouvons travailler sur une branche pendant plusieurs jours, et ne pas avoir de retour sur l’intégration du code. C’est une situation confortable, car nous avons le temps de travailler sur le code, de le peaufiner, de le rendre “parfait”(alerte à la mauvaise pratique. Préférez le YAGNI a une implémentation parfaite). Mais une fois que le développement de la fonctionnalité est complété, il est possible que le code ne soit pas compatible avec le reste du code, ou bien que nous devons corriger les conflits de merge. En une phrase, que l’étape d’intégration est désagréable. Et ce feedback désagréable nous incite à ne pas merger le code dans la branche principale fréquemment.

C’est aussi comme ça que j’explique pourquoi je complète mes timesheets toujours tard. À la fin, ça me prend plus temps de compléter les timesheets en dernière minute qu’au jour le jour.

Je pense que la pratique de CI requière de la discipline à cause de cette situation. Nous ne devrions pas avoir des habitudes sur des feedbacks qui nous poussent à un cycle de développement sous-optimal. Mais nous sommes humains.

Le serveur de CI est la partie visible de l’iceberg

Un serveur de CI produit un artefact beaucoup plus tangible que la pratique de CI. Une fois la pipeline terminé, il y a un rapport de test et d’analyse de code disponible. Ces rapports sont des aides à la décision. Il nous permet d’identifier du code qui a été produit sans test, de se mettre d’accord qu’un code est difficilement lisible à cause d’une complexité cyclomatique que nous n’avions pas identifié lorsque nous étions en train d’écrire.

Bénéficier des feedbacks positifs de ces rapports nous fait croire que la pratique de CI est complété. Avec cet outil bénéfique, il est facile de ne pas voir plus loin. Autant le serveur de CI est un outil formidable, il ne remplace pas la pratique de CI.

Un mot pour la fin

J’espère avoir éclairci ce qu’est originalement le Continous Integration. Avec un rappel des intentions initiales de la pratique de CIs, et donner des pistes d’explications pourquoi une confusion existe sur le sujet, j’espère vous avoir ouvert l’envie d’en apprendre plus. Si vous n’y avez rien appris, je me réjouis que vous soyez bien informé.

Au final, l’article pointe du doigt que nous ne devrions pas avoir à produire des branches vivants longtemps, ou long lived branches. Que le développement dans le tronc principal (Trunk Based Development, ou TBD) est une pratique pour un cycle de développement efficace. Les PRs peuvent être employées, à condition qu’elles soient fréquentes et revus très vite par les collègues.