pdf - e-book - archive - github.com

1.13  Conseils techniques

1.13.1  Le problème

Dès que l’on code un programme d’une certaine envergure, bon nombre de problèmes se posent.

  1. Le débogage peut devenir un cauchemar sans fin.
  2. Ensuite le code écrit peut être une bouillie lisible que par son auteur.
  3. Le code peut contenir des redondances qui n’en facilite ni la lecture, ni le débogage.

Je passe les nombreuses difficultés et laideurs pouvant résulter d’un développement hâtif. Retenez que sans méthode, vous ne parviendrez pas à développer un projet important, tout simplement parce que le temps de débogage sera prohibitif, et parce que votre code, après les multiples bidouillages auxquels vous soumettrez pour déboguer sera un gros pavé bordélique et illisible.

1.13.2  Les règles d’or

La solution au problème mentionné ci-avant réside dans l’application des règles d’or suivantes :

Généralités

Avant toute chose, rappelons quelques évidences :

  1. indentez votre code, on doit pouvoir trouver, pour chaque accolade ouvrante, où se trouve l’accolade fermante qui lui correspond en moins d’un quart de seconde.
  2. utilisez des noms de variable explicites, le nom de la variable doit être suffisant pour pouvoir comprendre à quoi elle sert.

Fonctions

Commencez par découper votre très gros problème en plein de petits problèmes que vous traiterez individuellement avec des fonctions. Chaque fonction devra :

  1. porter un nom explicite : vous avez droit à 256 caractères...
  2. être précédée d’un commentaire décrivant clairement et sans paraphraser le code ce que fait la fonction.
  3. tenir sur une page : il faut que vous puissiez voir toute la fonction sans avoir à utiliser l’ascenseur, et comprendre aisément ce qu’elle fait.
  4. contenir au maximum trois niveaux d’imbrication : si vous avez plus de trois blocs (boucles, conditions, etc.) imbriqués, placez tout ce qui déborde dans une autre fonction.
  5. ne jamais utiliser de variables globales : l’utilisation des variables globales est réservée à certains cas très précis. Dans tous les autres cas, vos fonctions doivent utiliser les passages de paramètres (par adresse si nécessaire) et les valeurs de retour.
  6. être précédée de toutes les fonctions qu’elle appelle : d’une part un lecteur parcourant votre code le comprendra plus aisément si pour chaque appel de fonction, il a déjà vu la définition de cette fonction. Ainsi on pourra lire votre code dans l’ordre et éviter de slalomer entre les fonctions. D’autre part, cela vous évitera d’avoir à écrire les prototypes des fonctions en début de fichier. Vous réserverez aux cas où il et impossible (ou laid) de faire autrement les fonctions qui s’invoquent mutuellement.

Compilation séparée

Un fichier contenant plusieurs centaines de fonctions est impossible à lire, en plus d’être d’une laideur accablante. Vous prendrez donc soin de regrouper vos fonctions dans des fichiers, de les compiler séparément et de les linker avec un makefile.

1.13.3  Débogage

Vous êtes probablement déjà conscients du fait que le débogage occupe une partie très significative du temps de développement. Aussi est-il appréciable de la diminuer autant que possible.

  1. D’une part parce que lorsque ce temps devient prohibitif, il constitue une perte de temps fort malvenue.
  2. D’autre part parce que les multiples bidouilles opérées pour corriger les erreurs ne font souvent que nuire à la lisibilité du code
  3. Et enfin parce qu’un temps anormalement élevé est en général une conséquence d’une analyse et d’un codage hâtifs.

Aussi est-il généralement plus efficace de passer un peu plus de temps à coder, et beaucoup moins de temps à déboguer. Pour ce faire :

  1. Notez bien que tous les conseils énoncés précédemment sont encore d’actualité. En particulier ceux sur les fonctions.
  2. Construisez les fonctions dans l’ordre inverse de l’ordre d’invocation, et testez-les une par une. Les bugs sont beaucoup plus facile à trouver quand on les cherche dans une dizaine de lignes que dans une dizaine de pages. Et en assemblant des fonctions qui marchent correctement, vous aurez plus de chances de rédiger un programme correct.
  3. N’hésitez pas à utiliser des logiciels comme valgrind. valgrind examine l’exécution de votre code et vous rapporte bon nombre d’utilisation irrégulière de la mémoire (pointeurs fous, zones non libérées, etc.)

1.13.4  Durée de vie du code

Si vous ne voulez pas qu’un jour un développeur fasse un sélectionner/supprimer sur votre code, vous devez avoir en tête l’idée que quand quelqu’un reprend ou utilise votre code vous devez réduire au minimum à la fois le temps qu’il mettra à le comprendre et le nombre de modifications qu’il devra faire.

Le code doit être réutilisable

Cela signifie qu’un autre programmeur doit pouvoir poursuivre votre projet en faisant un minimum de modifications. Autrement dit, un autre programmeur doit pouvoir appeler vos fonctions aveuglément et sans même regarder ce qu’il y a dedans. Les noms des fonctions et les commentaires décrivant leurs comportement doivent être clairs, précis, et bien évidemment exempt de bugs. Sinon, un sélectionner/supprimer mettra fin à la courte vie de votre code. Notez bien par ailleurs que si vous avez réparti votre code dans des fichiers séparés de façon intelligente, votre code sera bien plus simple à réutiliser.

Le code doit être adaptable

Supposons que pour les besoins d’un projet, un autre développeur veuille partir de votre code, par exemple utiliser des listes chaînées au lieu de tableau, ou encore ajouter une sauvegarde sur fichier, etc. Si votre code est bien découpé, il n’aura que quelques fonctions à modifier. Si par contre, vous avez mélangé affichage, saisies, calculs, sauvegardes, etc. Notre développeur devra passer un temps considérable à bidouiller votre code, et cela sans aucune certitude quand à la qualité du résultat. Il fera donc un sélectionner/supprimer et recodera tout lui-même.

1.13.5  Exemple : le carnet de contacts

Je me suis efforcé, autant que possible, de suivre mes propres conseils et de vous donner un exemple. Je vous laisse à la fois observer mes recommandations dans le code qui suit, et traquer les éventuelles effractions que j’aurais pu commettre.

toutes les sources

util.h

#ifndef UTIL_H
#define UTIL_H

/*
  Saisit une chaine de caracteres de longueur sizeMax a l'adresse 
  adr, elimine le caractere de retour a la ligne et vide si necessaire 
  tous les caracteres supplementaires du tampon de saisie. 
 */

void getString(char* adr,  int sizeMax);

/*****************************************************/

/*
  Saisit un entier.
 */

int getInt();

/**********************************************************/

/*
  Echange les chaines de caracteres s1 et s2, de tailles 
  maximales sizeMax.
*/

void swapStrings(char* s1, char* s2, int sizeMax);

#endif

Télécharger le fichier

util.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void getString(char* adr,  int sizeMax)
{
  int len;
  fgets(adr, sizeMax, stdin);
  len = strlen(adr);
  if (*(adr + len - 1) == '\n')
      *(adr + len - 1) = 0;
  else
    while(getchar() != '\n');
}

/*****************************************************/

int getInt()
{
  char tab[10];
  getString(tab, 10);
  return atoi(tab);
}

/**********************************************************/

void swapStrings(char* s1, char* s2, int sizeMax)
{
  char temp[sizeMax];
  strncpy(temp, s1, sizeMax);
  strncpy(s1, s2, sizeMax);
  strncpy(s2, temp, sizeMax);  
  *(s1 + sizeMax - 1) = 0;
  *(s2 + sizeMax - 1) = 0;
}

Télécharger le fichier

tableau.h

#ifndef TABLEAU_H
#define TABLEAU_H

#define SIZE_MAIL 30
#define NB_MAILS 6

