J'aurais aimé avoir votre avis sur l'utilisation des pointeurs en C++.
On conseil très souvent de ne pas avoir les habitudes du C lorsque l'on fait du C++, et il se trouve que les pointeurs en font partie.
Actuellement, je crée un projet dont une variable évolue tout au long du programme. Elle est déclarée dans la fonction main, et finalement retranscrite dans un fichier à la fin du programme. Pour la modifier, j'utilise plusieurs classes, espaces de noms, fichiers, fonctions, ... Mais je trouve que cela fait un peu lourd d'écrire chaque fois quelque chose comme ceci :
void main() {
FinalText text;
text += MyFunc1(...);
text += MyFunc2(...);
text += MyFunc2(...);
}
De plus, cette variable contient des méthodes qui doivent parfois être appelées pour ajouter quelques choses au rendu final ou autre.
Enfin, le contexte on s'en fou un peu, mais voici à quoi mon code ressemble actuellement :
Juste une petite question : pourquoi absolument vouloir passer une donnée dont tu sais qu'elle existe obligatoirement par pointeur, au lieu de la passer par référence?
Cela n'aurait que des avantages:
tu aurais la certitude que la donnée existe au moment de l'appel de la fonction
la syntaxe pour manipuler la référence serait la même dans la fonction appelée que s'il s'agissait de la donnée elle-même
tu n'aurais aucun doute quant à l'éventuel besoin de libérer (ou non) la mémoire allouée au pointeur
De plus, on le répète à l'envie : on n'utilise les pointeurs que lorsque l'on n'a pas d'autre choix. Or, l'alternative qui consiste à utiliser des références -- qu'elles soient constantes ou non -- s'avère souvent parfaitement utilisable (surtout depuis C++11 et l'arrivée de std::reference_wrapper )
Enfin, fais juste attention à un truc tout bête: tu te pleins de la lourdeur d'écriture d'un code proche de text += maFonction(...);, mais l'alternative est de faire en sorte que maFonction occasionne ce que l'on appelle un "effet de bord" sur ta donnée (qu'elle prenne la dite donnée sous la forme d'un pointeur ou d'une référence n'ayant pas beaucoup d'importance sur ce point).
Or la présence d'un effet de bord dans un code n'a que des inconvénients:
1- Cela rend le code beaucoup plus difficile à comprendre et à débugger, parce que chaque appel de fonction (et chaque fonction appelée de manière indirecte) risque de modifier la valeur de nos données, et, si ce n'est pas fait correctement, on en arrive rapidement à devoir vérifier toutes les fonctions qui sont appelées pour arriver à déterminer "ou ca cloche".
2- Cela fout un bordel incroyable lorsque l'on travaille dans un contexte multi-threadé, car il suffit que deux fonctions ayant un effet de bord sur la même donnée soient appelées par deux threads différents pour que le résultat soit imprévisible
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Le "rejet" des pointeurs (rejet relatif, c'est surtout qu'il faut les utiliser uniquement quand ils sont adaptés) en C++ n'est pas une question de goût. Il y a des raisons techniques. Si ton seul argument, c'est que "c'est lourd", c'est bof bof.
En soit, la seconde syntaxe n'est pas choquante. On pourrait proposer ça par exemple pour modifier des données qui sont dans un buffer (mais pas avoir un pointeur alors que tu peux utiliser une reference). Mais dans ton exemple, je ne vois pas trop de raison de préférer la seconde. (Et même, on pourrait dire que la première syntaxe respecte plus la pureté fonctionnelle).
Si les pointeurs ne te font pas peur, c'est probablement que tu ne comprends pas les problèmes qu'ils posent. (Et donc c'est encore plus risqué de les utiliser). Si tu les utilises massivement, c'est peut être parce que tu ne connais pas les alternatives et en quoi elles sont mieux. (Donc c'est encore plus un problème)
J'avoue ne pas comprendre comment on est passé d'un appel de fonction à l'utilisation d'un pointeur de fonction membre ?
Du coup, je ne vais me concentrer que sur le paramètre text. Pourquoi un pointeur ? Et surtout, pourquoi la référence devient magiquement un pointeur lorsqu'elle devient un paramètre ?
À mon sens, il existe plusieurs situations où le pointeur est "légitime" (ou pas):
passage d'ownership d'un objet alloué dynamique. Sauf que cela fait belle lurette qu'il y a std::unique_ptr ou autres pointeur intelligent.
utilisation d'un paramètre optionnel. Personnellement je préfère un type explicite comme std::optionnel.
le couple data+len, même si un objet dédié tel qu'un array_view serait mieux (std::span en c++20).
un référé qui peut changer. Mais un std::reference_wraper ou not_null_ptr est de mise (gsl::not_null).
des cas obscurs de manipulation de mémoire.
Où se situe text là-dedans ? Le paramètre devrait être une référence.
Je ne connais pas réellement la différence entre les références et les pointeurs en fait. Raison pour laquelle je ne les utilises probablement pas.
Je ne me plein pas non-plus vraiment de la "lourdeur" syntaxique du code, mais plutôt du fait de devoir déclarer plusieurs de dizaine de fonctions qui effectueraient "plus de travail" à renvoyer une valeur, puis ensuite de la traiter avec la variable que de traiter directement cette variable.
Il est également possible que je ne connaisse pas tout des pointeurs (c'est même une certitude), mais je pense en connaitre suffisamment que pour pouvoir les utilisées à bonne escient.
Quant aux effets de bord, même si le risque n'est pas nul, il l'est quasiment.
Ma variable à traiter contient plusieurs champs et méthodes qui agissent sur la valeur privée, qui sera par la suite retranscrite dans un fichier. Si j'ai besoin d'ajouter une donnée X ou Y à l'aide d'une méthode de cette même variable par exemple, la méthode se charge de modifier la variable et/ou les conteneurs temporaire privés pour que tout soit bien manipuler lors de la retranscription des données.
Evidemment, je pourrais me passer des pointeurs, mais je n'ai encore rencontrer aucuns problèmes pour le moment.
(1) Il est également possible que je ne connaisse pas tout des pointeurs (c'est même une certitude), mais je pense en connaitre suffisamment que pour pouvoir les utilisées à bonne escient.
(2) Quant aux effets de bord, même si le risque n'est pas nul, il l'est quasiment.
(1) Les pointeurs c'est la raison numéro 1 d'undefined behavior dans les programmes C et même les experts du langage se cassent les dents dessus. Alors je pense pouvoir affirmer sans trop m'inquiéter que tu vas juste te tirer une balle dans le pied aussi souvent qu'il sera possible.
(2) Des effets de bords, tu en fais tout le temps, c'est leur quantité qui peut poser problème et la manière dont ils interagissent.
Geralt de Riv a écrit:
Je ne me plein pas non-plus vraiment de la "lourdeur" syntaxique du code, mais plutôt du fait de devoir déclarer plusieurs de dizaine de fonctions qui effectueraient "plus de travail" à renvoyer une valeur, puis ensuite de la traiter avec la variable que de traiter directement cette variable.
Si tu as besoin de plusieurs dizaines de fonction pour remplacer quelques pointeurs, c'est ta conception qu'il faut que tu revoies. Très clairement.
Geralt de Riv a écrit:
Evidemment, je pourrais me passer des pointeurs, mais je n'ai encore rencontrer aucuns problèmes pour le moment.
Tu as combien de tests unitaires pour chacune de tes fonctions ?
Si tu as besoin de plusieurs dizaines de fonction pour remplacer quelques pointeurs, c'est ta conception qu'il faut que tu revoies. Très clairement.
Tu veux dire quoi par là ?
Ksass`Peuk a écrit:
Les pointeurs c'est la raison numéro 1 d'undefined behavior dans les programmes C et même les experts du langage se cassent les dents dessus. Alors je pense pouvoir affirmer sans trop m'inquiéter que tu vas juste te tirer une balle dans le pied aussi souvent qu'il sera possible.
Evidemment je ne suis pas un professionnel... Mais dans simplement ça :
Quels pourraient être les problèmes/risques ? Mise à part que les données manipulées par mon programme sont en provenance de l'utilisateur ?
En gros, mon programme fonctionne comme tel. Avec également des appels de méthodes depuis la variable du type X pointé.
Ksass`Peuk a écrit:
Des effets de bords, tu en fais tout le temps, c'est leur quantité qui peut poser problème et la manière dont ils interagissent.
Oui, excuse moi, je me suis trompé de terme. Eh bien, justement, des fonctions void modifiant une variable pointée, il ne peut y avoir que des effets de bord. Mais, comment cela pourraient-il poser problème à long terme ?
Ksass`Peuk a écrit:
Tu as combien de tests unitaires pour chacune de tes fonctions ?
Zéro. J'ai testé beaucoup de possibilités, et il s'avère que cela fonctionne à chaque fois. Et je sais, ce n'est surement pas bon...
Quels pourraient être les problèmes/risques ? Mise à part que les données manipulées par mon programme sont en provenance de l'utilisateur ?
Bien sur, si on fait toujours très attention avec les pointeurs et qu'on ne se trompe jamais, il n'y aura pas d'accidents. Mais on se gourre si facilement : la preuve c'est que tu essaies d'affecter *foo, au lieu de *MyVar...
D'autant que le choix de MyVar comme nom n'est pas terrible. C'est un pointeur, une variable contenant l'adresse d'une chaine, faudrait quelle s'appelle adr_myvar, un truc comme ça, sinon on est en train de se faire des noeuds par métonymie, en appelant variable ce qui est son adresse. Les conditions idéales pour se gourrer, on se les construit soi-même, souvent.
L'un des apprentissages le plus difficile (après celui de se faire un idée précise de ce que sont les pointeurs et de tous les problèmes qu'ils peuvent occasionner, justement) est d'arriver à se convaincre de faire comme si on ne savait rien du contexte dans lequel la fonctionnalité que l'on développe est utilisée.
Je m'explique:
Mettons que tu crées une fonction foo. Tu le fait sans doute soit pour respecter le SRP (et donc la règle "un verbe == une fonction"), soit parce que tu t'es rendu compte que tu dois "factoriser" du code afin de respecter le DRY.
A priori, tu as donc au moins une vague idée d'au moins un contexte dans lequel cette fonction sera appelée: que la variable transmise comme paramètre aura été déclarée de telle ou telle manière, qu'elle a déjà subit telle ou telle transformation avant l'appel de la fonction que tu développes, et -- peut-être même -- qu'elle subira telle ou telle transformation après l'appel à la fonction que tu développes.
Hé bien, tu dois te convaincre d'ignorer purement et simplement ce contexte, parce que, si ça se trouve, dans les semaines et les mois à venir, tu fera appel à cette fonction dans dix ou vingt situations totalement différentes du contexte dans lequel tu envisages d'y faire appel aujourd'hui.
Or, une fois que l'on est à l'intérieur de la fonction, tout ce que la fonction sait, c'est :
les données qu'elle a reçues en paramètre
les variables locales qu'elle va déclarer
la logique qu'elle doit suivre
les éventuelles fonctions auxquelles elle devra faire appel (en partant du principe qu'elles feront correctement leur boulot), et donc
les données qu'elle devra leur transmettre et sous quelle forme et
le type de donnée qu'elle doit obtenir lors de l'appel
la valeur qu'elle devra (éventuellement) renvoyer
Mais, par contre, la fonction est totalement incapable de "tracer l'origine" des données qu'elle reçoit en paramètre: elle n'a aucune idée du contexte dans lequel elle a été appelée, et elle ne peut donc absolument pas "partir du principe" que tel paramètre a été déclaré de telle ou telle manière ou qu'il a déjà subit telle ou telle modification.
Tout ce qu'elle peut éventuellement faire, c'est comparer la valeur de la donnée qu'elle a reçu en paramètre à l'une ou à l'autre valeur "prédéfinie" afin de décider la manière dont ell va travailler.
Mais l'usage cette possibilité devrait être réduit au maximum, car cela rend la logique à mettre en place tout de suite beaucoup plus compliquée.
Ensuite, vient le temps de se poser la question dont la réponse fâche : est-ce que la fonction peut faire confiance à celui qui y fera appel pour qu'il transmette des données valides et cohérentes?
Malheureusement, la réponse à cette question est toujours non, car, en vertu de la loi de l'emmerdement maximum, l'utilisateur (comprend: celui qui fera appel à la fonction) est un imbécile distrait. Et le fait que l'utilisateur (présumé) de la fonction soit -- justement -- celui qui l'aura développée n'y change absolument rien
La fonction doit donc systématiquement mettre en doute toutes les données qu'elle obtient par n'importe quel moyen (que ce soit sous la forme de paramètre, en la demandant à l'utilisateur de l'application ou en l'extrayant d'un flux de données quelconques) et dont elle ne définit pas elle-même la valeur.
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
J'ai été trop vite pour écrire mon exemple. Je corrige ça tout de suite.
Tu viens de montrer quel est le problème : tu feras des erreurs. Et avec les pointeurs, les erreurs sont tres compliquées a debuger.
Bien sur que c'est un code d'exemple et que pour un vrai code, tu aurais fait plus attention. Mais pour un vrai code, tu écriras plus de code, tu n'auras pas un relecteur qui te signalera l'erreur. Et quelque soit l'attention que tu portes a ton code, tu feras toujours des fautes.
Les évolutions des langages (pas que le C++, tous les langages) et des techniques de programmation ne sont qu'une réponse au fait qu'on essaie d'éviter de refaire les mêmes erreurs.
Quels pourraient être les problèmes/risques ? Mise à part que les données manipulées par mon programme sont en provenance de l'utilisateur ?
A long terme?
Tu vas avoir une fonction (foo) qui a un effet de bord qui appelle une deuxième fonction (bar) qui a un autre effet de bord qui elle-même appelle deux fonctions encore différentes (doIt et doSomething) (*) qui ont elles aussi un effet de bord (différent de tous les autres, cela va de soi), et tu vas te rendre compte que foo ne fournit pas le résultat auquel tu t'attendais.
Et pour pouvoir corriger le problème afin d'obtenir le résultat attendu, tu vas devoir commencer par... localiser l'origine de ce problème.
Mais pour localiser l'origine du problème, tu n'auras que l'embarras du choix car le problème peut trouver son origine:
au niveau de la logique de ta fonction foo
au niveau de l'effet de bord occasionné par foo
au niveau de l'effet de bord occasionné par bar
au niveau de l'effet de bord occasionné par doIt
au niveau de l'effet de bord occasionné par doSomething
Mais comment pourras tu en être sur? La seule solution que tu auras sera de vérifier systématiquement le résultat de toutes de foo, de bar, de doIt et de doSomething pour t'assurer que les modifications apportées sont cohérentes.
A l'inverse, si aucune de ces fonctions n'occasionnent un effet de bord, mais qu'elles renvoient systématiquement une valeur correspondant au résultat qu'elles ont obtenu lors de leur traitement, les choses deviennent bien plus simples:
tu vérifie le résultat obtenu lors de l'appel de chaque fonction. S'il correspond à ce à quoi tu t'attendais, c'est que cette fonction fait correctement son taf. Et s'il ne correspond pas à ce à quoi tu t'attendais, tu vas voir dans la fonction ce qui se passe, pourquoi elle ne donne pas le résultat auquel tu t'attendais.
Cette "nouvelle" fonction que tu observe fera peut-être -- elle aussi -- appel à d'autres fonctions, qui renverront -- elles aussi -- le résultat de leur traitement. Et encore une fois, tu pourras vérifier que le résultat de leur traitement correspond au résultat auquel tu t'attendais. Et ainsi de suite.
A chaque fois que tu décideras d'aller voir ce que fait une fonction qui ne renvoie pas le résultat auquel tu t'attendais, tu te rapprochera "un peu plus" de l'origine réelle de ton problème, jusqu'à ce que, à un moment donné, tu tombes sur "la connerie qui fait tout foirer": une comparaison foireuse, une boucle qui ne s'exécute pas ou l'ajout d'un 's' en trop dans une chaine de caractères.
Bien sur, la connerie en question peut être un peu plus subtile que les cas que j'ai cités. Mais l'idée reste malgré tout la même.
Le gros avantage, c'est qu'au lieu de devoir parcourir toutes les fonctions qui sont appelées pour t'assurer qu'elles font correctement leur taf, tu pourras systématiquement écarter du problème celles dont le résultat est correct, pour n'avoir à t'occuper que de celles pour lesquelles ce n'est pas le cas.
Sur un projet important, cela peut faire passer le temps de débugage de plusieurs heures à... quelques minutes à peine
(*) Et je pourrais continuer longtemps comme cela, avoir plusieurs dizaines de fonctions appelées au final par une seule fonction à la base est monnaie courante
- Edité par koala01 23 juin 2018 à 18:51:12
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Oups J'ai été trop vite pour écrire mon exemple. Je corrige ça tout de suite.
Mais ce code n'est qu'un exemple. Je n'utiliserais jamais de nom tel que foo ou MyVar, c'était juste pour illustrer...
Je vois bien. Je profitais de l'exemple pour souligner, c'est qu'il y avait deux trucs, en apparence innocents, qui contribuent à s'embrouiller. Classiquement
void foo(struct Thing *thing) {
...
}
c'est fait avec de la bonne volonté (c'est mieux que *t) mais il faut garder obstinément dans un coin de la tête, pour lire le code qui suit le fait que "thing" n'est pas une structure Thing, mais un pointeur.
Le second c'est qu'en travaillant avec des pointeurs plutot qu'avec des références, il faut constamment faire attention entre le pointeur, qu'on manipule explicitement, et la chose pointée.
Mine de rien, des neurones, y en a pas tant que ça, et ils fatiguent vite. Dès qu'on charge un peu, le taux d'erreurs grimpe, ce qui donne du boulot en plus. Et ça, c'est mal.
Merci pour toutes vos réponses, je vois mieux comment m'améliorer
Je sais que je vais paraître un peu têtu, mais je suis quand même pratiquement sûr du résultat qu’occasionnerons mes fonctions void qui modifient ma valeur pointée (encore une fois, sans testes unitaire...)
En effet, ce n'est pas "bêtement" modifier un champ std::string privé à tout bout de champs. Chaque fonction void est appelée si et seulement si le programme détermine qu'un tel ajout doit être fait (il n'y a que des ajouts, aucunes de modifications des valeurs précédentes). En prime, lors de la retranscription grâce à une méthode MyVar.GetFinal(), un "brassage" est effectué entre les champs privés afin d'avoir une sortie optimal.
Par exemple si l'utilisateur utilise plusieurs fois une fonctionnalité externe à inclure, le programme ne perd pas son temps à ré-importer la fonctionnalité, puisqu'elle à déjà été utilisée. Il ne boucle donc pas bêtement.
Les valeurs pointées sont utilisées par les fonctions uniquement si le programme leur en "donne la permission". Et même si il y a une petite coquille (ce qui n'est pas censé arrivé avec tous les testes, mais on est jamais trop prudent), la méthode GetFinal() s'assure de la corriger intelligemment selon les précédentes valeurs.
Autre exemple, si une fonction à besoin de faire appel à une des méthodes de la variable pointée, cette même méthode s'assure de la viabilité des données ajoutées afin d'éventuellement les corriger avant de les intégrées, si besoin.
Je pense faire assez attention à l'utilisation des pointeurs dans ce projet en fait. Mais, évidemment, cela ne veut pas dire qu'il n'y aucuns risque d'erreur.
Par contre, pourquoi une fonction devrait-elle "savoir" quelles ont étés les précédentes modifications de la variable pointé qu'elle traite ?
Alors, utilise des références (constantes!!! histoire de bien indiquer qu'elles ne seront pas modifiées) comme paramètre. Tu verras, tu ne t'en sentira que mieux dans ton développement
Et puis
Geralt de Riv a écrit:
mais je suis quand même pratiquement sûr du résultat qu’occasionnerons mes fonctions void qui modifient ma valeur pointée (encore une fois, sans testes unitaire...)
Traduction: tu n'es sur de RIEN: Tu crois, tu espères que tes fonctions n'auront pas d'effets de bord.
Mais comme dirait mon père: laisse croire les béguines, elles sont spécialistes et payées pour!
Toi, en tant que développeur, tu dois avoir des certitudes. Tu dois même dans certains cas (Ksass ` Peuk pourrait t'en parler :D) pouvoir apporter la preuve de tout ce que tu avances.
Le problème, c'est que le développeur informatique est soumis à la même règle que les ambulanciers:
on ne peut être sur que d'une chose : on n'est jamais sur de rien (et même cela, il est permis d'en douter )
- Edité par koala01 23 juin 2018 à 21:02:28
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
J'aime bien tes petites notes philosophique éducative
koala01 a écrit:
Alors, utilise des références (constantes!!! histoire de bien indiquer qu'elles ne seront pas modifiées) comme paramètre. Tu verras, tu ne t'en sentira que mieux dans ton développement
Ok, j'y tâcherais
koala01 a écrit:
Traduction: tu n'es sur de RIEN: Tu crois, tu espères que tes fonctions n'auront pas d'effets de bord.
Correction: Je suis sûr des testes que j'ai effectuées, mais j'escompte et désire qu'il n'y aura pas d'effets de bord indésirable lorsque ça sera au tour de l'utilisateur.
koala01 a écrit:
Toi, en tant que développeur, tu dois avoir des certitudes. Tu dois même dans certains cas (Ksass ` Peuk pourrait t'en parler :D) pouvoir apporter la preuve de tout ce que tu avances.
Oui, j'ai jeter un p'tit coup d’œil au cours sur la preuve de programmes C de Ksass`Peuk. J'ai également suivi une partie du talk d'hier (jeudi), vers 20h00. Ce que je retiens de tout ça, c'est qu'il faut se fier aux outils extérieur et avoir un contrôle maximum sur toutes les éventualités. Je ferrais donc des testes unitaire (ce sera une "première" pour moi ).
koala01 a écrit:
Le problème, c'est que le développeur informatique est soumis à la même règle que les ambulanciers:
on ne peut être sur que d'une chose : on n'est jamais sur de rien (et même cela, il est permis d'en douter )
En effet, le doute est le commencement de la sagesse
J'aime bien tes petites notes philosophique éducative
A laquelle fais tu référence? A
koala01 a écrit:
laisse croire les béguines, elles sont spécialistes et payées pour!
ou à
koala01 a écrit:
on ne peut être sur que d'une chose : on n'est jamais sur de rien (et même cela, il est permis d'en douter )
Pour la première, remercie mon père. Un homme d'une grande sagesse qui n'a pas su s'empêcher de faire quelques conneries malgré tout
Pour la deuxième... Ca aide d'avoir servi dans un service d'ambulances pendant 8 ans
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
Correction: Je suis sûr des testes que j'ai effectuées, mais j'escompte et désire qu'il n'y aura pas d'effets de bord indésirable lorsque ça sera au tour de l'utilisateur.
Dans ce cas, mon conseil est de te rendre sans attendre dans l'église la plus proche de chez toi d'y brûler une bonne centaine de cierges et de rester là bas prier pendant quelques semaines. On ne sait jamais, il y a peut être un ange qui fera en sorte que ton programme fonctionne correctement...
Plus sérieusement, Murphy veille au grain, s'il y a la moindre chance pour que ton programme merde, tu peux être certain qu'il va merder. Espérer et désirer ne sert à rien ici, ce qu'il faut c'est être sûr que tout à été fait pour qu'il n'y ait pas de problèmes.
Faut quand même insister sur le fait que la programmation, c'est passer une grande partie de son temps à chercher pourquoi ce qu'on a écrit ne marche pas tout à fait comme on avait prévu. C'est à dire ne marche pas, tout court.
Non seulement ce temps perdu se réduit quand on prend le maximum de précautions, mais c'est aussi le cas pour le temps total = mise en place des précautions + temps perdu.
Je n'ai pas dit non plus que je m'en foutait si aucuns réels tests n'était fait Je vais bien sûr tester mes fonctions, et mon programme en général avec des outils adaptés.
Mais, en attendant que mes fonctions soient terminées, et que je soit sûr de ne plus y toucher, alors là je ferrais des tests pour m'assurer de leurs viabilités.
Au passage, que me conseilleriez-vous pour bien tester un programme / une procédure ?
Et il est en général très profitable d'écrire les tests avant de développer les fonctions.
1) parce que ça permet de voir de suite si ça marche
2) parce que quand on pense que ça marche, la dernière chose qu'on a envie de faire, c'est d'écrire des tests, alors qu'il y a plein de choses plus intéressantes à faire dans la vie.
Donc, puisqu'on en a besoin, le faire avant.
voir google "'tests unitaires", "développement dirigé par les tests", etc.
Au départ, ça peut prendre la forme d'une série d'appels
void test_myStrlen(char * string, int expectedLength) {
println("- test myStrlen(\"%s\") == %s\n",
string, expectedLenght)
int r = myStrlen(string);
if (r != exectedLength) {
println("* FAILED: found %d\n", r);
}
}
void serie_tests_myStrlen() [
test_myStrlen("", 0);
test_myStrlen("a", 1);
test_myStrlen("b", 1);
test_myStrlen("aaaaa", 5);
test_myStrlne("abcdefgh", 8);
}
Après, c'est mieux si ça va piocher les valeurs à tester dans un fichier texte.
Au départ, ça peut prendre la forme d'une série d'appels : ...
Si c'est ça que l'on appelle "test unitaire", alors aucun problème J'ai effectivement écrit une fonction par fonctions à tester qui se charge de vérifier si toutes les possibilités que j'aurais pu imaginer pour une certaine procédure ou fonction travaillent correctement. Depuis un fichier en plus.
Actuellement, ces tests se sont montrés plutôt concluent, mais j'imagine qu'il y a plus que cela à faire pour être de plus en plus certain du résultat.
Je pensais que "faire des tests unitaires" nécessitait des programmes ou logicielle externe en fait. Si ce n'est pas le cas, ou du moins de ce que j'ai compris maintenant, les teste, je les effectue bien Je ne "code pas dans le vide" .
Dans tes tests unitaires, tu dois aussi tester les cas où quelque chose ne va pas. Par exemple, si tu as un pointeur en paramètre, tu dois vérifier que ta fonction ne fait pas n'importe quoi si on lui donne NULL (avec une référence si c'est possible, tu n'as pas ce problème ^^). Tu ne dois pas seulement tester les possibilités qui arriveront peut-être dans le programme, mais aussi les possibilités qui n'arriveront pas selon toi (même si tu es sûr que tu ne l'appelleras jamais avec NULL, ben une erreur est si vite arrivée).
Tu sais, quand je faisais mes études, j'avais un prof de C (et par la suite de C++) qui se foutais pas mal du code qu'on lui donnait:
Il le compilait en activant tous les avertissement qu'il pouvait avec le compilateur, et il commençais par tester les cas pour lesquels le compilateur avait émis un avertissement.
Par exemple, le compilateur disait quelque chose au sujet d'un problème suite à la comparaison d'une valeur signée avec une valeur non signée? il testait le fonctionnement du programme avec la valeur -1 et la valeur 4 294 967 295
Et bien sur, il ne se gênait ni pour introduire "hello" ou 3.1415926 quand l'application lui demandait un nombre entier ni pour écrire 32 quand l'application attendait une chaine de caractères.
Ta note finale dépendait des avertissements que le compilateur avait émis et, surtout, du résultat des tests qu'il avait effectué suite à ceux-ci
Faire des tests unitaires, c'est cela : vérifier tous les cas qui pourraient "mal tourner" et vérifier que l'application reste cohérente. C'est rarement en une seule fonction que l'on peut s'en rendre compte
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
le test _unitaire_, c'est un test qui porte sur une partie précise, isolée.
<< Il s'agit pour le programmeur de tester un module, indépendamment du reste du programme, ceci afin de s'assurer qu'il répond aux spécifications fonctionnelles et qu'il fonctionne correctement en toutes circonstances. >> '
Le prof qui fait tourner un programme en lui refilant des données pourries, ça relève plutot du test d'intégration.
<< dans le test d’intégration, chacun des modules indépendants du logiciel est assemblé et testé dans l’ensemble. >>
en "boite blanche" (on se base sur les messages d'avertissements et un coup d'oeil au code). Qui plus est ce n'est pas automatisé.
PS Le test est là pour vérifier que la fonction se comporte correctement avec des données qui respectent une certaine spécification. Si on prend l'exemple d'une implémentation maison de strlen, avec un pointeur de caractères en paramètre, vérifier que ce pointeur n'est pas nul est
1) hors sujet parce que la spéc dit que le paramètre pointe sur une chaine, donc n'est pas NULL - faut rester dans le "domaine de définition"
2) contre-productif (ce n'est pas son job de vérifier, donc le test doit être fait ailleurs )
3) fantaisiste, parce que rien ne dit dans la spécification ce que doit être l'effet dans ce cas, et qu'on vérifie ou pas ça reste un "comportement non spécifié".
DESCRIPTION
The strlen() function calculates the length of the string pointed to by
s, excluding the terminating null byte ('\0').
RETURN VALUE
The strlen() function returns the number of characters in the string
pointed to by s.
Le but du prof qui joue à ça est plutot pédagogique : convaincre ses étudiants que les avertissements produits par le compilateur correspondent le plus souvent à des problèmes potentiels, qu'on peut déclencher en réfléchissant un peu. Faut lutter contre "ça compile donc c'est bon".
Si tu reçois une chaîne invalide dans strlen c'est une rupture du contrat, donc c'est pas un scénario d'erreur du programme, c'est un scénario d'erreur du développeur.
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
Discord NaN. Mon site.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C
Eug
Discord NaN. Mon site.
Posez vos questions ou discutez informatique, sur le Discord NaN | Tuto : Preuve de programmes C