Conseils de codage en C/Lisibilité des sources
Les conseils suivants rendent les logiciels plus maintenables.
Leur respect a pour but de :
- faciliter la lecture des codes sources écrits en C
- diminuer l'effort nécessaire pour diagnostiquer les déficiences ou causes de défaillance, ou pour identifier les parties à modifier.
Cartouche d'entête (c_lis_1)
modifierChaque fichier source (.c, .h) et chaque fonction C doivent être précédés d’un cartouche qui pourra contenir :
- Le nom de l'unité ou de la fonction.
- Son rôle et ses points de programmation critiques.
- Pour une fonction, la description des arguments et des codes retours.
- La date, l’auteur et le rôle de chaque modification.
- Sa licence et droits d'utilisation
- La mention de documents de conception ou de référence
Justification
modifierLe cartouche de l’unité permet de faciliter sa compréhension d'un point de vue général. Les cartouches de fonctions permettent de comprendre leur rôle et conditions d'appel sans se plonger dans le code source . Ces entêtes sont indispensables à la gestion de la configuration et à la maintenance.
Exemple
modifier/*******************************************************
Nom ......... : limites.c
Role ........ : Affiche les caractéristiques numériques
pour les types du langage C
Auteur ...... : Thierry46
Version ..... : V1.1 du 10/3/2008
Licence ..... : GPL
Compilation :
gcc -Wall -pedantic -std=c99 -o limites.exe limites.c
Pour exécuter, tapez : ./limites.exe
********************************************************/
Outils
modifier- La plupart des environnements de développement bâtissent un squelette de cartouche d'entête lors de la création d'un nouveau fichier
- Pour les projets importants, développés en équipe, les versions des fichiers sources pourront être gérées par un logiciel spécialisé comme CVS, subversion...
- L'utilisation d'un outil de gestion de la documentation comme Doxygen permet, à l'aide d'une syntaxe particulière dans les commentaires, de générer à partir des sources la documentation de programmation du projet.
Indentation des instructions (c_lis_2)
modifierLes instructions imbriquées sont indentées à l’aide de tabulations ou d'espaces de façon homogène dans tous les fichiers sources du projet.
Justification
modifierLa maintenance et la relecture des sources sont facilités par une présentation homogène.
Exemple (extrait)
modifier //...
// Teste si les valeurs lues sont les valeurs attendues.
for (i=0; i<(int)nbInt; i++)
{
(void)printf("%d, ", valIntLu[i]);
if ( valIntLu[i] != valInt[i] )
{
(void)printf("Au lieu de %d\n", valInt[i]);
exit(EXIT_FAILURE);
}
}
Outils
modifier- L'outil UNIX indent permet d'améliorer la présentation des fichiers sources écrits en C. De nombreuses options permettent de paramètrer le style de ses sorties. Elles peuvent être passées sur la ligne de commande ou mieux dans un fichier de configuration spécifique.
Indentation des commentaires (c_lis_3)
modifierLes commentaires suivent l’indentation des instructions de code.
Justification
modifierAugmente la lisibilité.
Une instruction par ligne (c_lis_4)
modifierNe pas écrire plus d'une instruction par ligne.
Justification
modifierAugmente la lisibilité.
Décomposer les instructions longues (c_lis_5)
modifierCoder des lignes simples.
Justification
modifierIl est quelquefois tentant de coder des lignes longues :
- Dans le domaine numérique, le programmeur écrit des formules compliquée juxtaposant de nombreuses fonctions et opérateurs.
- Le langage C permet de mélanger tests, appel de fonctions, opérations.
Le code parait, à première vue, plus compact, plus efficace. Il faut cependant décomposer les lignes longues en plusieurs, en utilisant des variables intermédiaires. Les compilateurs récents se chargeront d'optimiser efficacement le code pour vous. Le résultat sera plus facile à comprendre par d'autres. Les problèmes de priorité pourraient être atténués.
Constantes symboliques et macros en majuscule (c_lis_6)
modifierLes noms des constantes symboliques et des macros instructions doivent être écrits en majuscule.
Justification
modifierDans un fichier source, permet des distinguer rapidement les constantes des variables.
Exemple
modifier#define LONGUEUR_NOM_FICHIER 15
#define STR(s) #s
#define XSTR(s) STR(s)
Pas de lignes trop longues (c_lis_7)
modifierLes instructions ne doivent pas dépasser la colonne 80. Les instructions longues doivent être placées sur plusieurs lignes.
Justification
modifierLes lignes courtes seront plus faciles à comprendre par une personnes chargée de maintenir le logiciel.
Exemples
modifierMauvais :
#define sonne(N) { int n; for (n=N; n>0; n--) fputc(0x7, stderr);}
(void)printf("\nProgramme %s, fichier source "__FILE__"\nCompile le "__DATE__" a "__TIME__"\n", argv[0]);
Meilleur :
#define sonne(N) \
{ \
int n; \
for (n=N; n>0; n--) fputc(0x7, stderr); \
}
//...
(void)printf("\nProgramme %s, fichier source "__FILE__
"\nCompile le "__DATE__" a "__TIME__"\n",
argv[0]);
Nommage des identificateurs (c_lis_8)
modifierVous devez donner des noms significatifs aux identificateurs.
- Évitez les noms trop courts sans signification fonctionnelle : v, vv, vvv...
- Ne différenciez pas deux identificateurs uniquement en changeant la casse de certains caractères : Fichier, fichier.
- Ne différenciez pas deux identificateurs uniquement par un nombre trop restreint de lettres.
- N'utilisez pas de caractère souligné en tête ou en fin d'identificateur.
- Utiliser une règle de nommage cohérente dans le projet : mot séparés par des soulignés (fichier_parametres_calcul) ou mots collés commençant par des majuscules sauf première lettre (fichierParametresCalcul).
Justification
modifierFacilite la lisibilité et la maintenabilité.
Cohérence des identificateurs (c_lis_9)
modifierDès lors qu’un identificateur de variable est défini pour une entité significative, il faut utiliser ce même identificateur quelle que soit l’unité de code considérée.
Justification
modifierFacilite l'analyse du programme.
Arguments du programme principal (c_lis_10)
modifierLes arguments du programme principal sont standards : int main(int argc, char *argv[])
Justification
modifierAméliore la lisibilité du code. argv[0] représente le nom de lancement du programme et peut être utilisé pour les messages d'erreur. La récupération des arguments du programme pourra se faire avec les fonctions getopt(), getsubopt().
Limiter l'utilisation des opérateurs ++ et -- (c_lis_11)
modifierLes opérateurs ++ et -- ne sont autorisés que pour les indices de boucles et les pointeurs, à condition qu’aucun autre opérateur n’apparaisse et que seule l’utilisation post-fixée soit utilisée.
Justification
modifierFacilite l’analyse et la maintenance par des programmeurs habitués à d’autres langages.
Exemple
modifier- Correct :
for (i=0; i<n; i++) {//...
. - Moins bon :
t[i++]=f(i++);
Limiter l'utilisation de l'opérateur de test ternaire (c_lis_12)
modifierL’utilisation de l’expression conditionnelle ? : est interdite en dehors des macros.
Justification
modifierFacilite l’analyse et la maintenance par des programmeurs habitués à d’autres langages.
Ne pas économiser les accolades (c_lis_13)
modifierUtilisez des accolades {} autour d'une ou des instructions d'un bloc. Ce bloc peut faire partie d’une structure de contrôle comme if - else - for - do - while.
Justification
modifierRend le code plus lisible et évite les erreurs lors de l'ajout d'une instruction dans un bloc qui n'en contient qu'une seule.
Exemple
modifierMauvais :
for (i=0; i<n; i++)
table[i] = 1;
Meilleur :
for (i=0; i<n; i++)
{
table[i] = 1;
}
Exemple d'erreur provoquée par la violation de ce conseil :
if (test == 2)
{
for (i=0; i<n; i++)
table[i] = 1;
table2[i] = 2;
}
/* Suite... */
L'instruction table2[i] = 2;
donne faussement l'impression de faire partie de la boucle for, ce qui est renforcé par l'indentation et l'accolade fermante du if.
Si table2 était dimensionné à n, les indices acceptables iraient de 0 à n-1 inclus. Lors de l'exécution de l'instruction table2[i] = 2;
, i vaudrait la valeur n, ce qui provoquerait l'écriture d'une valeur hors de l'espace mémoire réservé pour le tableau table2.
Il s'en suivra :
- Le débordement hors de l'espace utilisateur qui provoquera une violation mémoire : Sanction rapide par arrêt du programme exception SIGSEGV.
- L'écrasement du contenu d'un autre pointeur situé juste après l'espace table2 qui lorsqu'il sera utilisé plus tard (100 lignes plus bas ?) provoquera une autre catastrophe : Cette erreur peut être difficile à détecter sans l'utilisation d'un outil qualité d'analyse dynamique.
- Ces erreurs peuvent arriver aléatoirement selon le système, le compilateur utilisé ou le chargement du programme en mémoire.
Bien aligner les accolades (c_lis_14)
modifierUne accolade fermante se trouve toujours à la verticale de l’accolade ouvrante correspondante.
Justification
modifierPermet de mieux repérer les blocs d'instructions conditionnelles, les corps de boucles surtout en cas de structures imbriquées.
Exemple
modifier// Compilation : gcc -Wall -pedantic -std=c99 -o essai.exe essai.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const int nbLigne = 3;
const int nbCol = 4;
int table[nbLigne][nbCol];
for(int ligne = 0; ligne < nbLigne; ligne++)
{
for(int col = 0; col < nbCol; col++)
{
table[ligne][col] = ligne + col;
}
}
// Impression
for(int ligne = 0; ligne < nbLigne; ligne++)
{
for(int col = 0; col < nbCol; col++)
{
(void)printf("table[%d][%d] = %d,",
ligne, col, table[ligne][col]);
}
(void)puts("");
}
return EXIT_SUCCESS;
}
Éviter d'utiliser les opérateurs d'affectation spécifique au C (c_lis_15)
modifierL’utilisation de l’assignation composée (+=, -=, *=, %=, /=)
et de l’affectation multiple (v1 = v2 = v3;)
est déconseillée.
Justification
modifierFacilite l’analyse et la maintenance par des programmeurs habitués à d’autres langages.
Ne pas déclarer plusieurs variables dans la même instruction (c_lis_16)
modifierIl faut déclarer chaque variable séparément et non les unes à la suite des autres, séparées par des virgules.
Justification
modifierLa déclaration de plusieurs variables dans une même instruction peut provoquer des erreurs de type pour les variables.
Une déclaration séparée permet aussi de décrire dans un commentaire en bout de ligne le rôle de chaque variable.
Exemple
modifier// Declaration incorrecte :
// nomFicSortie est de type char au lieu de char * et ne peut donc être initialisé à NULL
char *nomFicEntree = NULL, nomFicSortie = NULL;
// Declaration correcte mais pas lisible
char *nomFicEntree = NULL, *nomFicSortie = NULL;
// Meilleur
char *nomFicEntree = NULL; // Fichier de parametres du calcul
char *nomFicSortie = NULL; // Fichier pour les points calculés
Seuils pour les métriques déterminant la facilité d'analyse (c_lis_17)
modifierLes métriques suivantes influent sur la facilité d'analyse d'un programme (ISO/IEC 9126) :
- VG : nombre cyclomatique : nombre de chemins linéairement indépendants dans un graphe connexe g, V(g) = A - N + 1 avec A : nombre de graphe entre les nœuds du graphe et N : nombre de nœuds du graphe.
- STMT : Nombre d'instructions exécutables entre les accolades de début et de fin de la fonction.
- FCOM : Fréquence des commentaires : F_COM = (BCOM+BCOB) / STMT, avec BCOM = Nombre de blocs de commentaires dans la fonction, BCOB : nombre de blocs de commentaires avant la fonction.
- AVGS : Taille moyenne des instructions, calculée à partir du nombre d'opérateur et d'opérande distincts.
Seuils pour les métriques :
- VG : de 1 à 20
- STMT : de 1 à 100
- FCOM : de 0,2 à 1,2
- AVGS : de 2 à 10
Justification
modifier- VG trop élevée montre qu'une fonction est trop complexe et mériterait d'être décomposée en plusieurs ou réanalysée.
- STMT trop élevée indique que la fonction est trop longue. Il faudrait la diviser ou peut-être appeler des fonctions.
- FCOM : Habituellement les programmes ne sont pas assez commenté : FCOM faible. Cependant un programme avec trop de commentaires peut aussi montrer des problèmes (trop de ruses d'un expert qui aurait dû écrire plus simplement ???).
- AVGS : Les instructions trop longues sont difficiles à comprendre et peuvent provoquer des problèmes de priorité des opérateurs, de conversion de type involontaires.