/*
  Implemente un carnet de contacts a l'aide d'un tableau.
  Une case inoccupee est representee par une chaine vide, 
  toutes les adresses sont disposees par ordre alphabetique 
  au debut du tableau. 
 */

/**********************************************************/

/*
  Affiche le carnet de contacts. 
*/

void afficheMails(char* mails);

/**********************************************************/

/*
  Retourne l'adresse du i-eme mail.
*/

char* getMail(char* mails, int i);
 
/**********************************************************/

/*
  Retourne le nombre de contacts.
*/

int nombreMails(char* mails);

/*****************************************************/

/*
  Creee un tableau de d'e-mails et le retourne. Ce tableau contient 
  NB_MAILS chaines de capacites longueur SIZE_MAIL initialises 
  avec des chaines vides. 
*/

char* creerMails();

/*****************************************************/

/*
  Libere la memoire
*/

void detruitMails(char* mails);

/**********************************************************/

/*
  Supprime l'adresse dont l'indice est passe en parametre.
*/

void supprimeMail(char* mails, int indice);

/**********************************************************/

/*
  Ajoute le mail mail dans le tableau mails.
*/

void ajouteMail(char* mails, char* mail);

/**********************************************************/

/*
  Remplace le indice-eme mail du tableau mails par mail. L'indice
  est suppose valide. 
*/

void changeMail(char* mails, char* mail, int indice);

/**********************************************************/

/*
  Ecrit tous les contacts de mails dans le fichier nomFichier.
*/

void sauvegardeMails(char* mails, char* nomFichier);

/**********************************************************/

/*
  Lit tous les contacts de mails dans le fichier nomFichier.
*/

void restaureMails(char* mails, char* nomFichier);

#endif 

Télécharger le fichier

tableau.c

#include "../../src/methodo/tableau.h"

#include<stdio.h>
#include<malloc.h>
#include<string.h>
#include<stdlib.h>

#include "util.h"

/**********************************************************/

void afficheMails(char* mails)
{
  int indice = 0;
  printf("Liste des contacts : \n");
  while(indice < NB_MAILS && *getMail(mails, indice))
    {
      printf("%d : %s\n", indice + 1, getMail(mails, indice));
      indice++;
    }
}

/**********************************************************/

char* getMail(char* mails, int i)
{
  return mails + i * SIZE_MAIL;
}
 
/**********************************************************/

int nombreMails(char* mails)
{
  int indice = 0;
  while(indice < NB_MAILS && *getMail(mails, indice))
      indice++;
  return indice;
}

/*****************************************************/

char* creerMails()
{
  char* adr = (char*)malloc(sizeof(char) * SIZE_MAIL * NB_MAILS);
  int i;
  if (adr == NULL)
    {
      printf("Heap overflow");
      exit(0);
    }
  for(i = 0 ; i < NB_MAILS ; i++)
      *getMail(adr, i) = 0;
  return adr;
}

/*****************************************************/

void detruitMails(char* mails)
{
  free(mails);
}

/**********************************************************/

void supprimeMail(char* mails, int indice)
{
  while(indice < NB_MAILS && *getMail(mails, indice + 1))
    {
      strncpy(getMail(mails, indice), 
       getMail(mails, indice + 1), 
       SIZE_MAIL);
      indice++;
    }
  if(indice < NB_MAILS)
    *getMail(mails, indice) = 0;
}

/**********************************************************/

/*
  Retourne l'indice du premier emplacement libre dans 
  le tableau mails contenant nbMax adresses. On suppose que le tableau 
  n'est pas plein.
*/

int indicePremiereChaineVide(char* mails, int indiceMax)
{
  int milieu;
  if (indiceMax == 0)
    return 0;
  milieu = indiceMax / 2;
  if (!*getMail(mails, milieu))
    return indicePremiereChaineVide(mails, milieu);
  else
    return milieu + 1 + 
      indicePremiereChaineVide(getMail(mails, milieu + 1), 
          indiceMax - (milieu + 1));
}

