Cours C++.livre(Règles de programmation)

De
Publié par

CHAPITRE 20 Quelques règles de programmation en C++Le langage C++ 325einev Télécommunications mjnIl existe une littérature très (trop?) abondante concernant C++ et comment utiliser aumieux le langage, mais peu d’auteurs se sont risqués à définir ce qu’était qu’un bon program-me C++. Parmi les rares qui ont essayé, citons le remarquable ouvrage de Meyers (EffectiveC++, Addison-Wesley) qui enumère 50 règles de base à observer pour écrire un bon program-me C++. Ce chapitre énumère en bref les principales règles, sans expliquer forcément pour-quoi il est préférable de faire de cette manière plutôt que de telle autre.Les règles enumérées çi-dessous ne sont de loin pas exhaustives. L’auteur de ces règlesest ouvert à la critique (constructive) et aux suggestions. Sentez-vous libre, avec le temps,d’introduire de nouvelles règles, ou de modifier, voire supprimer certaines existantes.1. Ne pas porter du code C existant en C++ à moins que l’on y soit contraint, ou que le code doive subir d’importantes modifications de toutes manières. Modifier du code fonction-nant correctement pour le plaisir est une perte de temps. En revanche, il est parfois intéres-sant de recompiler le code C sans modifications au moyen d’un compilateur C++, car des erreurs peuvent être détectées par le compilateur C++.2. A l’inverse, profiter de la nécessité d’apporter d’importantes modifications à unfragment de code pour se poser la question si une réecriture en C++, avec une philo-sophie ...
Publié le : samedi 24 septembre 2011
Lecture(s) : 80
Nombre de pages : 6
Voir plus Voir moins
Quelques règles de CHAPITRE 20 programmation en C++
Le langage C++
325
einev
Télécommunications
mjn
Il existe une littérature très (trop?) abondante concernant C++ et comment utiliser au mieux le langage, mais peu d’auteurs se sont risqués à définir ce qu’était qu’un bon program me C++. Parmi les rares qui ont essayé, citons le remarquable ouvrage de Meyers (Effective C++, AddisonWesley) qui enumère 50 règles de base à observer pour écrire un bon program me C++. Ce chapitre énumère en bref les principales règles, sans expliquer forcément pour quoi il est préférable de faire de cette manière plutôt que de telle autre.
Les règles enumérées çidessous ne sont de loin pas exhaustives. L’auteur de ces règles est ouvert à la critique (constructive) et aux suggestions. Sentezvous libre, avec le temps, d’introduire de nouvelles règles, ou de modifier, voire supprimer certaines existantes.
1.Ne pas porter du code C existant en C++ à moins que l’on y soit contraint, ou que le code doive subir d’importantes modifications de toutes manières. Modifier du code fonction nant correctement pour le plaisir est une perte de temps. En revanche, il est parfois intéres sant de recompiler le code C sans modifications au moyen d’un compilateur C++, car des erreurs peuvent être détectées par le compilateur C++. 2.A l’inverse, profiter de la nécessité d’apporter d’importantes modifications à un fragment de code pour se poser la question si une réecriture en C++, avec une philo sophie «OO», ne serait pas profitable. 3.Faire une séparation très nette entre le créateur d’une classe et l’utilisateur. L’utili sateur n’a pas besoin de savoir ce qui se cache derrière la définition d’une classe (qui se soucie de savoir comment est implémenté un fichier en langage PASCAL?). Le créateur d’une classe doit tenir compte de l’utilisation qui sera faite, et s’assurer que la classe résultante est robuste, etque son usage est parfaitement transpa rent. L’utilisation de librairies n’est aisé que s’il est transparent. 4.Lorsque vous créez une classe, utilisez des noms aussi clairs que possible. Evitez tout de même les dénominations trop longues, difficiles à utiliser. Une règle qui est en train de se généraliser veut que les identificateurs de type commencent par une majuscule, et les instances par une minuscule. Microsoft fait exception à cette règle. 5.L’encapsulation vous permet, en tant que créateur de la classe, de cacher les détails de l’implémentation. Mieux ces détails seront cachés, plus il vous sera facile, dans le futur, d’adapter votre classe à de nouvelles exigences sans perturber les utilisa teurs de votre classe. Souvenezvous que vous n’avez aucun moyen de contrôler l’accès à un membre donnée public, et que l’introduction d’une méthode d’accès à ce même membre (défini cette fois comme privé) ne coûte généralement inline rien en exécution, tout en vous laissant la possibilité, ultérieurement, d’introduire tous les contrôles d’accès que vous jugerez adéquats. 6.Lors de l’analyse d’un problème, votre méthode d’analyse doit produire, au mini mum, la liste des classes dans le système, leurs définitions d’interfaces, et leurs rela tions (spécialement avec les classes de base). Si votre méthode produit plus d’éléments que ceux énumérés ici, réfléchissez si tous les éléments que vous avez produit sont nécessaires. 7.Les classes doivent être aussi simples que possible. Si une classe tend à devenir trop complexe (par exemple, une classe contenant plus de 20 méthodes), il est souvent plus intéressant de la découper en plusieurs classes plus simples.
326
Le langage C++
einev
Télécommunications
8.Commencer avec un interface aussi minimaliste que possible. Au fil du temps, les utilisateurs vous indiqueront dans quel sens vous devez étendre la fonctionnalité d’une classe. Si vous tentez de définir d’emblée un interface complet, il y a des chances pour que certaines fonctions ne soient pas ou très peu utilisées, vous obli geant par la suite à la maintenance de fragments de code que peu de clients utilisent (et fort probablement, vous ne serez pas dispensé de faire des extensions à votre classe, aussi complet que soit l’interface que vous aurez défini). Parfois, il est nécessaire, pour des raisons d’accès à un membre privé d’une classe 9. de base, de faire dériver une nouvelle classe d’une classe de base. Si l’accès au membre privé est la seule raison de l’héritage, il est préférable de construire une classe intermédiaire héritant de la classe de base, puis de définir une instance de cette classe intermédiaire comme membre de donnée (si possible privé) de la nou velle classe. Ceci permet souvent d’éviter le recours, plus tard, à de l’héritage mul tiple. De manière générale, une classe de base définit un interface pour les classes déri 10. vées. Si vous créez une classe de base, il est recommandé de définir les fonctions membres comme purement virtuelles, en laissant aux classes dérivées le soin de définir des implémentations. Ceci vous donne un maximum de généralité et de flexibilité. 11.Dans le même ordre d’idées, si une seule fonction d’une classe est virtuelle, com mencez par définir toutes vos fonctions membres comme virtuelles. Plus tard, lors que vous optimisez la classe, vous pourrez ôter le motclé «» là où il virtual n’est pas nécessaire. 12.Si vous devez définir quelque chose de nonportable (comme par exemple l’accès à un widget sous MOTIF), créez tout d’abord une abstraction pour cet élément, et définissez une classe autour de cette abstraction. Ainsi, votre élément nonportable est localisé, et ne se distribue pas allègrement au travers de tout votre programme. 13.Evitez l’héritage multiple. L’héritage multiple sert souvent à se sortir de situations difficiles, en particulier pour contourner des impossibilités dûes à des interfaces de classes mal définis sans que l’on ait le contrôle de la classe mal définie. Attendez d’être un programmeur C++ chevronné avant d’utiliser l’héritage multiple. 14.Evitez l’héritage privé. Bien qu’il soit parfois de quelque utilité, il est bien souvent possible de le remplacer par une relation de contenance, où la classe dont on voulait tout d’abord hériter le comportement par héritage privé se retrouve membre privé. 15.Lorsque vous définissez une relation d’héritage public, souvenezvous toujours de «is a». Si vous ne pouvez affirmer que l’objet dérivé «est un» objet de la classe de base, alors vous ne devez pas utiliser l’héritage public. Un chatest unmammifère, mais un alligatorn’est pasun mammifère, même si tous deux sont des prédateurs carnivores. 16.Si un seul membre d’une classe que vous avez définie est virtuel, alors le destruc teur doit forcément être virtuel également. 17.Préférez, dans la mesure du possible, la liste d’initialisation du constructeur à du code explicite pour initialiser une instance de classe.
Le langage C++
mjn
327
einev
Télécommunications
18.La surcharge d’opérateurs n’est qu’un artifice syntactique. Si cette surcharge n’apporte pas d’améliorations significatives à la lisibilité de la classe, évitez de sur charger. Dans le même ordre d’idées, ne créez qu’un convertisseur de type automa tique pour une classe donnée. 19.Faites d’abord fonctionner votre programme avant de l’optimiser. Ainsi, utiliser des fonctions inline, ne pas utiliser de fonctions virtuelles n’apporte rien à la stabilité de votre code. Donc, à moins que l’optimisation soit le seul et unique but du code que vous écrivez, concentrezvous sur la fonction du code avant son efficacité. 20.Ne laissez pas le compilateur faire les choses à votre place. Définissez toujours les constructeurs, destructeurs et opérateur = : ainsi, vous conservez le contrôle de votre classe. Si vous n’avez pas besoin de l’un de ces composants, déclarezle privé. 21.Si votre classe contient des pointeurs, vous devez déclarer le contructeur de copie, l’opérateur = et le destructeur, sans quoi, vous allez au devant d’ennuis. 22.pour définir desEvitez le préprocesseur dans la mesure du possible. Utilisez const valeurs etpour remplacer des macros. inline 23.Les programmeurs ayant fait leurs armes avec des langages comme PASCAL utili sent souvent la valeur d’un argument pour exécuter conditionnellement du code dans une fonction (ex: IF sex = Male THEN ....). C++ offre une bien meilleure méthode dans nombre de cas, qui est la surcharge de fonctions. 24.Minimisez la durée de vie de vos objets. Un objet ayant une durée de vie brève est généralement plus facile à gérer. Comme toute règle, il faut savoir ne pas abuser de cette pratique : la création et destruction forcenée d’objets risque de segmenter la mémoire disponible pour des applications ayant une grande durée de vie (plusieurs mois). Eviter à tout prix l’utilisation de variables globales. Une variable globale est visible 25. depuis tout le programme, et peut être modifiée sans contrôle, créant des fautes dif ficiles à identifier. Si vous avez absolument besoin de ressources globalement accessibles, faites une classe autour de ces ressources, et créez des méthodes contrô lant l’accès à ces ressources (même si ce contrôle n’est qu’un simple traçage à des fins de déboguage...). 26.Si vous définissez un opérateur, réfléchissez bien à ce que cet opérateur devra retourner. Le cas de l’opérateur = retournant incorrectement un(a = b = cÞ void Erreur!) doit rester à l’esprit. De même, pour prendre un autre exemple, que l’opéra teur [] retournant une valeur au lieu d’une référence. 27.Pensez aux cas particuliers lorsque vous définissez un opérateur. Un cas classique est l’opérateur = appliqué à luimême (x = x). 28.Lorsque vous définissez une fonction, prenez l’habitude de passer les arguments de préférence comme références constantes. L’appel ainsi généré a la simplicité d’un appel avec passage de paramètre par valeur, sans avoir pour autant l’inconvénient de nécessiter un appel à un constructeur et à un destructeur. 29.Prenez garde à la création d’objets temporaires. En particulier lorsque le construc teur et le destructeur sont complexes, le coût de la création d’une instance tempo raire peut s’avérer exagérèment élevé. Aussi, dans la phase d’optimisation, essayez
328
Le langage C++
mjn
einev
Télécommunications
de minimiser cette création. Ainsi, au lieu de retourner une valeur d’une fonction en la créant explicitement dans le corps de la fonction, essayez toujours de construire la valeur de retour dans l’ordre return : someType f(inti, int f) {....} on essayera de retourner la valeur de la manière suivante : return someType(i, j); au lieu de
mjn
someType xx(i,j); return xx; La seconde formulation a pour effet un appel inutile à un constructeur de copie et à un destructeur supplémentaires. 30.Ne faites que le minimum absolu au sein d’un constructeur. Cela vous procurera l’avantage de nécessiter moins de travail supplémentaire lors de la création d’objets temporaires, et votre constructeur sera d’autant plus facile à tester. 31.Le destructeur ne doit pas seulement libérer les ressources reservées par le cons tructeur, mais également toute ressource ayant été allouée pendant le cycle de vie de l’objet. Soyez particulièrement attentifs à ce fait lorsque votre classe contient des pointeurs, ou utilise des ressources extérieures (fichiers, sockets, stacks de pro tocole, etc...) 32.Ne créez vos propres templates que lorsque vous avez épuisé toutes les possibilités de trouver quelque chose ailleurs. Il existe sur le marché de très nombreuses librai ries incluant entre autres de nombreux templates bien testés. Il est plus sage d’utili ser ce code déjà abondamment testé que de tenter de réinventer la roue. 33.Lorsque vous créez un template, identifiez soigneusement le code ne dépendant pas du type des données, et mettez ce code dans une classe de base qui ne soit pas un template. D’une manière générale, lorsque des templates sont utilisés, il faut tou jours soigneusement se demander si on ne peut pas utiliser des classes génériques pour minimiser au maximum le volume de code que devra produire le compilateur. 34.Evitez les fonctions decomme (),etc... Ok, elles fonction stdio.h, printf nent, vous les connaissez (peutêtre) et elles sont simples d’emploi. Mais leur utili sation n’est pas soumise au contrôle de conformance de type, et il n’est pas possible de les dériver. Préférez l’utilisation de. D’une manière générale, si iostream vous avez la possibilité de choisir, préférez toujours la version C++ d’une librairie à une version C: cette stratégie est sûrement payante à moyen et long terme. Utilisez la clauseautant que possible et de manière systématique. Cette pra const 35. tique requiert un peu de discipline, et demande une grande consistance parmi les classes que vous écrivez, mais le bénéfice que l’on en retire, du fait du contrôle d’erreurs que peut effectuer le compilateur compense largement ces inconvénients. 36.Préférez les erreurs de compilation aux erreurs en exécution. Une erreur de compi lation n’aboutira jamais chez le client, alors que vous êtes tributaire de la qualité des tests effectués sur votre code pour découvrir l’erreur en exécution. Cette recom mendation est d’ailleurs à mettre en parallèle avec la précédente (35, page329).
Le langage C++
329
einev
330
Le langage C++
Télécommunications
mjn
Soyez le premier à déposer un commentaire !

17/1000 caractères maximum.