XVIII. Annexe A : Le style de codage▲
Cet appendice ne traîte pas de l'indentation ni du placement des parenthèses ou des accolades, ce sera cependant mentionné. Il traîte des lignes de conduite générales utilisées dans l'organisation des codes de ce livre.
Bien que beaucoup de ces questions ont été introduites tout au long de ce livre, cet appendice apparaît à la fin de manière à ce que l'on puisse considérer qu'aucun sujet ne vous mettra en difficulté, et vous pouvez retourner à la section appropriée si vous ne comprenez pas quelque chose.
Toutes les décisions concernant le style de codage dans ce livre ont été considérées et prises délibérément, parfois sur une période de plusieurs années. Évidemment, chacun a ses propres raisons pour organiser le code de la manière dont il le fait, et j'essaye juste de vous expliquer comment j'en suis arrivé aux miennes ainsi que les contraintes et les facteurs environnementaux qui m'ont conduit à ces décisions.
Général
Dans le texte de ce livre, les identifiants (de fonction, de variable, et les noms de classe) sont écrits en gras. La plupart des mots clés sont aussi écrits en gras, sauf les mots-clés tellement utilisés que leur affichage en gras deviendrait pénible, comme “class” et “virtual.”
J'utilise un style de codage particulier pour les exemples de ce livre. Il a été développé sur un certain nombre années, et est partiellement inspiré par le style de Bjarne Stroutrup dans son livre original The C++ Programming Language. (64)Le style de formatage est matière à de nombreuses heures de débat animé, je vais donc juste dire que je n'essaye pas de dicter un style correct par mes exemples; j'ai mes propres raisons pour utiliser ce style. Comme le C++ est un langage de programmation libre de forme, vous pouvez continuer à utiliser le style que vous préférez.
Ceci dit, j'insisterai sur le fait qu'il est important d'avoir un style de formatage cohérent à l'intérieur d'un projet. Si vous effectuez une recherche sur Internet, vous trouverez un certain nombre d'outils qui peuvent être utilisés pour restructurer l'ensemble du code de votre projet pour atteindre cette cohérence essentielle.
Les programmes dans ce livre sont des fichiers qui sont extraits automatiquement du texte, ce qui permet de les tester afin de s'assurer qu'ils fonctionnent correctement. De ce fait, les fichiers de code imprimés dans ce livre vont tous fonctionner sans erreur de compilation si vous utilisez un compilateur conforme au standard du C++ (notez que tous les compilateurs ne supportent pas toutes les fonctionnalités du langage). Les erreurs qui devraient causer des erreurs de compilation sont commentées avec //! afin qu'elles puissent être facilement trouvées et testées en utilisant des méthodes automatiques. Les erreurs trouvées et rapportées à l'auteur apparaîtront dans un premier temps dans la version électronique du livre (sur www.BruceEckel.com) et plus tard dans les mises à jour du livre.
L'un des standards de ce livre est que tous les programmes sont compilables et peuvent subir l'édition de liens sans erreur (même s'ils vont parfois occasionner des avertissements). À cette fin, certains de ces programmes, qui démontrent seulement un exemple de code et ne représentent pas un programme complet, vont disposer de fonction main( ) vide, comme ceci
int
main() {}
Cela permet à l'éditeur de liens de travailler sans erreur.
La norme pour main( ) est de renvoyer un int, mais la norme déclare que s'il n'y a pas d'instruction return dans main( ), le compilateur doit générer automatiquement du code pour effectuer return 0. Cette possibilité (pas d'instruction return dans main( ) est utilisée dans ce livre (certains compilateurs peuvent encore générer un avertissement pour cela, mais ils ne sont alors pas compatibles avec le Standrard C++).
Les noms de fichiers
En C, la tradition est de nommer les fichiers d'en-tête (qui contiennent les déclarations) avec l'extension .h et les fichiers d'implémentation (qui occasionnent l'allocation pour le stockage et la génération du code) avec l'extension .c. C++ est passé par une évolution. Il a été développé au départ sur Unix, qui est un système d'exploitation faisant une différence entre les noms de fichiers en majuscule et ceux en minuscule. Les noms de fichiers étaient à l'origine de simples versions mises en majuscule des extensions du C: .H et .C. Cela ne fonctionnait évidemment pas sur les systèmes d'exploitation qui ne font pas la distinction entre majuscules et minuscules, comme DOS. Les fournisseurs C++ pour DOS utilisaient des extensions hxx et cxx, respectivement pour les fichiers d'en-têtes et pour les fichiers d'implémentation, ou hpp et cpp. Et puis, certaines personnes ont remarqué que la seule raison d'avoir une extension différente était de permettre au compilateur de déterminer s'il devait le compiler comme un fichier C ou un fichier C++. Comme le compilateur ne compile jamais les fichiers d'en-tête directement, seule l'extension du fichier d'implémentation devait être changée. La coutume est donc, sur presque tous les systèmes, d'utiliser cpp pour les fichiers d'implémentation et h pour les fichiers d'en-tête. Notez que lorsque vous incluez les fichiers d'en-tête du Standard C++, on utilise l'option de ne pas avoir d'extension, par exemple: #include <iostream>.
Les balises de commentaires de début et de fin
Un point très important de ce livre est que tous les codes que vous y trouvez doivent être vérifiés pour s'assurer qu'ils sont corrects (avec au minimum un compilateur). Ceci est accompli par extraction automatique des fichiers du livre. Pour faciliter les choses, tous les codes source qui sont censés être compilés (par opposition aux fragments de code, qui sont peu nombreux) disposent de balises commentées à leur début et à leur fin. Ces balises sont utilisées par l'outil d'extraction de code ExtractCode.cpp présenté dans le volume 2 du livre (que vous pouvez vous procurer sur le site www.BruceEckel.com) pour extraîre les codes source de la version en texte brut du livre.
La balise de fin de code indique simplement à ExtractCode.cpp qu'il est à la fin du code source, mais la balise de début de code est suivie par certaines informations concernant le répertoire dans lequel devra se trouver le fichier (généralement organisé par chapitre, ainsi un fichier qui se trouve dans le chapitre 8 aura la balise C08), suivie par deux points et le nom du fichier.
Du fait que ExtractCode.cpp crée aussi un makefile pour chaque répertoire, des informations concernant la manière dont le programme est créé et la ligne de commande utilisée pour le tester sont aussi incorporées dans le code source. Si un programme est destiné à être utilisé seul (qu'il ne doit pas être lié avec quoi que ce soit d'autre), il n'a aucune information supplémentaire. C'est aussi vrai pour les fichiers d'en-tête. Par contre, s'il ne contient pas de main( ) et qu'il doit être lié avec quelque chose d'autre, le code source contiendra un {O} après le nom de fichier. Si le programme est censé être le programme principal, mais qu'il doit être lié avec d'autres composants, une ligne séparée commence par //{L} et continue avec les fichiers qui doivent y être liés (sans extension, du fait qu'elles peuvent varier en fonction des plateformes).
Vous trouverez des exemples tout au long du livre.
Si un fichier doit être extrait, mais que les balises de début et de fin ne doivent pas être incluses dans le fichier extrait (par exemple, si c'est un fichier de données de test) la balise de début est directement suivie par un ‘ !'.
Les parenthèses, les accolades et l'indentation
Vous pourrez noter que le style de formatage de ce livre est différent de bien des styles traditionnels en C. Bien sûr, chacun pense que son style personnel est le plus rationnel. Cependant le style utilisé ici suit une logique simple, qui sera présentée couplée avec des idées sur les raisons pour lesquelles d'autres styles se sont développés.
Le style de formatage est motivé par une chose: la présentation, aussi bien à l'impression que lors de séminaires. Vous pouvez penser que vos besoins sont différents parce que vous ne faites pas beaucoup de présentations. Cependant, un code fonctionnel est plus souvent lu qu'il n'est écrit, et il devrait donc être facile à comprendre pour le lecteur. Mes deux critères les plus importants sont la “lisibilité linéaire” (la facilité avec laquelle un lecteur peut saisir le sens d'une seule ligne) et le nombre de lignes qui peuvent tenir sur une page. Ce dernier peut sembler futile, mais quand vous faites une présentation publique, cela distrait énormément l'assistance si le présentateur doit changer sans arrêt les transparents qu'il montre, et quelques lignes gâchées peuvent en être responsables.
Tout le monde semble d'accord avec le fait qu'un code entre deux accolades doit être indenté. Ce sur quoi les gens ne sont pas d'accord ' et le point sur lequel il y a le plus d'incohérence entre les styles de formatages différents – est ceci : où mettre les accolades ouvrantes ? Cette seule question est, je pense, ce qui cause de telles variations dans les styles de formatage de code (pour une liste des différents styles de formatages de code, voyez C++ Programming Guidlines de Tom Plum et Dan Sals, Plum Hall 1991.) Je vais essayer de vous convaincre que beaucoup de styles de codages courants nous viennent d'avant les contraintes émanant du C Standard (avant les prototypes de fonctions) et sont donc inappropriés maintenant.
D'abord, ma réponse à cette question clé : les accolades ouvrantes devraient toujours aller sur la même ligne que leur “précurseur” (par cela, j'entends « tout ce dont traite le corps : une classe, une fonction, une définition d'objet, une instruction if, etc.”). C'est une règle unique, cohérente que j'applique à tous les codes que j'écris, et qui rend le formatage du code beaucoup plus simple. Cela rend la “lisibilité linéaire” plus grande, par exemple, quand vous regardez cette ligne:
int
func(int
a);
Vous savez, par le point-virgule à la fin de cette ligne, qu'il s'agit d'une déclaration, et que cela ne va pas plus loin, mais quand vous voyez cette ligne:
int
func(int
a) {
vous savez immédiatement que vous avez affaire à une définition parce que la ligne finit par une accolade, et non par un point-virgule. En utilisant cette approche, il n'y a pas de différence entre l'endroit où vous placez une parenthèse dans une définition multiligne :
int
func(int
a) {
int
b =
a +
1
;
return
b *
2
;
}
et pour une définition sur une ligne, qui est parfois utilisée pour les fonctions inline:
int
func(int
a) {
return
(a +
1
) *
2
; }
De manière similaire, pour une classe:
class
Thing;
est la déclaration d'un nom de classe, et
class
Thing {
est la définition d'une classe. Vous pouvez dire, rien qu'en regardant cette seule ligne, si c'est une déclaration ou si c'est une définition. Et, bien sûr, le fait de mettre l'accolade ouvrante sur la même ligne, au lieu de la mettre sur une ligne séparée, vous permet de faire tenir plus de lignes sur une page.
Alors, pourquoi avons-nous tellement d'autres styles ? En particulier, vous aurez noté que beaucoup de gens créent les classes en suivant le style ci-dessus (qui est celui que Stroutrup utilise dans toutes les éditions de son livre The C++ Programming Language aux éditions Addison-Wesley), mais créent les définitions de fonctions en plaçant l'ouverture des accolades sur une ligne unique (qui engendre aussi beaucoup de styles d'indentation différents). Stroutrup fait celasauf pour les courtes fonctions inline. Avec l'approche que je décris ici, tout est cohérent – Vous nommez ce que c'est ( class, fonction, enum, etc.) et vous placez l'accolade sur la même ligne, pour indiquer que le corps de cet élément va suivre. En outre, c'est la même chose pour les fonctions inline et pour les définitions de fonctions ordinaires.
J'affirme que le style des définitions de fonctions utilisé par beaucoup de gens vient d'avant le prototypage de fonction du C, dans lequel on ne déclare pas les arguments à l'intérieur des parenthèses, mais bien entre la parenthèse fermante et l'ouverture de l'accolade (ainsi que le montrent les racines du C en assembleur):
void
bar()
int
x;
float
y;
{
/* corps ici */
}
Ici, ce serait assez maladroit de placer l'accolade sur la même ligne, et personne ne le fait. Cependant, cela a occasionné des décisions variées sur la manière dont les accolades devraient être indentées par rapport au corps du code ou à quel niveau elles devraient se trouver par rapport au “précurseur”. Ainsi, nous avons beaucoup de styles de formatage différents.
Il y a d'autres arguments en faveur du fait de placer l'accolade ouvrante sur la ligne qui suit celle de la déclaration (d'une classe, d'une structure, d'une fonction, etc.). Ce qui suit vient d'un lecteur et vous est présenté afin que vous sachiez quels sont les problèmes :
Les utilisateurs expérimentés de ‘vi' (vim) savent qu'enfoncer deux fois la touche ‘]' va emmener l'utilisateur à l'occurrence suivante du ‘{‘ (or ^L) à la colonne 0. Cette fonctionnalité est extrêmement utile lors de la navigation dans le code (saut à la définition de fonction ou de classe suivante). [Mon commentaire: quand j'ai travaillé au départ sous Unix, GNU Emacs venait juste d'apparaître et je m'y suis habitué. De ce fait ‘vi' n'a jamais eu de sens pour moi, et ne ne pense donc pas en terme de “position colonne 0.” Cependant il existe un certain nombre d'utilisateurs de ‘vi', et ils sont donc affectés par cette question.]
Placer le ‘{‘ sur la ligne suivante élimine la confusion dans le code de conditions complexes, ce qui aide à la lisibilité. Par exemple:
if
(cond1
&&
cond2
&&
cond3) {
statement;
}
Ce qui précède [affirme le lecteur] n'est pas très lisible. Cependant,
if
(cond1
&&
cond2
&&
cond3)
{
statement;
}
sépare l'instruction ‘if' du corps, ce qui résulte en une meilleure lisibilité. [Votre avis sur la véracité va varier en fonction de ce à quoi vous êtes habitués.]
Finalement, il est bien plus facile d'aligner visuellement les accolades quand elles sont alignées sur la même colonne. Elles « collent » visuellement beaucoup mieux. [Fin du commentaire du lecteur]
La question de savoir où placer l'accolade ouvrante est probablement la question la plus discordante. J'ai appris à scanner les deux formes, et en fin de compte on en vient à la méthode avec laquelle vous êtes à l'aise. Cependant, je note que le standard officiel Java (trouvé sur le site web de Sun) est, de fait, le même que celui que je vous présente ici – puisque de plus en plus de gens commencent à programmer dans les deux langages, la cohérence entre les modèles de langages peut être utile.
L'approche que j'utilise supprime toutes les exceptions et les cas spéciaux, et produit logiquement un style unique d'indentation. Même à l'intérieur d'un corps de fonction, la cohérence persiste, comme dans:
for
(int
i =
0
; i <
100
; i++
) {
cout <<
i <<
endl;
cout <<
x *
i <<
endl;
}
Le style est facile à apprendre et à retenir– vous employez une règle simple et cohérente pour tout votre formatage, pas une pour les classes, deux pour les fonctions (monoligne inlines contre multiligne), et probablement d'autres pour pour les boucles for, instructions if, etc. La cohérence seule la rend, je pense, digne de considération. Par-dessus tout, C++ est un langage plus récent que le C, et bien que nous devions faire beaucoup de concessions au C, nous ne devrions pas y importer des causes de problèmes supplémentaires. De petits problèmes multipliés par de nombreuses lignes de code deviennent de gros problèmes. Pour un examen complet du sujet, quoi que traité pour le C, voyez C Style: Standards and Guidelines, de David Straker (Prentice-Hall 1992).
L'autre contrainte sur laquelle je dois travailler est la largeur, puisque le livre a une limitation à 50 caractères. Que se produit-il quand quelque chose est trop long pour tenir sur une ligne? Eh bien, j'essaye encore d'avoir une approche cohérente sur la manière dont les lignes sont brisées, de manière à ce qu'elles puissent être facilement visualisées. Aussi longtemps que quelque chose fait partie d'une définition unique, liste d'arguments, etc. les lignes de continuation devraient être indentées d'un niveau par rapport au début de la définition ou de la liste d'arguments.
Les noms identifiants
Ceux qui sont familiers avec Java auront noté que j'ai utilisé le standard Java pour tous les identifiants de noms. Cependant, je ne peux pas être parfaitement cohérent là-dessus parce que les identifiants provenant des bibliothèques Standards C et C++ ne suivent pas ce style.
Le style est assez simple. La première lettre d'un identifiant est en majuscule uniquement s'il s'agit d'une classe. Si c'est une fonction ou une variable, la première lettre est en minuscule. Le reste de l'identifiant consiste en un ou plusieurs mots, l'un suivant l'autre, mais distingués en mettant la première lettre du mot en majuscule. Ainsi, une classe ressemble à ceci:
class
FrenchVanilla : public
IceCream {
L'identifiant d'un objet ressemblant à:
FrenchVanilla myIceCreamCone(3
);
et une fonction ressemble à:
void
eatIceCreamCone();
(qu'il s'agisse d'une fonction membre ou d'une fonction régulière).
La seule exception concerne les constantes de compilation ( const ou #define), pour lesquelles toutes les lettres de l'identifiant sont en majuscules.
La valeur de ce style est que la mise en majuscule a du sens – vous êtes en mesure de voir dès la première lettre si vous parlez d'une classe ou d'un objet/méthode. C'est particulièrement utile quand on accède à des membres de classes static.
Ordre d'inclusion des fichiers d'en-tête.
Les en-têtes sont inclus dans l'ordre “du plus spécifique vers le plus général.” Cela signifie que les fichiers d'en-tête du dossier local sont inclus en premier, puis ceux de mes propres en-têtes qui me servent d'« outils », tels que require.h, puis les en-têtes des bibliothèques tierces, puis celles des bibliothèques du Standard C++, et finalement les en-têtes des bibliothèques C.
La justification de cet ordre vient de John Lakos dans Large-Scale C++ Software Design(Addison-Wesley, 1996):
Des erreurs latentes d'utilisation peuvent être évitées en s'assurant que le fichier .h d'un composant est analysé par lui-même – sans déclarations ou définitions extérieures… L'inclusion du fichier .h à la première ligne du fichier .c vous assure qu'aucune information critique intrinsèque à l'interface physique du composant ne manque dans le fichier .h (ou, s'il y en a, que vous le remarquerez dès que vous essayerez de compiler le fichier .c).
Si l'ordre d'inclusion des fichiers d'en-tête va “du plus spécifique au plus général”, alors il est plus probable que si votre fichier d'en-tête ne s'analyse pas de lui-même, vous découvrirez plus tôt la raison et cela vous évitera des ennuis en cours de route.
Ajouter des gardes dans les fichiers d'en-tête
Les gardes d'inclusion sont toujours utilisés dans un fichier d'en-tête durant la compilation d'un seul fichier .cpp pour éviter l'inclusion multiple d'un fichier d'en-tête. Les gardes d'inclusion sont implémentés en utilisant la directive préprocesseur #define et en vérifiant que le nom n'a pas encore été défini. Le nom utilisé comme garde est basé sur le nom du fichier d'en-tête, avec toutes les lettres du nom de fichier en majuscule et en remplaçant le ‘ .' par un underscore (ND: _ ). Par exemple:
// IncludeGuard.h
#ifndef INCLUDEGUARD_H
#define INCLUDEGUARD_H
// Corps du fichier d'en-tête ici...
#endif
// INCLUDEGUARD_H
L'identifiant sur la dernière ligne est ajouté par souci de clarté. Cependant, certains préprocesseurs ignorent tout caractère suivant un #endif, ce n'est pas un comportement standard et l'identifiant est donc commenté.
Utilisation des espaces de nommages
Dans les fichiers d'en-tête, toute “pollution” de l'espace de nommage dans lequel le fichier d'en-tête est inclus doit être scrupuleusement évitée. La raison en est que si vous changez l'espace de nommage en dehors d'une fonction ou d'une classe, vous provoquerez le changement dans tout fichier qui inclut votre fichier d'en-tête, ce qui peut se traduire par toutes sortes de problèmes. Aucune déclaration using d'aucun type n'est autorisée en dehors des déclarations de fonctions, et aucune directive globale using n'est autorisée dans les fichiers d'en-tête.
Dans les fichiers cpp, toute directive globale using n'affectera que ce fichier, et elles sont donc généralement utilisées dans ce livre pour produire un code plus facilement lisible, spécialement dans les petits programmes.
Utilisation de require( ) et de assure( )
Les fonctions require( ) et assure( ) définies dans require.h sont utilisées de manière cohérente tout au long de ce livre, de manière à ce qu'elles puissent reporter correctement les problèmes. Si vous êtes familiers avec les concepts de préconditions et postconditions(introduites par Bertrand Meyer) vous reconnaîtrez que l'utilisation de require( ) et de assure( ) fournissent plus ou moins la précondition (c'est le cas en général) et la postcondition (occasionnellement). Ainsi, au début d'une fonction, avant que tout élément du “cœur” de la fonction ne soit effectué, les préconditions sont testées pour être sûr que tout est en ordre et que toutes les conditions nécessaires sont remplies. Après que le “cœur” de la fonction ait été exécuté, certaines postconditions sont testées pour s'assurer que le nouvel état des données se situe dans un périmètre prédéfini. Vous noterez que les tests de postconditions sont rares dans ce livre, et que assure( ); est principalement utilisé pour s'assurer que les fichiers ont été ouverts avec succès.