/**********************************************************/

/*
  Trie le tableau mails contenant (indice + 1) elements, 
  ne fonctionne que si tous les autres elements
  sont tries. 
*/

void placeMail(char* mails, int indice)
{
  if (indice > 0 && indice < NB_MAILS &&
      strncmp(getMail(mails, indice),
       getMail(mails, indice - 1), 
       SIZE_MAIL) < 0)
    {
      swapStrings(getMail(mails, indice),
    getMail(mails, indice - 1),
    SIZE_MAIL);
      placeMail(mails, indice - 1);
    }
  else
    if (indice >= 0 && indice < NB_MAILS - 1 &&
 *getMail(mails, indice + 1) &&
 strncmp(getMail(mails, indice),
  getMail(mails, indice + 1), 
  SIZE_MAIL) > 0)
      {
 swapStrings(getMail(mails, indice),
      getMail(mails, indice + 1),
      SIZE_MAIL);
 placeMail(mails, indice + 1);
      }
}

/**********************************************************/

void ajouteMail(char* mails, char* mail)
{
  int indice;
  if (*getMail(mails, NB_MAILS - 1))
    {
      printf("Carnet de contact plein.\n");
    }
  else
    {
      indice = indicePremiereChaineVide(mails, NB_MAILS - 1);      
      strncpy(getMail(mails, indice), mail, SIZE_MAIL);
      *(getMail(mails, indice) + SIZE_MAIL - 1) = 0;
      placeMail(mails, indice);
    }
}

/**********************************************************/

void changeMail(char* mails, char* mail, int indice)
{
  strncpy(getMail(mails, indice), mail, SIZE_MAIL);
  *(getMail(mails, indice) + SIZE_MAIL - 1) = 0;
  placeMail(mails, indice);
}

/**********************************************************/

void sauvegardeMails(char* mails, char* nomFichier)
{
  FILE* f = fopen(nomFichier, "w");
  int i;
  if (f == NULL)
    printf("Impossible d'ouvrir le fichier %s", nomFichier);
  else
    for(i = 0 ; i < NB_MAILS && *getMail(mails, i) ; i++)
      fwrite(getMail(mails, i), sizeof(char), SIZE_MAIL, f);
  fclose(f);
}

/**********************************************************/

void restaureMails(char* mails, char* nomFichier)
{
  FILE* f = fopen(nomFichier, "r");
  int i, ret = 1;
  if (f == NULL)
    printf("Impossible d'ouvrir le fichier %s", nomFichier);
  else
    for(i = 0 ; i < NB_MAILS && ret ; i++)
      ret = fread(getMail(mails, i), sizeof(char), SIZE_MAIL, f);
  fclose(f);  
}

Télécharger le fichier

eMails.c

#include<stdio.h>
#include<stdlib.h>

#include "tableau.h"
#include "util.h"

#define AFFICHER_OPTION 1
#define SUPPRIMER_OPTION 2
#define MODIFIER_OPTION 3
#define AJOUTER_OPTION 4
#define QUITTER_OPTION 5
#define NB_OPTIONS 5

#define F_NAME ".adressesMails.txt"

/**********************************************************/

/*
  Affiche le menu principal.
*/

void afficheMenu()
{
  printf("\nOptions disponibles :\n"
  "%d - afficher les contacts\n"
  "%d - supprimer un contact\n"
  "%d - modifier un contact\n"
  "%d - ajouter un contact\n"
  "%d - quitter\n",
  AFFICHER_OPTION, 
  SUPPRIMER_OPTION,
  MODIFIER_OPTION,
  AJOUTER_OPTION,
  QUITTER_OPTION);
}

/**********************************************************/

/*
  Affiche le menu principal, retourne la valeur saisie par 
  l'utilisateur.
*/

