Ma carrière professionnelle de développeur C a débuté en 1994 et j’ai naturellement adopté avec enthousiasme le C++ en 1999. J’étais un développeur C++ heureux et j’avais considéré la nouvelle version du standard C++11 juste comme une évolution tant attendue. En 2015, je réalise enfin à quel point, il a révolutionné l’écosystème C++ et ses bouleversements dans l’état d’esprit de la communauté. Je me passionne alors pour C++11, C++14, puis C++17, je m’implique beaucoup, je deviens un référent pour mes collègues, je donne des conférences sur ce langage, j’organise des meet‐ups à Paris, je publie de nombreux articles C++ sur LinuxFr.org…

En 2018, une expérience change radicalement ma façon d’appréhender le développement logiciel.

Sommaire

Mieux développer pour les utilisateurs finaux

Comprendre le client et développer vite

En 2018, une expérience change radicalement ma façon d’appréhender le développement logiciel. À cette époque, une équipe C++ se retrouve surchargée et ne peut implémenter toutes les fonctionnalités attendues. On réfléchit à une solution de secours, et me voilà chargé de développer une application en Python en intégrant des briques sous licence libre. Je travaille alors en étroite collaboration avec les utilisateurs finaux et on sort l’application en quelques mois.

Ma nouvelle vision :

Il y a plus important que la technologie, c’est de se mettre dans la peau du client final et de comprendre ses frustrations au quotidien. Et on y arrive mieux en intégrant l’utilisateur final dans son équipe de développement.

L’application bricolée en Python donne entièrement satisfaction. Les utilisateurs sont contents d’avoir eu très tôt leur outil, et cela a coûté moins cher par rapport à toute une équipe C++ dans sa « tour d’ivoire ».

Intégrer l’utilisateur final dans son équipe

Attention, je ne dénigre pas le C++, celui‐ci peut être une bonne solution face à de nombreuses problématiques. Mais avant de courir tête baissée, s’assurer que la technologie choisie répondra bien aux attentes non exprimées de l’utilisateur final.

J’écris « attentes non exprimées » car bien souvent le développeur se base sur sa propre interprétation d’un document présenté comme LA spécification qui a en plus été rédigée par un intermédiaire (MOA, MOE, Business Analyste, Product Owner, Architecte…).

Nous l’avons peut‐être déjà tous constaté, l’utilisateur final ne sait pas vraiment ce qu’il veut, et a beaucoup de difficulté à exprimer clairement par écrit ses attentes.

Un intermédiaire (MOA, MOE, BA, PO, Archi) est nécessaire pour lui permettre de prendre du recul et pour traduire une demande fonctionnelle en exigence technique.

Mais l’intermédiaire rajoute une couche intermédiaire ! C’est humain, l’intermédiaire aura tendance à se rendre indispensable. Et sans le faire exprès, l’intermédiaire évitera que développeurs et utilisateurs échangent en direct.

Le top est d’avoir l’utilisateur final dans son équipe de développement, même si ce n’est pas dans le même bureau. Avec les habitudes de travail à distance, développeurs et utilisateur peuvent clavarder (chat) régulièrement.

Faire des sprints d’une journée

La durée idéale d’un sprint c’est la journée.

Le matin on échange rapidement avec le client de ce que l’on pourrait faire, on s’attelle à la tâche, on livre, l’utilisateur peut tester, on re‐livre… Et, en fin de journée, on débriefe très rapidement.

L’intérêt du Python (par rapport au C++) dans ce mode de fonctionnement c’est la livraison : on peut se permettre de livrer directement le code source et hop l’utilisateur exécute l’application ! Ainsi, dans mon cas, quand l’utilisateur lançait le démarrage de l’application, la branche master du dépôt Git était automatiquement mise à jour. J’essayais quand même de le prévenir quand une nouvelle version était sur la branche master.

La joie de livrer souvent, rapidement, et d’avoir du feed‐back dans la foulée. :-)

Le choix du langage : une comparaison

Le C++ ne serait donc pas la panacée ?

Nous pourrions caricaturer :

en C++, le développement est lent, mais l’application est très rapide ;

en Python, le développement est rapide, mais l’application est très lente1.

Bien souvent, le client final a besoin rapidement d’une fonctionnalité, même si l’exécution n’est pas super optimisée. En livrant rapidement cette fonctionnalité, le client gagne en maturité et a de nouvelles idées, de nouvelles façons pour optimiser son travail… et ainsi de suite avec des itérations courtes.

WebSocket en Python

Pour une application existante, nous avons besoin de fournir une interface WebSocket basique afin que nos clients puissent accéder à un service de Souscription/Publication.

Je développe la fonctionnalité en utilisant Python et Socket.io. Les tests JavaScript sont concluants. Cependant, nous nous apercevons que Socket.io rajoute son protocole par dessus le protocole WebSocket. Nous allons donc forcer les clients à devoir utiliser Socket.io ce qui n’est pas acceptable.

Nous voulons offrir une simple WebSocket afin que le client ne soit pas enfermé dans une technologie et soit libre d’implémenter son logiciel comme il le souhaite.

Je m’oriente alors vers WAMP avec Autobahn… Mais, rebelote, le P dans WAMP signifie Protocol.

Pour ne pas réinventer la roue, je cherche alors une solution qui implémente déjà la fonctionnalité Souscription/Publication et idéalement avec des coroutines ( async et await ).

WebSocket en C++

C’est alors que je découvre uWebSockets (µWS), une implémentation C++17 à couper le souffle qui intègre la fonctionnalité Souscription/Publication tout en conservant le protocole WebSocket de base. \o/

Mon équipe est enthousiaste. Je clone le projet, j’adapte un exemple C++ à nos besoins et je présente un résultat convainquant. C’est agréable de compiler un projet C++ qui ne tire pas des dizaines de dépendances. :-)

Je précise que pour ce nouveau projet, je viens de rejoindre une autre organisation, et j’ai été embauché, en partie, pour mes connaissances pointues en C++17.

La compilation C++ est archaïque

En Python, Node.js, Ruby, Go, Rust on a pip , npm , gem , go-get , cargo qui simplifie le téléchargement des dépendances, et l’intégration au projet avec un import xxxx . De plus, certains IDE prennent en charge cette gestion des dépendances.

En C++, la gestion des dépendances et leur compilation ne sont pas standardisées. En fait, cela n’a pas beaucoup évolué depuis les origines : le C++ a quarante ans et se compile toujours dans le même esprit que le C qui a lui soixante ans.

Pour cela, nous avons le choix du compilateur, dont voici une sélection avec leurs dates de naissance :

GCC et son g++ (GNU, libre, 1987-2019) ;

(GNU, libre, 1987-2019) ; LLVM et son clang++ (Apple, libre, 2007-2019) ;

(Apple, libre, 2007-2019) ; AOCC et son clang++ (AMD, libre, 2017-2018) ;

(AMD, libre, 2017-2018) ; ICC et son icc (Intel, non libre, ????-2018) ;

(Intel, non libre, ????-2018) ; MSVC et son cl (Microsoft, non libre, 1993-2019) ;

(Microsoft, non libre, 1993-2019) ; C++Builder et son bcc64 (Embarcadero, non libre, 1997-2019).

Construction logicielle C++

Pour la construction logicielle (build), nous avons une dizaine d’outils pour nous abstraire du compilateur, en voici une petite sélection sous licence libre :

make et son Makefile ;

et son ; Ninja et son build.ninja ;

; Jam remplacé par Boost.build et leur Jamfile ;

; SCons et sa configuration en Python ;

Waf qui ambitionne de remplacer SCons.

Générateurs de configuration pour la construction logicielle C++

Et pour nous abstraire de ces outils de construction logicielle, nous utilisons des générateurs de configuration de build, dont voici des projets toujours actifs :

Autotools génère du Makefile ;

; CMake génère Makefile , build.ninja et les fichiers projet pour de nombreux IDE ;

, et les fichiers projet pour de nombreux IDE ; Premake en cours de réécriture active depuis une dizaine d’années ;

xmake génère Makefile et les fichiers projet pour quelques IDE ;

et les fichiers projet pour quelques IDE ; Meson génère build.ninja et est compatible avec quelques IDE.

Autres outils de construction logicielle C++

Avec les outils ci‐dessus, le build et les tests de non régression d’une importante application C++ peut parfois prendre une demi‐journée.

C’est beaucoup trop pour attendre si ce que l’on a implémenté est correct !

D’autres outils de construction logicielle ont donc été développées dans le but de construire et tester en un minimum de temps :

Bazel codé en Java et géré/financé par Google ;

Buck codé en Java et géré/financé par Facebook ;

Pants codé en Python et géré par une communauté ;

Please codé en Go et géré/financé par Thought Machine.

Ces projets ne réutilisent pas les outils de build cités plus haut, et ne génèrent pas les fichiers projet des IDE.

En revanche, ces outils analysent finement le graphe de dépendance, gèrent de gros dépôts de code source, parallélisent la construction logicielle sur tous les cœurs (CPU) de nombreuses machines (cloud). Les étapes du build sont mémoïsées pour éviter de refaire la même opération N fois (par exemple, pour éviter de compiler un fichier non modifié, ou de lier une bibliothèque inchangée).

Sans avoir à recourir à ces outils, on peut améliorer les temps de compilation C++ et d’édition de liens (link) avec ces deux bons vieux outils :

ccache pour éviter de recompiler (ou lier) un fichier inchangé ;

pour éviter de recompiler (ou lier) un fichier inchangé ; distcc pour distribuer le build sur plusieurs machines (voir aussi icecream).

Ces outils ccache et distcc sont pris en charge par CMake, SCons, …

Les dépendances : gestion, essais, échecs et solution finale

Gestion de dépendances en C++

Mais au fait, comment gérer les dépendances C++ ? Quel est le gestionnaire de paquets C++ officiel (package manager) ? Quel est l’équivalent C++ pour les commandes pip , npm , gem , go-get et cargo ?

Eh bien… disons que nous avons des initiatives encourageantes :

le groupe de travail SG15 (du comité de standardisation du C++) réfléchit à une nouvelle approche pour compiler le C++ (voir leurs propositions P1482 et P1484 en anglais) ;

(du comité de standardisation du C++) réfléchit à une nouvelle approche pour compiler le C++ (voir leurs propositions P1482 et P1484 en anglais) ; le projet build2 est très bien pensé et dont son auteur avait proposé de standardiser arborescence des projets C++ avec P1204 ;

est très bien pensé et dont son auteur avait proposé de standardiser arborescence des projets C++ avec P1204 ; le projet conan utilise une configuration en Python et propose déjà 600 paquets C++ disponibles sur les deux dépôts publics principaux (conan-central et bincrafters) ;

utilise une configuration en Python et propose déjà 600 paquets C++ disponibles sur les deux dépôts publics principaux (conan-central et bincrafters) ; le projet vcpkg se base sur CMake et propose 1000 paquets C++ dont une bonne partie sont compatibles Windows, GNU/Linux et macOS ;

se base sur CMake et propose 1000 paquets C++ dont une bonne partie sont compatibles Windows, GNU/Linux et macOS ; le projet Hunter se base également sur CMake et propose environ 300 paquets C++.

Attention à ne pas confondre l’exécutable b du projet build2 avec l’exécutable b2 du projet Boost.build cité plus haut.

Copier les dépendances dans son code source

Une autre façon de gérer très simplement ses dépendances C++ est de carrément copier le code source de celles‐ci (et aussi le code source des dépendances des dépendances) avec le code source de l’application. Ça compile toujours.

Mais ce n’est pas une bonne pratique pour, au moins, deux raisons :

respecter les licences libres, c’est avant tout éviter de mélanger des codes sources de licences et d’auteurs différents ; mixer les codes source complique leur mise à jour (par exemple, comment intégrer la correction d’une faille de sécurité ?).

Microsoft me fait perdre deux jours

Je teste différents moyens pour obtenir le paquet uWebSocket : le package manager de ma distribution, Conan, Hunter… et finalement, le paquet uWebSocket a fraîchement été intégré au dépôt vcpkg. Je teste, et, ô miracle, cela télécharge le code source et me l’installe sur ma distribution GNU/Linux !

L’outil vcpkg fonctionne avec CMake, donc ce sera CMake qui gérera la construction logicielle.

Le temps de se documenter et bien prendre en main vcpkg, de tenter une intégration dans mon CMakeLists.txt , d’échouer, de recommencer…

Pourtant l’exemple avec SQLite3 fonctionne chez moi… Arg… En fait, c’est mal empaqueté, le find_package() ne pourra jamais trouver la bibliothèque uWebSocket fournie par vcpkg.

J’essaye de le faire moi‐même. Puis, je me rends compte au second jour que vcpkg est un outil amateur, à ne surtout pas utiliser pour aller en production : pas de version des dépendances, impossible de décider des options de compilation des dépendances…

Allez, on empaquette soi‐même cette bibliothèque

Bon, je connais bien C++, alors empaqueter proprement une bibliothèque qui ne dépend de rien ne devrait pas poser problème. Je retrousse mes manches et je commence à chercher si quelqu’un a déjà empaqueté uWebSockets…

Je trouve surtout que le mainteneur principal a supprimé les fichiers CMakeLists.txt et meson.build en 2017.

Et celui‐ci semble envoyer bouler les contributeurs proposant la compatibilité avec CMake, supprime le titre des pull requests et on trouve même des commentaires supprimés.

On trouve une explication dans la FAQ :

I don’t accept any specific build systems because I know from experience that doing so opens up hell. People simply cannot agree on which build system to use, or even how to use one particular build system. That’s why I no longer accept any such PRs. I’ve had too many CMake PRs dragging in completely different directions to know this is the only solution. You’ll have to clone the repo and create a new project in whatever build system you want to use.

En gros, le mainteneur principal refuse tout système de construction logicielle, car les développeurs veulent utiliser différents outils et que pour un même outil, les développeurs ne se mettent pas d’accord sur la bonne façon de faire.

Un commit très étonnant est le « Not my problem » qui change la licence et remplace dans tous les fichiers la ligne :

Copyright 2018 Alex Hultman and contributors.

par :

Authored by Alex Hultman, 2018-2019.

Intellectual property of third-party.

CMake gagne, Meson rentre à la maison

Mon IDE préféré pour le C++ est QtCreator, mais celui‐ci ne prend pas encore en charge Meson. Et je n’ai plus beaucoup de temps pour prendre en main GNOME Builder.

Et j’obtiens un joli CMakeLists.txt qui fonctionne sur deux différentes distributions GNU/Linux et aussi sur GitLab.

Je me rends aussi compte que GCC 8 ne compile pas cette bibliothèque, et que nous devons utiliser seulement Clang.

Mais, mon plus gros problème est que je n’arrive plus à faire compiler cette bibliothèque par QtCreator, alors qu’avec la ligne de commande cela fonctionne parfaitement… Argh… J’investis encore du temps…

Échec C++

Mes collègues ne comprennent pas pourquoi je mets autant de temps pour faire l’équivalent d’un pip install en C++. De plus, je vais devoir refaire ce même travail dès que j’intégrerai les autres bibliothèques : base de données, file d’attente de messages, journalisation…

Nous décidons ensemble d’arrêter de s’obstiner, de relever la tête, et de lister les possibilités :

continuer l’intégration de uWebSockets avec CMake ;

utiliser seulement les paquets Conan ou Hunter comme boost::beast pour la websocket (c’est même disponible avec apt install ) ;

pour la websocket (c’est même disponible avec ) ; revenir sur le Python et implémenter la Souscription/Publication ;

passer sur Node.js et intégrer la bibliothèque C++ uWebSockets avec la simplicité de npm (uWebSockets.js) ;

(uWebSockets.js) ; prendre le virage Rust, seul rival au C++ avec la simplicité de cargo ;

; Go to the langage Go qui est simple comme Python et est une des technos les plus performantes.

WebSocket en Node.js

