Bonnes pratiques C++ (1/n)

Ce billet appartient à une série consacrée aux bonnes pratiques de développement C++. Certains sont toutefois applicables à d'autres langages. Je réunirai sans doute tous ces billets dans un futur petit ouvrage, plus facile à consulter qu'un blog.

Fixez-vous des règles de codage

À la différence de langages comme Python, où le nommage des identificateurs et la présentation du code constituent la syntaxe, le C++ autorise la plus grande liberté en matière d’organisation des sources, de nommage des classes ou des méthodes, d’indentation des blocs, de répartition du code entre fichiers, etc. Il en résulte une vaste diversité de style de codes sources, certains faisant même preuve d’une assez grande créativité. Il suffit de parcourir quelques projets C++ sur GitHub pour s’en convaincre.

En réalité, le style n’est pas très important. Un développeur professionnel peut s’adapter sans trop d’effort à n’importe quel style de code C++. Ce qui est difficile et pénible en revanche, c’est de passer d’un style à l’autre au sein du même projet ou pire, au sein d’un même fichier.

La principale raison pour laquelle vous devez vous fixer des règles de codage est donc celle-ci : la cohérence, pour que tout le monde dans l’équipe écrive le même style de code. L’effort initial demandé à chacun pour s’adapter à un style qui n’est peut-être pas spontanément le sien sera compensée par la lisibilité et la maintenabilité du code produit. Et ce n’est pas qu’une affaire de style : il s’agit aussi de ne pas avoir dans la même équipe un développeur qui ne jure que par std::string et un autre qui ne jure que par char * et les fonctions historiques de manipulation de chaînes de caractères.

En matière de style, plein de points sont l’objet de débats interminables et quasi religieux : tabulations ou espace, accolade ouvrante en fin de ligne ou au début de la ligne suivante, règles de nommage des identificateurs, etc. Franchement, cela n’a aucune importance, toutes ces options présentent des avantages et des inconvénients. Les deux objectifs principaux sont la lisibilité (rappelez-vous : une des principales fonctions du code est de pouvoir être relu facilement) et la cohérence. Arbitrez un choix avant de démarrer les développements et tenez-vous-y.

Attention aussi aux règles trop strictes. Je ne crois pas à la vertu de règles rigides comme « une fonction ne doit pas faire plus de 40 lignes » ou bien « un nom de variable ne doit pas faire moins de 3 caractères ». Vous allez vous retrouver avec un code inutilement alambiqué parce qu’un développeur aura rusé pour réduire sa fonction de 41 lignes à 40, ou bien avec des variables de boucles nommées iii parce que le classique (et universellement accepté) i est interdit. Préférez des règles souples. Par exemple : les fonctions doivent avoir une longueur raisonnable de l’ordre de la hauteur d’un écran, les variables doivent avoir autant que possible un nom significatif, etc.

Attention également aux règles désuètes. Par exemple, à une époque, toute une génération d’ingénieur a appris à l’école qu’une fonction ne devait avoir qu’un seul point d’entrée et un seul point de sortie. On sait aujourd’hui que ce genre de contraintes produit du code difficile à lire : soit parce qu’il faut écrire des conditions tarabiscotées quand on doit sortir en avance d’une fonction (par exemple en cas d’erreur) soit parce que cela conduit à un nombre exagéré de niveaux d’imbrication de blocs if…else. Au contraire, une bonne pratique moderne serait de favoriser plutôt le early return, c’est-à-dire de sortir d’une fonction dès que c’est nécessaire. Il en va de même pour la notation hongroise, qui consistait à préfixer les noms des variables par leur type, par exemple pszFoo pour pointer on string zero-terminated, ou bien bFoo pour un boolean, ou iFoo pour integer. Il est maintenant admis qu’une telle habitude ne fait qu’alourdir la lecture du code et n’apporte aucun gain en matière de robustesse ou de maintenabilité. De plus, le type des variables est déjà indiqué par ailleurs ; il s’agit donc de duplication d’information et une information dupliquée finit toujours par diverger, par exemple le jour où l’on doit changer le type de int à long mais que personne n’a ni le temps ni le courage de renommer les centaines d’occurrences de la variable iSomething en lSomething.

Personnellement, j’aime utiliser les règles de codage de Google. Elles sont exhaustives, argumentées, bien pensées et produisent un style de code que je trouve très lisible.

Utilisez un contrôleur de code source

Un contrôleur de code source est indispensable dès que l’on travaille en équipe sur un projet. Il autorise chacun à travailler sur sa copie des sources et réalise la fusion des modifications effectuées par les différents développeurs. Il permet de mener des tâches en parallèle, comme développer une fonctionnalité d’un côté tout en corrigeant un bug urgent de l’autre, et fusionner le tout à la fin. Il sert de sauvegarde permanente, rendant virtuellement impossible de perdre du travail. Il a également une fonction d’archivage, permettant de retrouver l’état des sources à n’importe quelle date dans le passé. Enfin, s’il est bien utilisé et couplé avec un gestionnaire de projet tel que Jira ou Team Foundation Server, il permet d’établir une traçabilité entre les spécifications, les tâches de développement, les tests, les bugs, et le code effectivement produit par l’équipe.

Aussi incroyable que cela puisse paraître, je rencontre parfois des entreprises qui de nos jours, n’utilisent toujours pas de contrôleur de code source. Chaque développeur possède une copie du projet sur son poste et quand cela est nécessaire, tout le monde synchronise ses modifications en se passant des fichiers sur des clefs USB. La perte de temps est considérable et les risques d’erreurs énormes. Il va sans dire qu’une telle façon de travailler n’est plus acceptable.

De nos jours, hors produits commerciaux, le choix d’un contrôleur de code source se résume aux outils libres Git et SVN. Chacun a ses avantages et ses inconvénients.

  • SVN à l’avantage de la simplicité. Il repose sur des concepts faciles à comprendre et à se représenter mentalement, l’utilisation est intuitive, d’autant plus qu’il existe des surcouches graphiques simples à manipuler. Il est rare de casser quoi que ce soit ou de perdre du code, même en cas de fausse manœuvre. En revanche, ses fonctionnalités sont limitées.
  • Git est un outil plus difficile à maitriser. Il est parfois ardu de se faire une image mentale claire de son fonctionnement et en cas de fausse manœuvre, il est possible de se mettre dans des situations d’où il sera acrobatique de sortir sans rien perdre. En revanche, il a l’avantage de la souplesse et de la puissance. Il peut également fonctionner en local, sans dépôt central sur un serveur externe, ce qui est très utile pour un petit projet personnel, par exemple.

L’objectif n’est pas de vous imposer un outil plutôt qu’un autre ; documentez-vous sur chaque outil, voyez lequel semble le plus adapté à votre besoin et surtout, choisissez en tenant compte de l’avis et des compétences de l’équipe qui devra l’utiliser.

Quel que soit le contrôleur retenu, il est impératif de décrire et de normaliser un « workflow ». Il existe des dizaines de façons de gérer du code source, et plus encore avec Git : à quel moment créer des branches, comment les nommer, quels développeurs poussent sur quelles branches et à quel moment, comment s’effectuent les fusions des modifications (merge ou rebase, avec ou sans squash, etc.) Sauf besoin spécifique, nul besoin de réinventer la roue, internet regorge de workflows pour Git. Le plus connu est Git Flow.

Enfin, n’hésitez pas à vous assurer que tout le monde dans l’équipe maîtrise le système et le workflow, en particulier si vous utilisez Git. Beaucoup de développeurs ne sont pas familiarisés avec ces outils, ou bien ont acquis dans leurs missions précédentes des habitudes de travail différentes de celles que vous souhaitez mettre en place. Il vaut mieux passer quelques heures à se former en début de projet, plutôt que d’avoir des membres de l’équipe mal à l’aise, qui n’oseront pas demander de l’aide, et qui finiront par perdre du travail suite à une fausse manipulation.