int choisitOptionMenu()
{
  int option;
  do
    {
      afficheMenu();
      printf("Choisissez une option en saisissant son numero : ");
      option = getInt();
      if (option <= 0 && option > NB_OPTIONS)
 printf("option invalide\n");
    }
  while(option <= 0 && option > NB_OPTIONS);
  return option;
}

/**********************************************************/

/*
  Demande a l'utilisateur de saisir un mail, le place 
  a l'adresse adr.
*/

void saisitMail(char* adr)
{
  printf("Veuillez saisir l'adresse e-mail de votre contact : ");
  do
    {
      getString(adr, SIZE_MAIL);
      if (!*adr)
 printf("Vous devez saisir une adresse");
    }
  while(!*adr);
}

/**********************************************************/

/*
  Affiche la liste de mails, saisit et retourne le numero de 
  l'un d'eux. 
*/

int choisitMail(char* mails)
{
  int i, nbMails;
  nbMails = nombreMails(mails);
  afficheMails(mails);
  do
    {
      printf("Choisissez un mail en saisissant son numero : ");
      i = getInt();
      if (i <= 0 && i > nbMails)
 printf("Cet indice n'existe pas ! \n");
    }
  while(i <= 0 && i > nbMails);
  return i - 1;  
}

/**********************************************************/

/*
  Saisit un mail m et un indice i, puis remplace le i-eme mail 
  de mails par m.
*/

void modifierOption(char* mails)
{
  int i;
  char m[SIZE_MAIL];
  printf("Modification d'un contact : \n");
  i = choisitMail(mails);
  saisitMail(m);
  changeMail(mails, m, i);
}

/**********************************************************/

/*
  Saisit un mail m et un indice i, puis remplace le i-eme mail 
  de mails par m.
*/

void ajouterOption(char* mails)
{
  char m[SIZE_MAIL];
  printf("Ajout d'un contact : \n");
  saisitMail(m);
  ajouteMail(mails, m);
}

/**********************************************************/

/*
  Saisit un indice i, puis supprime le i-eme mail dans mails. 
*/

void supprimerOption(char* mails)
{
  int i;
  printf("Suppression d'un contact : \n");
  i = choisitMail(mails);
  supprimeMail(mails, i);
}

/**********************************************************/

/*
  Sauve les mails dans le fichier F_NAME et affiche un message 
  d'adieu.
*/

void quitterOption(char* mails)
{
  sauvegardeMails(mails, F_NAME);
  printf("Au revoir !\n");  
}

/**********************************************************/

/*
  Affiche le menu principal, saisit une option, et effectue le 
  traitement necessaire. 
*/

void executeMenu(char* mails)
{
  int option;
  do
    {
      option = choisitOptionMenu();
      switch(option)
 {
 case AFFICHER_OPTION : 
   afficheMails(mails);
   break;
 case AJOUTER_OPTION : 
   ajouterOption(mails);
   break;
 case MODIFIER_OPTION : 
   modifierOption(mails);
   break;
 case SUPPRIMER_OPTION : 
   supprimerOption(mails);
   break;
 case QUITTER_OPTION : 
   quitterOption(mails);
   break;
 default:
   break;
 }
    }
  while(option != QUITTER_OPTION);
}

/**********************************************************/

int main()
{
  char* mails = creerMails();
  restaureMails(mails, F_NAME);
  executeMenu(mails);
  detruitMails(mails);
  return 0;
}

Télécharger le fichier

makefile

all : eMails eMails.tgz

util.o: util.c util.h
 gcc -Wall -c util.c

tableau.o: tableau.c tableau.h util.h
 gcc -Wall -c tableau.c

eMails.o: eMails.c tableau.h util.h
 gcc -Wall -c eMails.c

eMails: util.o tableau.o eMails.o
 gcc -o eMails -Wall util.o tableau.o eMails.o

eMails.tgz: util.h util.c tableau.h tableau.c eMails.c makefile
 tar cvfz eMails.tgz util.h util.c tableau.h tableau.c eMails.c makefile

Télécharger le fichier