IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

Methode de debugage : le livre "C++ trucs et astuces pour les nuls"


Sujet :

C++

  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Février 2009
    Messages
    86
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Février 2009
    Messages : 86
    Par défaut Methode de debugage : le livre "C++ trucs et astuces pour les nuls"
    Bonjour,

    J'ai lu le chapitre sur les méthodes de debugages expliquees dans le livre "C++ trucs et astuces pour les nuls"...mais a moins que ce soit moi qui me trompe cela m'a l'air d'embrouiller plus qu'autre chose.

    Qu'en pensez vous? Notamment des methodes de tracages de flux....

    Comment au quotidien procedez vous pour le debugage (sous VC+6).

    D'avance merci pour votre aide.

    Sphere

  2. #2
    Rédacteur

    Avatar de Davidbrcz
    Homme Profil pro
    Ing Supaéro - Doctorant ONERA
    Inscrit en
    Juin 2006
    Messages
    2 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : Suisse

    Informations professionnelles :
    Activité : Ing Supaéro - Doctorant ONERA

    Informations forums :
    Inscription : Juin 2006
    Messages : 2 307
    Par défaut
    Tout le monde n'a pas accès à ce livre.
    Peux tu détailler les méthodes.

    Merci.
    "Never use brute force in fighting an exponential." (Andrei Alexandrescu)

    Mes articles dont Conseils divers sur le C++
    Une très bonne doc sur le C++ (en) Why linux is better (fr)

  3. #3
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    27 136
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2008
    Messages : 27 136
    Billets dans le blog
    150
    Par défaut
    Bonjour,

    Je dirai ma méthode change selon le bug. Sous Visual Studio , j'ai tendance à vérifier les valeurs de mes variables, pour être bien sur qu'elle contiennent ce que j'attends.
    ( Je n'ai pas accès au livre non plus :p )
    Il faut savoir qu'un débuggage commence par un bug, les genres dépendent ( erreur de segmentation, fuite de mémoire , ou comportement imprévu ( "programme ne faisant pas ce qu'il doit" ) )
    Pour les bugs de comportement :
    Lorsque je code, et que j'aperçois le bug, je regarde bien ce qu'il m'affiche ( dépend si c'est du graphique , du texte ... ) et je refléchit, comment ce cas arrive, après je vais placer mes breakpoints pour examiner ce cas

    Les erreurs de segmentation, c'est simple, Visual coupera le programme avant son crash, on remonte la pile d'execution pour voir son propre code, et regarde qu'elle valeur pause problème et hop un breakpoint quelque ligne d'avant pour pouvoir retracer le problème en ayant toute les données.

    Au sinon, un papier et un crayon c'est très bien
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  4. #4
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    ( Je n'ai pas accès au livre non plus :p )

    Je confesse être un fanatique du debugger :
    -> Pour voir l'état de ma/mes variables,
    -> Pour voir le retour des fonctions de bibliothèque (boîte noire) que j'appelle (parfois il y a une distance entre la doc et le comportement observé),
    -> Pour comprendre du code complexe plus rapidement qu'en l'analysant de manière statique,
    -> Avec visual, pour voir comment mes templates sont résolus,
    -> Et bien d'autre cas qui sont tellement évident qu'ils ne me viennent pas à l'esprit.

    J'ai cette démarche quand j'ai un bug ... mais aussi quand j'en ai pas. L'analyse avec une feuille et un crayon doit être la première étape, mais ensuite, comme pour tout théorie, il faut confirmer les prévisions aux observations

    Enfin, le debugger n'est pas le seul outil. Les traces sont aussi des moyens assez puissant pour analyser du code, et en particulier pour en avoir une vue dynamique.

  5. #5
    Membre Expert

    Profil pro
    Inscrit en
    Juin 2006
    Messages
    1 294
    Détails du profil
    Informations personnelles :
    Localisation : Royaume-Uni

    Informations forums :
    Inscription : Juin 2006
    Messages : 1 294
    Par défaut
    Salut,

    Depuis que je fais des tests unitaires je ne débogue quasiment plus (un peu de temps en temps dans les cas où "ça plante" mais c'est très rare).
    C'est bien je trouve comme méthode, on gagne beaucoup de temps et on peut se concentrer sur ce qui est utile.

    MAT.

  6. #6
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par Mat007 Voir le message
    Salut,

    Depuis que je fais des tests unitaires je ne débogue quasiment plus (un peu de temps en temps dans les cas où "ça plante" mais c'est très rare).
    Les tests U (comme tous les tests) sont aussi une indispensable pratique pour produire du code fiable. Cela ne m'empêche pas de continuer à utiliser le debugger. J'utilise les testU 'seuls' généralement pour la non régression. Pour les premières validations, je regarde quand même si tout se passe comme j'ai cru l'écrire.

  7. #7
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,
    Citation Envoyé par 3DArchi Voir le message
    Les tests U (comme tous les tests) sont aussi une indispensable pratique pour produire du code fiable. Cela ne m'empêche pas de continuer à utiliser le debugger. J'utilise les testU 'seuls' généralement pour la non régression. Pour les premières validations, je regarde quand même si tout se passe comme j'ai cru l'écrire.
    A vrai dire, je ne suis pas particulièrement d'accord avec ton point de vue (même s'il se défend sans trop de problème )...

    Le fait est qu'il est beaucoup plus facile de prévoir directement pour chaque comportement à implémenter quels tests effectuer avant même d'envisager d'implémenter le comportement en question, et d'effectuer des tests unitaires sur de petites portions de code (une ou deux fonctions/méthodes) à la fois que sur l'ensemble d'un code reprenant parfois jusqu'à des dizaines (voire des centaines) de fonctions/classes/méthodes, ce qui représente un code tout aussi étendu...

    De la même manière, lorsque l'on prend l'habitude de créer et de tester "manuellement" son algorithme avant même d'écrire la première lettre du code correspondant, si on choisi la "bonne méthode" algorithmique (je pense entre autres - bien que d'aucuns aient des reproches à faire à la méthode - au nassi-schneiderman), il est possible d'en arriver à ce que la première compilation réussie (une fois les inévitables fautes d'attention corrigées) fournissent le résultat recherché...

    Ces deux méthodes combinées sont - pour autant qu'elles soient correctement appliquées - quasiment en mesure de t'éviter tout recours à une quelconque méthode de débuggage, et à te faire gagner énormément de temps (n'oublions pas que la plus grande part du temps perdu l'est en période de débuggage )

    Je vous présente mes excuses pour ce post presque HS
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #8
    Membre confirmé
    Profil pro
    Inscrit en
    Février 2009
    Messages
    86
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Février 2009
    Messages : 86
    Par défaut
    Technique 62 : Inclusion du traçage dans les applications


    Implémentation de la classe de traçage de flux
    Test du système de traçage de flux


    code ch62.exe :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    #ifndef _FLOWTRACER_H_
    #define _FLOWTRACER_H_
     
    #include <string>
    #include <stack>
    #include <vector>
     
    /*
    L'objet de tracage de flux (debugFlowTrace) stocke un nom dont on se 
    servira pour definir des points d'entree specifiques dans le systeme.
    Elle contient egalement une liste de sous-flux, qui sont simplement
    les fonctions ou le flux passe apres avoir commence.Ex : Si vous commencez
    a la fonction un, appelez la fonction deux, puis la quatre, et que la 
    fonction deux appelle la fonction trois, vous aurez le tracage :
    	Fonction Un
    		Fonction Deux
    		Fonction Trois
    		Fonction Quatre
    */
     
    class debugFlowTracer //Implementn de la def de la classe tracage de flux.
    {
    private:
    	std::string m_sFlowName;
    	std::vector< debugFlowTracer > m_activefunctionStack;
    	bool						   m_bRemoved;
    protected:
    	virtual void AddFlow();
    	virtual void RemoveFlow();
     
    public:
    	debugFlowTracer(void)
    	{
    		m_sFlowName = "Inconnu";
    		m_bRemoved = false;
    		AddFlow();
    	}
     
    	debugFlowTracer(const char *strFlow)
    	{
    		m_sFlowName = strFlow;
    		m_bRemoved = false;
    		AddFlow();
    	}
    	debugFlowTracer( const debugFlowTracer& aCopy )
    	{
    		m_sFlowName = aCopy.m_sFlowName;
    		std::vector< debugFlowTracer >::const_iterator iter;
    		for ( iter = aCopy.m_activefunctionStack.begin(); iter != aCopy.m_activefunctionStack.end(); ++iter )
    			m_activefunctionStack.insert( m_activefunctionStack.end(), (*iter) );
    	}
     
     
    	~debugFlowTracer(void)
    	{
    		if ( !m_bRemoved )
    			RemoveFlow();
    		m_bRemoved = true;
    	}
     
    	std::string Name()
    	{
    		return m_sFlowName;
    	}
     
    	void AddSubFlow( debugFlowTracer& cSubFlow )
    	{
    		// Placement en haut de la pile de fonction active
    		m_activefunctionStack.insert( m_activefunctionStack.end(), cSubFlow );
    	}
    	void PrintStack(int iLevel)
    	{
    		std::vector< debugFlowTracer >::iterator iter;
    		for ( iter = m_activefunctionStack.begin(); iter != m_activefunctionStack.end(); ++iter )
    		{
    			for ( int i=0; i<iLevel; ++i )
    				putchar ('\t');
    			printf("%s\n", (*iter).Name().c_str() );
    			(*iter).PrintStack(iLevel+1);
    		}
    	}
    };
     
    #endif
     
    ///////////////////////////////////////////////////////////////////////
    // IMPLEMENTATION DE LA CLASSE ET DU GESTIONNAIRE DE TRACAGE DE FLUX //
    ///////////////////////////////////////////////////////////////////////
     
    class debugFlowTracerManager
    {
    /* Le role de la classe debugFlowTracerManager est de tracer (prendre note)
    les divers flux au sein du systeme. Un flux pouvant debuter et se terminer
    n'importe ou dans le code source de l'application, il faut un unique 
    emplacement pour tous les stocker.Cela permettra de les afficher au stade
    qui donne la meilleure vue du deroulement du processus.
     
    Noter que tous les constructeurs sont prives, de facon que l'utilisateur 
    ne puisse creer d'instance de la classe. Cela garantit qu'il n'existera 
    qu'un seul objet de cette classe dans toute l'application.
    */
    private: 
    	std::stack< debugFlowTracer> m_functionStack;
    	static debugFlowTracerManager *m_Instance;
     
    public:
    	static debugFlowTracerManager *Instance()
    	{
    		if ( m_Instance == NULL )
    			m_Instance = new debugFlowTracerManager();
    		return m_Instance;
    	}
    	void addFlow( debugFlowTracer& cFlow )
    	{
    		m_functionStack.push( cFlow );
    	}
    	void removeFlow(debugFlowTracer& cFlow)
    	{
    		if ( m_functionStack.empty() )
    			return;
     
    		// Accède à l'élément du haut
    		debugFlowTracer t = m_functionStack.top();
     
    		// Le supprime.
    		m_functionStack.pop();
     
    		// S'il reste quelque chose, on l'ajoute
    		if ( m_functionStack.empty() )
    		{
    			printf("Flot [%s]:\n", t.Name().c_str() );
    			t.PrintStack(0);
    		}
    		else
    			m_functionStack.top().AddSubFlow( t );
     
    	}
     
    private:
    	debugFlowTracerManager()
    	{
    	}
    	debugFlowTracerManager(const debugFlowTracerManager& aCopy )
    	{
    	}
    	virtual ~debugFlowTracerManager(void)
    	{
    	}
    };
     
    debugFlowTracerManager *debugFlowTracerManager::m_Instance = NULL;
     
    void debugFlowTracer::AddFlow()
    {
    	debugFlowTracerManager::Instance()->addFlow( *this );
    	//L'objet debugFlowTracer s'attache a l'instance du gestionnaire.
     
    }
     
    void debugFlowTracer::RemoveFlow()
    {
    	debugFlowTracerManager::Instance()->removeFlow( *this );
    }
     
    //////////////////////////////////////////////////////////////////
    //				TEST DU SYSTEME DE TRACAGE DE FLUX				//
    //////////////////////////////////////////////////////////////////
     
    void func_3()
    {
       debugFlowTracer flow("func_3");
    }
     
    void func_2()
    {
       debugFlowTracer flow("func_2");
       func_3();
    }
     
    void func_1()
    {
       debugFlowTracer flow("func_1");
       func_2();
    }
     
    int main(int argc, char* argv[])
    {
       debugFlowTracer mainFlow("main");
       func_1();
       func_2();
       func_3();
       return 0;
    }


    Ajout du traçage après coup

    S'exécute en faisant ch62_4.exe ch62_5.cpp dont les codes sont ci-dessous :

    code ch62_4.cpp :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
     
    //////////////////////////////////////////////////////////////////////
    //PROGRAMME POUR INSERER DU TRACAGE DANS UN FICHIER DE CODE EXISTANT//
    //////////////////////////////////////////////////////////////////////
     
    #include <string>
    #include <ctype.h>
     
     
    using namespace std;
    /*
    Le code prend un fichier d'entree et l'ecrit dans un fichier de 
    sortie temporaire, en y ajoutant des informations au fur et a mesure 
    de l'analyse du code du fichier d'entree. Dans notre cas, le code 
    recherche les accolades ouvrantes qui indiquent le debut d'une fonction.
    Lorsque l'une d'elles est rencontree, le nom de la fonction est analyse
    et la definition d'un objet debugFlowTracer est ecrite dans le fichier de
    sortie. Cela cree un systeme automatise pour l'insertion de tracage de 
    flux dans un fichier source existant.
    */
     
     
    void eat_line( FILE *fp, string& real_line )   
     
    {
    	// Lit jusqu'à la fin de la ligne
    	while ( !feof(fp) )
    	{
    		char c = fgetc(fp);
    		real_line += c;
    		if ( c == '\n' )
    			break;
    	}
    }
     
    void eat_comment_block( FILE *fp, string& real_line )
    {
    	char sLastChar = 0;
     
    	// Trouve la marque de fermeture de commentaire
    	while ( !feof(fp) )
    	{
    		char c = fgetc(fp);
    		real_line += c;
    		if ( c == '/' && sLastChar == '*' )
    			break;
    		sLastChar = c;
    	}
    }
     
     
    string get_line( FILE *fp, string& real_line )
    {
    	string sLine = "";
    	char sLastChar = 0;
    	while ( !feof(fp) )
    	{
    		// Lit un caractère d'entrée
    		char c = fgetc(fp);
     
    		real_line += c;
     
    		// Recherche de lignes de pré-processeur
    		if ( c == '#' && (sLastChar == 0 || sLastChar == '\n') )
    		{
    			eat_line( fp, real_line );
    			continue;
    		}
     
    		// Recherche de commentaires
    		if ( c == '/'  )
    		{
    			sLastChar = c;
    			c = fgetc(fp);
    			real_line += c;
     
     
    			if ( c == '/' )
    			{
    				eat_line( fp, real_line );
    				sLastChar = 0;
    				continue;
    			}
    			else
    				if ( c == '*' )
    				{
    					eat_comment_block( fp, real_line );
    					sLastChar = 0;
    					continue;
    				}
    				else
    				{
    					sLine += sLastChar;
    				}
    		}
     
    		// Il faut ignorer le contenu entre guillemets.
     
    		if ( c != '\r' && c != '\n' )
    		{
    			// Ici, cela devient un peu tordu. Si le dernier caractère
    			// est une parenthèse, il ne faut pas autoriser l'espace
    			if ( sLastChar != ')' || !isspace(c) )
    				sLine += c;
    			else
    				continue;
    		}
     
    		// Une ligne se termine par { ou } ou ; 
    		if ( c == ';' || c == '{' || c == '}' )
    			break;
     
     
    		sLastChar = c;
    	}
     
    	return sLine;
    }
     
    string parse_function_name( string& sLine )
    {
    	string sName = "";
    	int i; //PKS
     
    	// Recherche de la parenthèse ouvrante
    	int sPos = (int)sLine.find('(');
     
    	// Ignore les espaces préalables
    	//for ( int i=sPos-1; i>=0; --i )  //PKS
    	for (i=sPos-1; i>=0; --i )
    		if ( !isspace(sLine[i]) )
    		{
    			sPos = i;
    			break;
    		}
     
    	// Dès lors, tout ce qui est avant cela est le nom,
    	// jusqu'à atteindre le début de la ligne ou un espace
    	int sStartPos = 0;
    	//for ( int i=sPos; i>=0; --i )  //PKS
    	for (i=sPos; i>=0; --i )
    	{
    		if ( isspace(sLine[i]) )
    			break;
    		sStartPos = i;
    	}
     
    	sName = sLine.substr( sStartPos, sPos-sStartPos+1 );
     
    	return sName;
    }
     
     
    void ProcessFile( FILE *fp, FILE *ofp )
    {
    	string real_line;
     
    	while ( !feof(fp) )
    	{
    		real_line = "";
     
    		string sLine = get_line( fp, real_line );
     
    		// Recherche de fonction/méthodes
     
    		// Affiche la "véritable" ligne puis, si nécessaire, 
    		// les informations devant être incorporées.
    		fprintf(ofp, "%s", real_line.c_str() );
    		if ( sLine[sLine.length()-1] == '{' &&
    			 sLine[sLine.length()-2] == ')' )
    		{
    			string sName = parse_function_name( sLine );
    			fprintf(ofp, "\n\tdebugFlowTracer flow(\"%s\");\n", sName.c_str());
    		}
     
    	}
    }
     
    int main(int argc, char* argv[])
    {
    	if ( argc < 2 )
    	{
    		printf("Usage: insertflow.exe nom-fichier [nom-fichier...]\n");
    		exit(1);
    	}
    	for ( int i=1; i<argc; ++i )
    	{
    		FILE *fp = fopen(argv[i], "r");
    		if ( fp == NULL )
    		{
    			printf("Erreur: Impossible de traiter le fichier %s\n", argv[i] );
    			continue;
    		}
     
    		string sOut = string(argv[i]) + ".tmp";
    		FILE *ofp = fopen(sOut.c_str(), "w");
    		if ( ofp == NULL )
    		{
    			printf("Erreur: Impossible de créer le fichier de sortie %s\n", sOut.c_str() );
    			continue;
    		}
     
     
    		// Traite le fichier
    		ProcessFile( fp, ofp );
     
    		// Termine
    		fclose(fp);
    		fclose(ofp);
    	}
     
    	return 0;
    }
    code ch62_5.cpp :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #include <stdio.h>
    #include <string>
    #include <iostream>
     
    int func1()
    {
       printf("Ceci est un test\n");
    }
     
    void func2()
    {
       printf("Et voici un autre test\n");
    }
     
    class Foo
    {
    public:
       Foo();
       virtual ~Foo();
    };
     
    Foo:: Foo(void)
    {
    }
     
    Foo::~ Foo(void)
    {
    }
     
    int main()
    {
       Foo x;
    }


    Technique 63 : Macros et classes de débogage


    La macro assert
    Journalisation
    Test de la classe de journalisation Logger
    code ch63a.cpp (Une classe de journalisation):

    S'execute en faisant : ch63a.exe -log

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
     
    #include <iostream>
    #include <string>
    #include <stdlib.h>
    #include <stdarg.h>
    #include <fstream>
     
    using namespace std;
     
    class Logger
    {
       bool   _bOn;
       bool   _bForceFlush;
       string   _sMessage;
       string   _sFileName;
       ofstream   _file;
    public:
       Logger(void )
       {
          _bOn = false;
          _bForceFlush = false;
       }
       Logger(const char *strFileName )	// -> 2
       {
          _sFileName = strFileName;
          _bOn = false;
          _bForceFlush = false;
       }
       Logger(const Logger& aCopy )
       {
          _sFileName = aCopy._sFileName;
          _bForceFlush = aCopy._bForceFlush;
          setOn(aCopy._bOn );
       }
       virtual ~Logger()
       {
          Flush();
          if ( _bOn )
             _file. close();
       }
     
       void setOn(bool flag )
       {
          _bOn = flag;
          if ( _bOn )
          {
             _file. open( _sFileName. c_str() );
          }
       }
       bool getOn(void )
       {
          return _bOn;
       }
       void setForceFlush(bool flag )
       {
          _bForceFlush = flag;
       }
       bool getForceFlush(void )
       {
          return _bForceFlush;
       }
       void setFileName (const char *strFileName )
       {
          _sFileName = strFileName;
       }
       string getFileName (void )
       {
          return _sFileName;
       }
     
       void Log(const char *strMessage )	//-> 3
       {
          _sMessage += strMessage;
          _sMessage += '\n';
          if ( _bForceFlush )
             Flush();
       }
       void LogString(const char *fmt, ... )	//-> 4
       {
          char szBuffer[256];
          va_list marker;
     
          va_start(marker, fmt );     /* Initialise les arguments de variable */
     
          vsprintf(szBuffer, fmt, marker );
     
          _sMessage += szBuffer;
          _sMessage += '\n';
          if ( _bForceFlush )
             Flush();
       }
       void Flush(void )
       {
          if ( _bOn )
             _file << _sMessage << endl;
          _sMessage = "";
       }
    };
     
    int main(int argc, char **argv) //Programme de test de la classe Logger
    {
       Logger log("log.txt");
     
       // Obtenir que le journal écrive des informations
       // au fur et à mesure qu'il rencontre des chaînes,
       // pour éviter le vidage et la perte du journal
       // en cas de crash
       log. setForceFlush(true );
       int i;
       // Voir si la commande contient une option demandant à écrire le journal.
       for (i=0; i<argc; ++i )  //PKS
       {
          if ( ! strcmp(argv[i], "-log") )
          {
             log. setOn(true);
             break;
          }
       }
     
       log. Log("Arguments de d\202marrage du programme");
       for (i=0; i<argc; ++i )   //PKS
       {
          log. LogString("Argument entr\202 %d = %s", i, argv[i] );
     
       // Invite à entrer une chaîne, la modifie, puis l'affiche.
       while (1 )
       {
          printf("Entrer une commande: ");
     
          char szBuffer[80 ];
          memset(szBuffer, 0, 80 );
          if (gets(szBuffer) == NULL )
             break;
     
          log. LogString("Cha\214ne entr\202e: %s", szBuffer );
          if ( ! strlen(szBuffer) )
          {
             break;
          }
     
          string s = "";
          for (int i=strlen(szBuffer)- 1; i>=0; --i )
             s += szBuffer[i];
     
          log. LogString("Cha\214ne de sortie: %s", s. c_str() );
     
       }
     
       log. Log("Fin de l'application\n");
       return 0;
    }
    }
    Programmation par contrat (Design by Contract)
    code ch63.cpp :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
     
    #include <iostream>
    #include <stdlib.h>
    #include <string.h>
     
    void abort_program(const char *file, long line , const char *expression)
    {
       printf("Fichier: %s Ligne: %ld Echec de l'expression: %s\n", file, line, expression);
       exit(1);
    }
     
    class DBCObject
    {
       long   _magicNo;
    public:
       DBCObject(long magic )
       {
          _magicNo = magic;
       }
    #ifdef _DEBUG
        virtual bool IsValid()const = 0;
    #endif
        long Magic(void) const
       {
          return _magicNo;
       }
       void setMagicNo(long magic )
       {
          _magicNo = magic;
       }
    };
     
    #ifdef _DEBUG
    #define DBC_ASSERT(bool_expression) if (!(bool_expression)) abort_program(__FILE__, __LINE__, #bool_expression)
    #define IS_VALID(obj) DBC_ASSERT((obj) != NULL && (obj)-> IsValid())
    #define REQUIRE(bool_expression) DBC_ASSERT(bool_expression)
    #define ENSURE(bool_expression) DBC_ASSERT(bool_expression)
     
    #else
     
    // Lorsque votre code est compilé en mode final (release),
    // l'indicateur _DEBUG ne doit pas être défini, pour éviter
    // la surcharge due à ces contrôles.
     
    #define DBC_ASSERT(ignore) ((void) 1)
    #define IS_VALID(ignore) ((void) 1) 
    #define REQUIRE(ignore) ((void) 1)
    #define ENSURE(ignore) ((void) 1)
     
    #endif
     
    class MyClass : public DBCObject
    {
    private:
       char *_buffer;
       int   _bufLen;
    protected:
       void Init()
       {
          _buffer=NULL;
          _bufLen = 0;
       }
     
    #ifdef _DEBUG
        bool IsValid()const
       {
          // Condition: Tampon non null.
          if (getBuffer() == NULL )
             return false;
          // Condition: Longueur > 0.
          if (getLength() <= 0 )
             return false;
          // Condition: nombre "magique" correct.
          if (Magic() != 123456 )
             return false;
     
          // Toutes les conditions sont vérifiées, on continue.
          return true;
       }
    #endif
     
    public:
       MyClass(void)
          : DBCObject(123456 )
       {
          Init();
       }
       MyClass(const char *strIn )
          : DBCObject(123456 )
       {
          // Précondition: strIn non NULL.
          REQUIRE(strIn != NULL );
     
          Init();
          setBuffer(strIn );
    	  setLength(strlen(strIn));  //PKS
     
          // Post-condition: tampon non NULL.
          ENSURE(getBuffer() != NULL );
          // Post-condition: longueur du tampon non nulle.
          ENSURE(getLength() != 0 );
       }
       MyClass(const MyClass& aCopy )
          : DBCObject(123456 )
       {
          // Précondition: aCopy est valide.
          IS_VALID(&aCopy);
          // Précondition: aCopy._buffer non NULL.
          REQUIRE(aCopy. getBuffer() != NULL );
          // Précondition: aCopy._bufLen différent de 0.
          REQUIRE(aCopy. getLength() != 0 );
     
          // Assemble les éléments.
          setBuffer(aCopy._buffer );
          setLength(aCopy._bufLen );
     
          // Post-condition: tampon non NULL.
          ENSURE(getBuffer() != NULL );
          // Post-condition: longueur du tampon différente de 0.
          ENSURE(getLength() != 0 );
       }
       MyClass operator=( const MyClass& aCopy )
       {
          // Précondition: aCopy est valida.
          IS_VALID(&aCopy);
          // Précondition: aCopy._buffer non NULL.
          REQUIRE(aCopy. getBuffer() != NULL );
          // Précondition: aCopy._bufLen différent de 0.
          REQUIRE(aCopy. getLength() != 0 );
     
          // Assemble les éléments.
          setBuffer(aCopy._buffer );
          setLength(aCopy._bufLen );
     
          // Post-condition: tampon non NULL.
          ENSURE(getBuffer() != NULL );
          // Post-condition: longueur de tapon différente de 0.
          ENSURE(getLength() != 0 );
     
          // Retourne l'objet en cours.
          return *this;
       }
     
       virtual ~MyClass()
       {
          // Précondition: le nombre magique doit être correct.
     
     
          REQUIRE(Magic() == 123456 );	//-> 5
          // Pré-condition: longueur (length) >= 0.
          REQUIRE(getLength() >= 0 );
          // Pré-condition: Si length >=0, alors le tampon ne peut pas être NULL.
          if (getLength() )
             REQUIRE (getBuffer() != NULL );
     
          // Tout est correct; effacement du tampon.
             if (_buffer != NULL )
                delete[] _buffer;
          _buffer = NULL;
          // Effacement de la longueur.
          _bufLen = 0;
     
          // Post-condition: Le nombre magique est toujours correct.
          ENSURE(Magic() == 123456 );
          // Post-condition: Tampon NULL.
          ENSURE(getBuffer() == NULL );
          // Post-condition: Longueur (Length) 0.
          ENSURE(getLength() == 0 );	//-> 6
     
     
     
     
       }
     
       void setBuffer(const char *strIn )
       {
          // Précondition: strIn non NULL.
          REQUIRE(strIn != NULL );
     
          if (strIn != NULL )
          {
             _buffer = new char[strlen(strIn) + 1 ];
             strcpy ( _buffer, strIn );
          }
       }
       void setLength (int length )
       {
          // Pré-condition: longueur (Length) > 0.
          REQUIRE (length > 0 );
     
          _bufLen = length;
       }
     
       int getLength(void ) const
       {
          // Pas de condition.
          return _bufLen;
       }
       const char *getBuffer(void ) const
       {
          // Pas de condition.
          return _buffer;
       }
    };
     
    int main(int argc, char **argv )
    {
        // Conditions du programme
        REQUIRE (argc > 1 );
        REQUIRE (argv[1] != NULL );
     
        // Objet vide.
        MyClass mc1;
     
        // Objet défini par la ligne de commande.
        MyClass mc2(argv[1] );
        // Copie de cet objet.
        MyClass mc3 = mc2;
    }
    A bientôt,

    Sphere

  9. #9
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    27 136
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2008
    Messages : 27 136
    Billets dans le blog
    150
    Par défaut
    Ah oui le assert() c'est un bon truc pour faire du code fiable, mais je n'ai toujours pas le reflexe de l'utiliser... :s pourtant je sais comment l'écrire et comment il marche , mais j'y pense pas.

    Est ce que quelqu'un peu me donner la doc pour savoir ce qu'est un texte unitaire, et aussi la méthode "nassi-schneiderman" ( même si c'est en anglais ) c'est juste que je sais pas de quoi vous parler.
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  10. #10
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Un test unitaire est un test que tu fais sur... une unité aussi petite que possible.

    Pour info, l'unité la plus petite que tu puisse trouver en C++, c'est la fonction ou la classe

    L'idée générale, c'est que tu devrait commencer, pour chaque fonction, par réfléchir aux différentes conditions d'exécution de cette fonction:

    Elle peut, par exemple, lancer une exception si le deuxième paramètre est égal à 0, parce qu'il serait utilisé comme diviseur dans une division, ou renvoyer un code d'erreur si un paramètre (sur lequel tu dois calculer la racine carrée) est négatif...

    Bref, tu va réfléchir aux différents comportements qui pourraient être induis, lors de l'appel d'une fonction, du fait des différents arguments utilisés par cette fonction.

    Le test unitaire va te permettre de t'assurer que le comportement de ta fonction correspond pleinement à ce que tu attend d'elle:
    • si tu fournis un argument "correct" (dans les limites admises par la fonction), donne-t-elle un résultat correct
    • Lance-t-elle l'exception attendue si l'argument fourni ne correspond pas aux conditions attendues
    • ...

    Le plus souvent, tu fera appel à une des bibliothèques prévues à cet effet: cela peut aller de boost::test à cppunit et d'autres

    Quant à ce qu'il en est du nassi-schneiderman (j'hésite toujours sur l'orthographe ), il s'agit d'une méthode qui permet de représenter la logique que doit suivre une fonction ("l'algorithme" de la fonction).

    Tu connais surement (ou tu auras surement au moins entendu parler de) ce que l'on appelle le "pseudo code", pour lequel, bien que je conçoive qu'il permet de transmettre l'idée de l'algorithme sur un forum, mon avis (strictement personnel) est que, quitte à écrire un "faux code" (après tout, pseudo est un synonyme de "faux" ), autant en écrire directement du vrai , ou le flowchart, qui, bien qu'il soit particulièrement visuel présente l'énorme inconvénient de ne pas être adapté du tout aux langages dits "de troisième génération".

    Le nassi-schneiderman est un peu moins visuel que le flowchart, mais il permet de représenter facilement toutes les structures de contrôles (tests "vrai/faux", tests à choix multiples, boucles, ...)

    Toutes ces techniques ont pour ambition de te faire réfléchir, avant d'écrire la première ligne de code d'une fonction, à la logique qu'elle doit suivre pour fournir le résultat attendu, en te permettant d'en structurer les différentes étapes.

    Une fois l'algorithme mis au point, il devient facile, en n'utilisant qu'un bic et un papier, de t'assurer que le comportement reste logique par rapport à ce que tu attends de la fonction que tu as mis au point

    Normalement, une fois que tu as un algorithme correct et surtout facile à "traduire en code", tu as, pour ainsi dire, la certitude que ta fonction fera ce que tu attend d'elle dés la première compilation réussie (c'est à dire, une fois que tu as corrigé les différentes fautes d'attention)
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  11. #11
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Si je rajoute cette réponse, plutôt que d'éditer la précédente, c'est pour revenir, avec des exemples, sur l'utilité des tests unitaires...

    D'abord, sur une fonction, toute simple, dont tu as déterminé que le prototype prend la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    doublefoo(int dividende, int diviseur)
    et dont tu sais qu'à un moment elle contiendra un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    double resultat = (double) dividende / (double) diviseur;
    /* et, en sortie de fonction */
    return resultat;
    Si diviseur vaut 0, ta fonction va provoquer l'émission d'une erreur système "division par 0".

    Tu peux t'en prémunir, dans une certaine mesure, par un simple assert, sous la forme de
    Le problème, c'est que l'assert ne fonctionne que pendant la période de tests (en mode "debug")

    Cela implique que ton application risque, si les informations sont introduites manuellement par l'utilisateur (dont tout le monde sait qu'il est un imb... distrait) de ne pas passer le "test du singe" une fois l'application compilée en mode "release".

    De plus, l'assert a pour résultat... de terminer l'application.

    Il semble donc opportun de se donner la possibilité de vérifier à chaque fois l'argument nommé diviseur et de lancer une exception (ce qui permet de "faire remonter" l'information aux fonctions appelantes) qui pourra (devra) être récupérée "plus haut" pour signaler à l'utilisateur qu'il s'est gouré

    Cela peut transformer la fonction en quelque chose proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    double foo(int dividende, int diviseur)
    {
        if(diviseur == 0)
            throw DiviseurIsNull();
        /* suite de la fonction dont */
        double resultat = (double) dividende / (double) diviseur;
        /*...*/
        return resultat;
    }
    Tout ce raisonnement devrait - normalement - être suivi avant d'écrire la fonction, mais, je te présente le code pour te permettre de comprendre la logique que l'on met en oeuvre

    Lorsque tu va réfléchir à ton test unitaire, tu va donc te dire:
    1. Si diviseur n'est pas nul, la fonction doit me renvoyer le quotient de "dividende" par "diviseur",
    2. Si le diviseur est nul, la fonction doit me lancer une exception (ici de type DiviseurIsNull, qui doit être définie par ailleurs )

    Hé bien, tu va écrire tes tests de manière à t'assurer que ces deux résultats seront bel et bien obtenus, d'une part avec quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int i = 3;
    int j = 2;
    int zero = 0;
    if(foo( i, j ) == ( i / j ) )
    {
        /* c'est tout bon :D */
    }
    /* par contre l'appel suivant doit lancer une exception */
    try
    {
        foo( i, zero );
    }
    catch(DiviseurIsNull &e)
    {
        /* il faut vérifier que l'on passe bien par ici */
    }
    Une fois que tu auras effectué ce test sur ton code, tu pourra estimer qu'elle fournit exactement le comportement que tu attend d'elle, et que tu n'a - a priori - plus à t'en inquiéter.

    Par contre, cette fonction peut - potentiellement - présenter un problème (enfin, si on peut dire, parce que c'est - malgré tout - ce qu'on attend d'elle ): elle va lancer une exception dans certaines conditions...

    Et comme il risque de se passer "un certain temps" entre l'écriture de cette fonction et celle de fonctions qui l'appelle, tu risque d'avoir largement le temps d'oublier qu'elle lance l'exception

    Par chance, les bibliothèques de test unitaire sont en mesure de remarquer qu'une exception lancée n'est pas récupérée.

    Le fait de créer un test unitaire sur les autres fonctions (celles qui appellent foo, en l'occurrence) te permettra de te rappeler que foo lance une exception, et donc de décider du fait qu'il soit opportun, ou non, de gérer cette exception (si nous sommes en mesure de le faire, ne serait-ce que partiellement dans la fonction que l'on écrit).

    Au final, le fait de respecter une politique "stricte" de test, de prévoir (et d'effectuer) systématiquement des tests sur toute ta production de code te permettra d'avoir l'assurance "de proche en proche" que tout se déroulera correctement
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  12. #12
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    27 136
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Mai 2008
    Messages : 27 136
    Billets dans le blog
    150
    Par défaut
    Un grand merci à toi, pour l'explication.

    Par contre, juste une question supplémentaire, la STL, elle lance des exceptions pour certainement beaucoup de méthode, comment je peux connaitre ce que je dois catcher et même comment je peux savoir qu'elle va lancer une exception. C'est quelque chose que j'ai jamais remarqué sur la doc, et du coup je regrette presque la capacité des langage tels que JAVA et C# de me dire , fais gaffe lui il envoie une exception.
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  13. #13
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Il y a beaucoup moins de méthodes qui risquent de lancer une exception que ce que tu peux croire...

    De tête, et en risquant d'en oublier quand même, il y a:
    • le constructeur par copie (bad_alloc)
    • les différentes méthodes d'insertion (idem)
    • les méthodes at(), qui sécurisent l'accès à un élément par son index (out_of_range, je crois), pour autant que le compilateur en dispose
    • la tentative de lecture d'un flux étant déjà passé au status "EOF"


    Les autres renverront le plus souvent une valeur connue pour être invalide

    Mais c'est justement là qu'interviennent les bibliothèques de test en te permettant de remarquer qu'une des méthodes utilisée a lancé une exception que tu n'a pas rattrapée
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  14. #14
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Salut,

    A vrai dire, je ne suis pas particulièrement d'accord avec ton point de vue (même s'il se défend sans trop de problème )...
    Citation Envoyé par koala01 Voir le message
    Toutes ces techniques ont pour ambition de te faire réfléchir, avant d'écrire la première ligne de code d'une fonction, à la logique qu'elle doit suivre pour fournir le résultat attendu, en te permettant d'en structurer les différentes étapes.

    Une fois l'algorithme mis au point, il devient facile, en n'utilisant qu'un bic et un papier, de t'assurer que le comportement reste logique par rapport à ce que tu attends de la fonction que tu as mis au point

    Normalement, une fois que tu as un algorithme correct et surtout facile à "traduire en code", tu as, pour ainsi dire, la certitude que ta fonction fera ce que tu attend d'elle dés la première compilation réussie (c'est à dire, une fois que tu as corrigé les différentes fautes d'attention)
    Sauf que je suis comme Saint Thomas et je ne crois que ce que je vois. Donc j'ai tendance à user du debugger pour me 'rassurer', même après avoir travailler sur la conception et d'avoir préparer mes testU.

    L'expérience m'a montré que ce n'était pas toujours inutiles pour plusieurs raisons :
    1/ Je ne connais pas le langage sur le bout des doigts et certaines lignes dont j'étais persuadé qu'elle correspondait à telle code, correspondait en fait à un autre,
    2/ Tourmenté par un inconscient certainement étrange, j'ai tendance aussi à faire des lapsus et écrire un code différent de celui que je voulais écrire
    3/ Les bibliothèques externes sont parfois (souvent ?) 'mal' documentées : elles comportent des zones de non-dits dont on prend conscience qu'en analysant le comportement du code...
    4/Un compilateur comprend mieux le code source que moi ... vu qu'il génère le code cible (sans compté que parfois j'ai un lecture certainement un peu rapide)
    5/ Tu as plus souvent dans une entreprise un compilateur et un debugger que des bonnes pratiques ....
    6/ Toutes les fautes d'orthographes de ce fil, traduisent aussi ma faillibilité lorsque j'écris du code ;-)

  15. #15
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Sauf que je suis comme Saint Thomas et je ne crois que ce que je vois. Donc j'ai tendance à user du debugger pour me 'rassurer', même après avoir travailler sur la conception et d'avoir préparer mes testU.
    Je vais un peu "adoucir" mon langage parce que j'ai l'impression que tu as mal compris le sens de mon intervention:

    Bien sur, les méthodes de "tracking" quelles qu'elles soient (cout dans tous les sens, log de fonctionnement, débuggueur, ...) ont une utilité que je ne remet absolument pas en question...

    Ce que je remet en question, c'est l'optique du
    Citation Envoyé par trop de monde
    j'écris mon code comme il me vient à l'esprit, je passerai mon temps à traquer les erreurs à coups d'une (ou de plusieurs) de ces méthodes
    Du coup, ce pour quoi je plaide, c'est une utilisation "raisonnée" de ces méthodes où elles servent - et nous nous rejoignons donc visiblement - à apporter un "surplus de confiance" dans l'ensemble du processus de développement, voire, à déterminer pourquoi le test unitaire "plante" lamantablement parce que l'on effectue le test if(ptr->suivant) sans avoir vérifié si ptr n'est pas nul...
    L'expérience m'a montré que ce n'était pas toujours inutiles pour plusieurs raisons :
    1/ Je ne connais pas le langage sur le bout des doigts et certaines lignes dont j'étais persuadé qu'elle correspondait à telle code, correspondait en fait à un autre,
    2/ Tourmenté par un inconscient certainement étrange, j'ai tendance aussi à faire des lapsus et écrire un code différent de celui que je voulais écrire
    1/ je passerais pour un fou si je prétendais connaitre le langage sur le bout des doigt... j'adhère donc tout à fait cette raison
    2/ oui, mais, si tu écris tab.erase au lieu de tab.insert... ton test unitaire devrait te l'indiquer . Bon, je sais, c'est parfois plus compliqué que cela ... Mais, erreur de syntaxe mises à part, ce devrait être le rôle de ton test unitaire de remarquer que tu as confondu deux variables de même type et que tu as utilisé l'une en croyant qu'elle avait l'utilité de l'autre
    3/ Les bibliothèques externes sont parfois (souvent ?) 'mal' documentées : elles comportent des zones de non-dits dont on prend conscience qu'en analysant le comportement du code...
    4/Un compilateur comprend mieux le code source que moi ... vu qu'il génère le code cible (sans compté que parfois j'ai un lecture certainement un peu rapide)
    Là dessus, je ne puis encore qu'être d'accord...

    Mais je considère malgré tout que débuggeur et autres méthodes de traçage de données doivent venir en complément du reste, et qu'il ne faut pas, comme je l'ai dit plus haut, remettre la gestion des erreurs de programmation au moment où ces différentes possibilités seront réellement accessibles.

    Comme je l'ai déjà dit, le plus gros du retard sur un projet survient le plus souvent... dans une période dans laquelle on "crois qu'il est fini" et où l'on essaye de "mettre les derniers détails au point" ("test du singe", débuggage,...)

    Une grosse partie du retard pourrait pourtant être évitée si on veillait "en permanence" à la qualité du process en cours (conception, algorithmique, écriture du code), selon les méthodes qui y sont propres
    5/ Tu as plus souvent dans une entreprise un compilateur et un debugger que des bonnes pratiques ....
    Comment ne pas être d'accord avec toi

    Cependant, il est possible de (et je dirais qu'il faut se battre à tous les niveaux pour) faire évoluer les mentalités...

    Cela me rappelle une anecdote assez sympa:

    Je discutais un jour avec un "vieux briscard" de l'informatique (il a encore connu le temps des cartes perforées, c'est tout dire ) qui, après s'être écrié "C++, quelle horreur, c'est write once, read never" m'a expliqué qu'un de ses collègues "s'amusait", chaque fois qu'il "posait les pattes" sur un code, à en supprimer systématiquement toute la "mise en forme" qui m'est si chère et à faire des lignes aussi longues que possibles regroupant parfois plusieurs dizaines ou plusieurs centaines d'instructions (avec 65000 caractères minimum acceptés sur une ligne unique par les compilateur, il y a de quoi faire )

    Ce vieux briscard a donc, simplement, mis au point un script qui:
    • vérifiait les fichiers de code source
    • supprimait ceux pour lesquels la mise en forme n'était définitivement pas bonne
    • envoyait un mail à l'auteur de la dernière modification pour lui signaler le fait qu'elle était perdue
    (le système était capable de gérer un nombre impressionnant de versions concurrentes d'un même fichier )

    Il a fait tourner son script trois fois, obligeant son collègue à refaire une bonne partie du travail qu'il avait déjà fait... Mais au final, son collègue a abandonné son habitude exécrable

    Ne vois pas dans le fait que le vieux briscard dont je parle est mon père une quelconque sorte de fidélité mal placée, mais, mon avis est qu'il avait raison d'agir ainsi
    6/ Toutes les fautes d'orthographes de ce fil, traduisent aussi ma faillibilité lorsque j'écris du code ;-)
    Mais les fautes d'orthographes de l'écriture se transforment généralement en:
    • erreur de syntaxe (le ";" oublié )
    • variable ou valeur non déclarée
    • fonction inconnue

    Bref, en un tas de choses qui doivent être résolues pour que la compilation arrive à son terme, et donc, bien avant d'être en mesure d'utiliser les méthodes de traçage de l'information
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  16. #16
    Membre confirmé
    Profil pro
    Inscrit en
    Février 2009
    Messages
    86
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Février 2009
    Messages : 86
    Par défaut
    Si j'ai bien compris, un bon debugage devrait chronologiquement commencer par :

    1) Vérification au crayon du résultat attendu.
    2) Tests unitaires (c'est a dire vérification fonction par fonction et/ou classe par classe).
    3) Tracage de flux d'information, fichier log.

  17. #17
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par koala01 Voir le message
    long blabla
    Effectivement, on partage fondamentalement le même point de vue : le debuggage n'est pas là pour palier à l'absence de rigueur en amont (spec/conception/testU) .

    Citation Envoyé par sphere369 Voir le message
    Si j'ai bien compris, un bon debugage devrait chronologiquement commencer par :

    1) Vérification au crayon du résultat attendu.
    2) Tests unitaires (c'est a dire vérification fonction par fonction et/ou classe par classe).
    3) Tracage de flux d'information, fichier log.
    Un bon développement devrait commencer par des specs, une conception, une validation (testU, valid fonctionnelle, valid logiciel, valid système).

  18. #18
    Membre confirmé
    Profil pro
    Inscrit en
    Février 2009
    Messages
    86
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Février 2009
    Messages : 86
    Par défaut
    Citation Envoyé par 3DArchi Voir le message


    Un bon développement devrait commencer par des specs, une conception, une validation (testU, valid fonctionnelle, valid logiciel, valid système).
    C'est peut être une question a 1 euro que je vais poser mais qu'est ce qu'un spec (spécification, cahier des charges, diagrammes UML)?

  19. #19
    Membre confirmé Avatar de sopsag
    Profil pro
    Inscrit en
    Octobre 2008
    Messages
    224
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 224
    Par défaut
    Si je puis me permettre de me mêler à ces longues délibérations sur l’art du débogage, voici mes habitudes en la matière :

    Un ensemble de fonctions de trace. Dans un contexte de développement industriel, il s’agit souvent de macros (#define TRACE) qui peuvent être désactivées (en release par exemple) pour ne rien laisser dans le code. Ces traces doivent permettre d’être activées par niveau de gravité et éventuellement par module.
    Voici un petit exemple de ce j’aime trouver dans mon code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    nb = my_list.len() ;
    CTRACE2( nb == 0,("Attention : my_list est vide !"))
    CTRACE4( nb != 0,("my_list contient %d elements",nb ))
    for ( int i = 0 ; i < nb ; i++ )
        //...
    Ici, le C signifie conditionnel, le 2 un niveau de gravité équivalant à un warning et le 4, un niveau de gravité équivalant à une simple information.
    Quand on développe dans un environnement préhistorique sans débuggeur, c’est bien commode et en plus, ça rend le code plus compréhensible puisque ça oblige à commenter les points sensibles.
    Il est très important de mettre en place ce mécanisme avant de commencer à coder car l’expérience montre qu’on ne revient jamais après coup pour ajouter des traces.

    Toutes mes classes (ou presque) contiennent 2 méthodes :
    auto_check qui teste les invariants i.e tout ce qui peut être vérifié à n’importe moment de la vie d’un objet : pointeurs valides, valeurs numériques dans les intervalles autorisés, contraintes entre les valeurs liées (ex : nb de cases vides + nb de cases pleines = nb de cases total) etc.
    Selon ce que l’on s’autorise, cette méthode peut lever une exception, stopper l’exécution, envoyer une trace ou simplement renvoyer false.
    to_string qui retourne une chaîne de caractères qui décrit le contenu de l’objet. Très utile pour les traces.

    Pour une classe vraiment sensible, et si je n’ai pas une spec déjà bien claire avant (ce qui est souvent le cas), j’écris en parallèle l’interface et les tests unitaires avant d’écrire l’implémentation. Souvent, en écrivant les tests, je m’aperçois des lacunes de mon interface.

    J’aime aussi écrire ce que j’ai baptisé un "testeur universel". Ca ne s’applique à tous les cas, mais quand c’est possible, ça veut vraiment le coup :
    Il s’agit d’une boucle infinie qui à chaque tour choisir aléatoirement une méthode à appeler et l’appelle avec des paramètres aléatoires (en respectant les domaines de définition bien sûr). Cela permet de tester la robustesse de ma classe : si ça tourne pendant une semaine sans planter ni bouffer toute la mémoire, c’est bon signe.
    Si en plus, on peut écrire ce que j’appelle un "oracle" qui est une espèce de classe parallèle qui "sait" ce que la classe testée doit renvoyer, on peut en plus assurer la fonctionnalité (en plus de la robustesse).
    Ce teste permet de découvrir des enchaînements d’appels auxquels on n’avait jamais pensé et qui font planter.
    C’est utile aussi quand on veut écrire une version très optimisée d’un algo : l’oracle n’est autre que la version triviale (mais lente). Si 1000000 d’itérations avec des paramètres aléatoires n’ont pas révélé de différence entre les valeurs renvoyées par els 2 versions, c’est qu’on touche au but.

    Voilà, j’espère que ça donnera des idées à quelqu’un...

    Hadrien

  20. #20
    Membre confirmé
    Profil pro
    Inscrit en
    Février 2009
    Messages
    86
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Février 2009
    Messages : 86
    Par défaut
    Bonjour, j'ai trouvé réponse à ma question à 1euro.....

    Ok, merci à tous pour votre aide.

    A bientôt.

    Sphere369.

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Réponses: 0
    Dernier message: 05/08/2009, 12h49
  2. [Avis] livre "programmateur VBA EXCEL " pour les nuls
    Par gangura dans le forum Macros et VBA Excel
    Réponses: 7
    Dernier message: 18/09/2007, 18h14

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo