Dès que l’on code un programme d’une certaine envergure, bon nombre de problèmes se posent.
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.
La solution au problème mentionné ci-avant réside dans l’application des règles d’or suivantes :
Avant toute chose, rappelons quelques évidences :
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 :
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.
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.
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 :
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.)
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.
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.
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.
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.
#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
#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; }
#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
#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); }
#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; }
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