J’ai plusieurs collègues très compétents en Node.js, alors c’est parti. Effectivement, l’installation de la dépendance est très simple et on implémente rapidement l’application.

Le grand avantage de Node.js (et de JavaScript en général) est la taille de la communauté et l’esprit de partage et d’innovation incroyables. Alors qu’une décennie est nécessaire en C++ pour se décider, la communauté JavaScript décide en quelques mois. Les projets, les concepts de programmation, les méthodes de travailler ne cessent d’évoluer.

J’entre dans un vaisseau qui se déplace à la vitesse de la lumière. En C++, la durée de vie d’un projet peut être d’une dizaine d’années. En Node.js, c’est plutôt dix mois. C’est aussi l’inconvénient, il faut s’adapter vite.

Mais, nous nous apercevons que le projet uWebSockets.js ne prend pas en charge la Souscription/Publication de la bibliothèque sous‐jacente uWebSockets. Ça ne marche pas.

Stop, nous venons de gagner en maturité, voyons voir les possibilités :

Node.js (implémenter la Souscription/Publication en JavaScript…) ;

Rust ;

Go.

Les principaux enseignements

WebSocket en Go

Nous prenons conscience que la partie WebSocket risque d’être un point sensible au niveau performance. Finalement, nous mettons entre parenthèses notre tentative Node.js.

Le Rust est encore trop jeune (peu de développeurs maîtrisant Rust sur le marché). Et nous avons un collègue devops fan du Go.

Notre collègue maîtrisant Go n’étant pas très disponible, on apprend se débrouille seul. Finalement, le principal du langage Go s’acquiert en quelques heures de programmation. Le test WebSocket est un succès et nous implémentons une première version basique de la publication/souscription en Go.

Nous mettrons en place des tests de performance reproductibles, et seulement après nous pourrons décider quelles sont les parties qui nécessitent d’être optimisées.

Valoriser l’échec

C’est grâce à nos échecs successifs que nous avons pu trouver notre solution pour les WebSockets ;

nous aurions pu décider qu’il fallait éviter les échecs ;

mais nous avons plutôt cherché à nous planter car l’échec est un très bon moyen d’apprendre (de ses erreurs) ;

se planter plus rapidement/souvent permet donc d’apprendre plus vite ou souvent ;

donc, essayons d’augmenter nos échecs (car c’est augmenter notre apprentissage) ;

pour encourager à tester une idée, nous devons valoriser l’échec ;

tester simplement/rapidement une idée permet de gagner en maturité.

Enseignements sur les langages

Ce n’est pas une bonne idée de coder dans son langage de programmation (C++) ; quand on est le seul dév. de l’organisation qui connaisse ce langage ;

JavaScript et Node.js ça décoiffe ;

Go c’est vraiment simple (car c’est limité).

Louanges sur la simplicité

La simplicité permet de se concentrer sur l’essentiel ;

faire simple est souvent compliqué ;

avoir une IT simple permet d’intégrer de nouvelles contraintes et idées plus facilement ;

dans un monde de plus en plus concurrentiel et qui s’accélère, la simplicité évite de se perdre dans le brouillard.

Je continue d’aimer le C++

Mais, non, je n’abandonne pas le C++ !

J’apprécie d’acquérir de nouvelles cordes à mon arc, de m’ouvrir l’esprit sur de nouvelles pratiques. Certaines technologies sont plus adaptées à certains contextes. N’ayons pas de dogme, choisissons la bonne technologie selon la situation.

Cependant, je ne sais pas quand je coderai à nouveau en C++… Peut‐être quand on aura un C++ package manager standardisé… :-)

On embauche

Un peu de publicité… Tu apprécies cet état d’esprit et que tu aimes coder en Python, Node.js, JavaScript/TypeScript/Angular ou Go ? N’hésite pas à me contacter sur oli (à) Lmap (point) org . Nous avons des postes à Paris et à Metz. On peut s’arranger pour le télétravail. Pour le moment, on ne publie rien sous licence libre. Mais je pousse pour libérer quelques briques intéressantes…

Aller plus loin