
Comme dit le proverbe : Maille à maille se fait l'haubergeon.
Nous allons donc aujourd'hui apprendre à coder quelques outils qui nous seront très utiles pour détecter les mailles douteuses.
Ça peut vous sembler une perte de temps de mettre tout ce que nous allons faire sur pied. Nous pouvons, par expérience, vous détromper : si votre code grossit tant soit peu, si vous débuggez une portion du code que vous n'avez pas écrite (ça arrive quand on travaille à plusieurs), ou a plus forte raison si vous codez comme un porc, vous allez vite avoir du mal à localiser les bugs.
Les outils que nous allons vous présenter vont vous fournir un tas d'informations utiles qui vous aideront beaucoup, si vous savez les coder et vous en servir.
Pour commencer, voyons l'indispensable outil du petit programmeur qui aime savoir ce qui se passe : le logger.
Un logger est un outil qui va logger (consigner) tout ce qu'on lui demandera, soit dans une console, soit dans un fichier, n'importe où ça peut être utile à vrai dire.
Généralement, tout ce qu'on lui demande se rapporte aux étapes importantes du déroulement de l'application : initialisation réussie, échec de chargement d'une ressource, etc etc...
Le logger que nous allons réaliser pourra écrire un message dans n'importe quel fichier, avec en prime l'heure et la date à chaque utilisation. :)
En effet, il peut être utile de savoir à quel moment exactement se passent les choses dans son programme.
Nous allons encapsuler le logger dans une classe, dont l'interface sera relativement courte puisqu'il n'effectuera pas beaucoup de tâches différentes :
Pour ce faire, le constructeur prendra une std::string en paramètre pour indiquer le fichier où il faudra logger. Son rôle sera d'ouvrir un flux vers le fichier et de noter un message indiquant que le logger à bien été initialisé.
Le destructeur quand à lui notera que le logger à bien été fermé et fermera effectivement le flux automatiquement lors de la destruction de l'instance.
Vous l'aurez deviné, il faudra un attribut de type std::ofstream pour stocker le flux correspondant au fichier.
Ensuite il nous faut évidement la fonction permettant de logger un message. Celle ci prendra en paramètre une std::string contenant ledit message, et le recopiera dans le flux ofstream.
Si vous avez envie, vous pouvez également surcharger l'opérateur <<, de manière à pouvoir logger vos messages comme vous le feriez dans un flux. La méthode operator<< devrait alors appeler la fonction de loggage.
void CLogger::logToFile(const std::string& message)
{
//On ecrit automatiquement l'heure avant chaque message.
m_file << getCurrentTime() << " : " << message << std::endl << std::endl;
}
const std::string& CLogger::operator<(const std::string& message)
{
logToFile(message);
return message;
}
Nous devons maintenant nous occuper de la date et de l'heure.
Dans le message du constructeur disant que le logger a bien été initialisé, nous indiquerons la date et l'heure de lancement, de même, comme vous pouvez le voir juste au dessus nous indiquerons l'heure à laquelle chaque message à été loggé.
La fonction se chargeant de renvoyer une std::string contenant la date et l'heure est la suivante :
//On recupere le timestamp. time_t buffTime = time(NULL); //Et on retourne la traduction en chaîne de caractères. return ctime(&buffTime);
Elle utilise la bibliothèque standard du C, qui contient tout ce qu'il faut pour travailler avec le temps. Comme la bibliothèque standard du C est incluse dans celle du C++, cela ne devrait pas poser de problèmes. Mais n'oubliez pas d'inclure <ctime>.
La dernière méthode renverra donc une std::string contenant l'heure à laquelle elle est appelée :
//La fonction ne fonctionne qu'avec des chars... char s_time[10]; time_t buffTime = time(NULL); strftime(s_time, sizeof(s_time), "%H:%M:%S", localtime(&buffTime)); //On transfert dans une string et on renvoie. std::string sTime(s_time); return sTime;
Pour plus de précision sur ces fonctions, reportez vous à la page du manuel correspondante (man time). Un livre traitant de C vous informera plus précisément sur ce module CTime (time.h en C).
Vous pouvez retrouver l'interface de cette classe dans le code du KGG ici : URL
Et l'implémentation dans le fichier correspondant : URL
Un autre outil très important pour vérifier le bon déroulement de l'application sont les exceptions.
En effet, il peut arriver que pour une raison ou pour une autre, votre code fasse quelque chose que vous n'attendiez pas.
Dans ce cas, il y a deux points très importants : il faut d'abord être à même d'obtenir un maximum d'informations sur ce comportement habituel, et il faut aussi pouvoir le gérer de manière "propre", réparer les dégâts.
A chaque point sensible du programme nous mettrons donc un bloc try qui permettra de lever une exception en cas de problème.
Suivra immédiatement un bloc catch pour attraper les éventuelles exceptions lancées, logger les informations nécessaires, et endiguer le problème (les 3/4 du temps détruire une instance, mais c'est à vous de voir).
Pour pouvoir gérer les exceptions, nous allons créer une classe spéciale qui va implémenter une exception.
Cette classe dérivera de std::exception, de la bibliothèque standard du C++. La seule différence est que CException aura une std::string en attribut qui va contenir le message d'erreur associé à l'exception, que l'on pourra éventuellement logger, par exemple.
Ainsi, le bloc catch n'attrapera que des exceptions de type CException, loggera le message d'erreur, puis éventuellement agira en fonction.
Si à un moment ou à un autre, il se trouve que quelque chose ne va pas, il suffira de créer un objet de type CException puis de le lancer avec le mot-clé throw. Le bloc catch l'attrapera et traitera comme il faut.
// Portion critique de code
try
{
// ...
if(monImage == NULL) //Ici le chargement d'une image a echoue
{
CException erreur("Impossible de charger l'image pinguin.png");
throw erreur;
}
// ...
}
catch (CException exception)
{
std::cerr << erreur.what() < std::endl;
delete this;
}
Vous pouvez retrouver le code source de CException du KGG ici : URL.
Nous avons maintenant tout ce qu'il nous faut pour partir à l'aventure. :)
Bien sûr, il existe de nombreux autres outils de débugging comme ceux-ci. Nous en parleront peut être à l'avenir, mais vous pouvez déjà vous documenter vous mêmes.
Voici quelques pistes : les pointeurs intelligents (qui libèrent la mémoire tous seuls quand c'est nécessaire), la surcharge de new et de delete anti-fuite de mémoire,....
Et beaucoup d'autres encore. Mort aux bugs !