Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
> Le 30-11-2009, AG a écrit :
>> Je travaille dans une équipe de personnes pour lesquels le
>> débugger n'est pas l'outil qui tombe sous le sens lorsqu'il
>> s'agit de trouver les bugs des outils qu'ils ont développé.
> Mais est-ce grave ? J'avoue ne quasiment jamais me servir de
> débugger. Je travaille surtout en précondition/invariant
> (assert) + tests unitaires, et éventuellement valgrind quand
> je soupsonne une corruption mémoire
> Et une fois le bug identifié, à grand coups de cerr +
> assert.
> D'autant que souvent, le bug n'apparait pas en mode débug ;-)
Pour ma part, je ne pense pas qu'une technique soit mieux
qu'une autre. Le tout, c'est d'avoir la capacité à trouver
son bug, quelque soit la manière. Partant de ce postulat,
pourquoi ne pas simplement utiliser l'équivalent de ces
différentes manières?
Je m'explique : quand je développe, je base toutes mes
gestions d'erreur sur des exceptions, y compris les controles
appliqués (valeurs acceptées, pointeurs non NULL, retours de
syscalls corrects, etc.).
Pour chaque exception levée/reçue, j'ajoute une trace (via une
macro, désactivable donc) qui résulte en une stack trace
complète dans les logs. Ca fait un peu java, mais c'est très
clair. Au bout de la stack trace (cad au niveau le plus haut
de la gestion d'erreur, qui n'est pas forcément main() ),
j'affiche le message d'erreur qui est remonté ; une manière
élégante de voir d'où est venu le bug, et en plus d'où
venaient les appels à cette fonctionnalité. Et cela permet
également d'implémenter des préconditions/invariants.
Personnellement, je trouve l'assert un peu brutal, et le
printf trop limitatif :)
Pourquoi complètement stopper un programme lorsque quelque
chose ne se passe pas bien?
Ajoutez à cela une sauvegarde des données d'entrée lors de la gesti on
d'erreur, et une compilation optimisée avec les symboles de debug dans
des fichiers séparés.
Lors d'un problème en production, il suffit généralement de regarde r la
stacktrace pour voir ce qui s'est produit, et comment corriger le
problème.
J'accorde le fait qu'il sera toujours nécessaire d'arriver à
reproduire le bug avec un test unitaire,
mais ce n'est pas toujours le cas (si une
frame de niveau supérieur est corrompue, il se peut que le programme
"lache" lors d'appels un peu plus bas, ou après plusieurs retours ...
Sans compter que quand une stack est foirée, printf ne fait
pas toujours son boulot comme il faut.
Ce que je remarque en me relisant, c'est que pour du petit
bug, la trace peut suffir, mais je ne pense pas dans tous les
cas. Pour du "bon" bug (SIGSEGV/SIGPIPE, problèmes de synchros
entre threads, freeze, etc.), le débugueur apporte un confort
inégalé,
Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
> Le 30-11-2009, AG <hey...@gmail.com> a écrit :
>> Je travaille dans une équipe de personnes pour lesquels le
>> débugger n'est pas l'outil qui tombe sous le sens lorsqu'il
>> s'agit de trouver les bugs des outils qu'ils ont développé.
> Mais est-ce grave ? J'avoue ne quasiment jamais me servir de
> débugger. Je travaille surtout en précondition/invariant
> (assert) + tests unitaires, et éventuellement valgrind quand
> je soupsonne une corruption mémoire
> Et une fois le bug identifié, à grand coups de cerr +
> assert.
> D'autant que souvent, le bug n'apparait pas en mode débug ;-)
Pour ma part, je ne pense pas qu'une technique soit mieux
qu'une autre. Le tout, c'est d'avoir la capacité à trouver
son bug, quelque soit la manière. Partant de ce postulat,
pourquoi ne pas simplement utiliser l'équivalent de ces
différentes manières?
Je m'explique : quand je développe, je base toutes mes
gestions d'erreur sur des exceptions, y compris les controles
appliqués (valeurs acceptées, pointeurs non NULL, retours de
syscalls corrects, etc.).
Pour chaque exception levée/reçue, j'ajoute une trace (via une
macro, désactivable donc) qui résulte en une stack trace
complète dans les logs. Ca fait un peu java, mais c'est très
clair. Au bout de la stack trace (cad au niveau le plus haut
de la gestion d'erreur, qui n'est pas forcément main() ),
j'affiche le message d'erreur qui est remonté ; une manière
élégante de voir d'où est venu le bug, et en plus d'où
venaient les appels à cette fonctionnalité. Et cela permet
également d'implémenter des préconditions/invariants.
Personnellement, je trouve l'assert un peu brutal, et le
printf trop limitatif :)
Pourquoi complètement stopper un programme lorsque quelque
chose ne se passe pas bien?
Ajoutez à cela une sauvegarde des données d'entrée lors de la gesti on
d'erreur, et une compilation optimisée avec les symboles de debug dans
des fichiers séparés.
Lors d'un problème en production, il suffit généralement de regarde r la
stacktrace pour voir ce qui s'est produit, et comment corriger le
problème.
J'accorde le fait qu'il sera toujours nécessaire d'arriver à
reproduire le bug avec un test unitaire,
mais ce n'est pas toujours le cas (si une
frame de niveau supérieur est corrompue, il se peut que le programme
"lache" lors d'appels un peu plus bas, ou après plusieurs retours ...
Sans compter que quand une stack est foirée, printf ne fait
pas toujours son boulot comme il faut.
Ce que je remarque en me relisant, c'est que pour du petit
bug, la trace peut suffir, mais je ne pense pas dans tous les
cas. Pour du "bon" bug (SIGSEGV/SIGPIPE, problèmes de synchros
entre threads, freeze, etc.), le débugueur apporte un confort
inégalé,
Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
> Le 30-11-2009, AG a écrit :
>> Je travaille dans une équipe de personnes pour lesquels le
>> débugger n'est pas l'outil qui tombe sous le sens lorsqu'il
>> s'agit de trouver les bugs des outils qu'ils ont développé.
> Mais est-ce grave ? J'avoue ne quasiment jamais me servir de
> débugger. Je travaille surtout en précondition/invariant
> (assert) + tests unitaires, et éventuellement valgrind quand
> je soupsonne une corruption mémoire
> Et une fois le bug identifié, à grand coups de cerr +
> assert.
> D'autant que souvent, le bug n'apparait pas en mode débug ;-)
Pour ma part, je ne pense pas qu'une technique soit mieux
qu'une autre. Le tout, c'est d'avoir la capacité à trouver
son bug, quelque soit la manière. Partant de ce postulat,
pourquoi ne pas simplement utiliser l'équivalent de ces
différentes manières?
Je m'explique : quand je développe, je base toutes mes
gestions d'erreur sur des exceptions, y compris les controles
appliqués (valeurs acceptées, pointeurs non NULL, retours de
syscalls corrects, etc.).
Pour chaque exception levée/reçue, j'ajoute une trace (via une
macro, désactivable donc) qui résulte en une stack trace
complète dans les logs. Ca fait un peu java, mais c'est très
clair. Au bout de la stack trace (cad au niveau le plus haut
de la gestion d'erreur, qui n'est pas forcément main() ),
j'affiche le message d'erreur qui est remonté ; une manière
élégante de voir d'où est venu le bug, et en plus d'où
venaient les appels à cette fonctionnalité. Et cela permet
également d'implémenter des préconditions/invariants.
Personnellement, je trouve l'assert un peu brutal, et le
printf trop limitatif :)
Pourquoi complètement stopper un programme lorsque quelque
chose ne se passe pas bien?
Ajoutez à cela une sauvegarde des données d'entrée lors de la gesti on
d'erreur, et une compilation optimisée avec les symboles de debug dans
des fichiers séparés.
Lors d'un problème en production, il suffit généralement de regarde r la
stacktrace pour voir ce qui s'est produit, et comment corriger le
problème.
J'accorde le fait qu'il sera toujours nécessaire d'arriver à
reproduire le bug avec un test unitaire,
mais ce n'est pas toujours le cas (si une
frame de niveau supérieur est corrompue, il se peut que le programme
"lache" lors d'appels un peu plus bas, ou après plusieurs retours ...
Sans compter que quand une stack est foirée, printf ne fait
pas toujours son boulot comme il faut.
Ce que je remarque en me relisant, c'est que pour du petit
bug, la trace peut suffir, mais je ne pense pas dans tous les
cas. Pour du "bon" bug (SIGSEGV/SIGPIPE, problèmes de synchros
entre threads, freeze, etc.), le débugueur apporte un confort
inégalé,
On Dec 25, 11:20 pm, BOUCNIAUX Benjamin hosting.com>
wrote:Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
> Le 30-11-2009, AG a écrit :
>> Je travaille dans une équipe de personnes pour lesquels le débugger
>> n'est pas l'outil qui tombe sous le sens lorsqu'il s'agit de trouver
>> les bugs des outils qu'ils ont développé.> Mais est-ce grave ? J'avoue ne quasiment jamais me servir de
> débugger. Je travaille surtout en précondition/invariant (assert) +
> tests unitaires, et éventuellement valgrind quand je soupsonne une
> corruption mémoire> Et une fois le bug identifié, à grand coups de cerr + assert.> D'autant que souvent, le bug n'apparait pas en mode débug ;-)Pour ma part, je ne pense pas qu'une technique soit mieux qu'une autre.
Le tout, c'est d'avoir la capacité à trouver son bug, quelque soit la
manière. Partant de ce postulat, pourquoi ne pas simplement utiliser
l'équivalent de ces différentes manières?Je m'explique : quand je développe, je base toutes mes gestions
d'erreur sur des exceptions, y compris les controles appliqués (valeurs
acceptées, pointeurs non NULL, retours de syscalls corrects, etc.).
En somme, tu considères qu'une erreur de frappe de la part d'un
utilisateur s'assimile à une violation d'un invariant dans le code.
Pour chaque exception levée/reçue, j'ajoute une trace (via une macro,
désactivable donc) qui résulte en une stack trace complète dans les
logs. Ca fait un peu java, mais c'est très clair. Au bout de la stack
trace (cad au niveau le plus haut de la gestion d'erreur, qui n'est pas
forcément main() ), j'affiche le message d'erreur qui est remonté ; une
manière élégante de voir d'où est venu le bug, et en plus d'où venaient
les appels à cette fonctionnalité. Et cela permet également
d'implémenter des préconditions/invariants.Personnellement, je trouve l'assert un peu brutal, et le printf trop
limitatif :)
L'assert est brutal pour une raison : il ne sert que dans le cas des
erreurs de programmation. L'assert ne peut pas déclencher si ton
raisonement sur le code était correct. C'est donc que tu ne sais plus
dans quel état se trouve le programme.
Dans ce cas-là, chaque instruction que tu exécutes est une risque. Il
faut en exécuter les moins possibles. Et donc, ne pas exécuter les
destructeurs. (En général. Comme pour tout, il y a des trade-offs, et il
y a des cas particuliers où la règle générale ne s'applique pas.)
Pourquoi complètement stopper un programme lorsque quelque chose ne se
passe pas bien?
Si ce qui ne se passe pas bien, c'est qu'on ne sait plus l'état du
programme, ni ce qu'il fait, il faut bien l'arrêter le plus vite
possible, avant qu'il ne fasse quelque chose de vraiment nuisible.
Ajoutez à cela une sauvegarde des données d'entrée lors de la gestion
d'erreur, et une compilation optimisée avec les symboles de debug dans
des fichiers séparés.
Lors d'un problème en production, il suffit généralement de regarder la
stacktrace pour voir ce qui s'est produit, et comment corriger le
problème.
Exactement. Et c'est ce que te donne l'assert (au moins sous Unix, mais
je crois qu'on peut en avoir pareil sous Windows). Avec un log des
entrées, évidemment, pour pouvoir réproduire l'erreur.
J'accorde le fait qu'il sera toujours nécessaire d'arriver à reproduire
le bug avec un test unitaire,
Disons qu'il ne faut pas faire la moindre modification dans les sources
du programme avant de pouvoir déclencher l'erreur dans un programme de
test. Qui en suite fera partie de tes tests de regression.
mais ce n'est pas toujours le cas (si une frame de niveau supérieur est
corrompue, il se peut que le programme "lache" lors d'appels un peu
plus bas, ou après plusieurs retours ...
Oui, mais quelque chose à corrompu la frame de niveau supérieur. C'est
ça l'erreur qu'il faut chercher, et c'est pour ça qu'il faut un log des
entrées, pour pouvoir récréer l'état qui a déclenché l'erreur.
Sans compter que quand une stack est foirée, printf ne fait pas
toujours son boulot comme il faut.
Exactement. Et quand la stack est corrompue, rémonter une exception ne
march pas comme il faut.
[..,]Ce que je remarque en me relisant, c'est que pour du petit bug, la
trace peut suffir, mais je ne pense pas dans tous les cas. Pour du
"bon" bug (SIGSEGV/SIGPIPE, problèmes de synchros entre threads,
freeze, etc.), le débugueur apporte un confort inégalé,
C'est justement ce genre de problèmes où le deboggueur ne marche pas.
Parce qu'il change les temps, et donc l'exécution du programme.
On Dec 25, 11:20 pm, BOUCNIAUX Benjamin <boucniau...@polux- hosting.com>
wrote:
Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
> Le 30-11-2009, AG <hey...@gmail.com> a écrit :
>> Je travaille dans une équipe de personnes pour lesquels le débugger
>> n'est pas l'outil qui tombe sous le sens lorsqu'il s'agit de trouver
>> les bugs des outils qu'ils ont développé.
> Mais est-ce grave ? J'avoue ne quasiment jamais me servir de
> débugger. Je travaille surtout en précondition/invariant (assert) +
> tests unitaires, et éventuellement valgrind quand je soupsonne une
> corruption mémoire
> Et une fois le bug identifié, à grand coups de cerr + assert.
> D'autant que souvent, le bug n'apparait pas en mode débug ;-)
Pour ma part, je ne pense pas qu'une technique soit mieux qu'une autre.
Le tout, c'est d'avoir la capacité à trouver son bug, quelque soit la
manière. Partant de ce postulat, pourquoi ne pas simplement utiliser
l'équivalent de ces différentes manières?
Je m'explique : quand je développe, je base toutes mes gestions
d'erreur sur des exceptions, y compris les controles appliqués (valeurs
acceptées, pointeurs non NULL, retours de syscalls corrects, etc.).
En somme, tu considères qu'une erreur de frappe de la part d'un
utilisateur s'assimile à une violation d'un invariant dans le code.
Pour chaque exception levée/reçue, j'ajoute une trace (via une macro,
désactivable donc) qui résulte en une stack trace complète dans les
logs. Ca fait un peu java, mais c'est très clair. Au bout de la stack
trace (cad au niveau le plus haut de la gestion d'erreur, qui n'est pas
forcément main() ), j'affiche le message d'erreur qui est remonté ; une
manière élégante de voir d'où est venu le bug, et en plus d'où venaient
les appels à cette fonctionnalité. Et cela permet également
d'implémenter des préconditions/invariants.
Personnellement, je trouve l'assert un peu brutal, et le printf trop
limitatif :)
L'assert est brutal pour une raison : il ne sert que dans le cas des
erreurs de programmation. L'assert ne peut pas déclencher si ton
raisonement sur le code était correct. C'est donc que tu ne sais plus
dans quel état se trouve le programme.
Dans ce cas-là, chaque instruction que tu exécutes est une risque. Il
faut en exécuter les moins possibles. Et donc, ne pas exécuter les
destructeurs. (En général. Comme pour tout, il y a des trade-offs, et il
y a des cas particuliers où la règle générale ne s'applique pas.)
Pourquoi complètement stopper un programme lorsque quelque chose ne se
passe pas bien?
Si ce qui ne se passe pas bien, c'est qu'on ne sait plus l'état du
programme, ni ce qu'il fait, il faut bien l'arrêter le plus vite
possible, avant qu'il ne fasse quelque chose de vraiment nuisible.
Ajoutez à cela une sauvegarde des données d'entrée lors de la gestion
d'erreur, et une compilation optimisée avec les symboles de debug dans
des fichiers séparés.
Lors d'un problème en production, il suffit généralement de regarder la
stacktrace pour voir ce qui s'est produit, et comment corriger le
problème.
Exactement. Et c'est ce que te donne l'assert (au moins sous Unix, mais
je crois qu'on peut en avoir pareil sous Windows). Avec un log des
entrées, évidemment, pour pouvoir réproduire l'erreur.
J'accorde le fait qu'il sera toujours nécessaire d'arriver à reproduire
le bug avec un test unitaire,
Disons qu'il ne faut pas faire la moindre modification dans les sources
du programme avant de pouvoir déclencher l'erreur dans un programme de
test. Qui en suite fera partie de tes tests de regression.
mais ce n'est pas toujours le cas (si une frame de niveau supérieur est
corrompue, il se peut que le programme "lache" lors d'appels un peu
plus bas, ou après plusieurs retours ...
Oui, mais quelque chose à corrompu la frame de niveau supérieur. C'est
ça l'erreur qu'il faut chercher, et c'est pour ça qu'il faut un log des
entrées, pour pouvoir récréer l'état qui a déclenché l'erreur.
Sans compter que quand une stack est foirée, printf ne fait pas
toujours son boulot comme il faut.
Exactement. Et quand la stack est corrompue, rémonter une exception ne
march pas comme il faut.
[..,]
Ce que je remarque en me relisant, c'est que pour du petit bug, la
trace peut suffir, mais je ne pense pas dans tous les cas. Pour du
"bon" bug (SIGSEGV/SIGPIPE, problèmes de synchros entre threads,
freeze, etc.), le débugueur apporte un confort inégalé,
C'est justement ce genre de problèmes où le deboggueur ne marche pas.
Parce qu'il change les temps, et donc l'exécution du programme.
On Dec 25, 11:20 pm, BOUCNIAUX Benjamin hosting.com>
wrote:Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
> Le 30-11-2009, AG a écrit :
>> Je travaille dans une équipe de personnes pour lesquels le débugger
>> n'est pas l'outil qui tombe sous le sens lorsqu'il s'agit de trouver
>> les bugs des outils qu'ils ont développé.> Mais est-ce grave ? J'avoue ne quasiment jamais me servir de
> débugger. Je travaille surtout en précondition/invariant (assert) +
> tests unitaires, et éventuellement valgrind quand je soupsonne une
> corruption mémoire> Et une fois le bug identifié, à grand coups de cerr + assert.> D'autant que souvent, le bug n'apparait pas en mode débug ;-)Pour ma part, je ne pense pas qu'une technique soit mieux qu'une autre.
Le tout, c'est d'avoir la capacité à trouver son bug, quelque soit la
manière. Partant de ce postulat, pourquoi ne pas simplement utiliser
l'équivalent de ces différentes manières?Je m'explique : quand je développe, je base toutes mes gestions
d'erreur sur des exceptions, y compris les controles appliqués (valeurs
acceptées, pointeurs non NULL, retours de syscalls corrects, etc.).
En somme, tu considères qu'une erreur de frappe de la part d'un
utilisateur s'assimile à une violation d'un invariant dans le code.
Pour chaque exception levée/reçue, j'ajoute une trace (via une macro,
désactivable donc) qui résulte en une stack trace complète dans les
logs. Ca fait un peu java, mais c'est très clair. Au bout de la stack
trace (cad au niveau le plus haut de la gestion d'erreur, qui n'est pas
forcément main() ), j'affiche le message d'erreur qui est remonté ; une
manière élégante de voir d'où est venu le bug, et en plus d'où venaient
les appels à cette fonctionnalité. Et cela permet également
d'implémenter des préconditions/invariants.Personnellement, je trouve l'assert un peu brutal, et le printf trop
limitatif :)
L'assert est brutal pour une raison : il ne sert que dans le cas des
erreurs de programmation. L'assert ne peut pas déclencher si ton
raisonement sur le code était correct. C'est donc que tu ne sais plus
dans quel état se trouve le programme.
Dans ce cas-là, chaque instruction que tu exécutes est une risque. Il
faut en exécuter les moins possibles. Et donc, ne pas exécuter les
destructeurs. (En général. Comme pour tout, il y a des trade-offs, et il
y a des cas particuliers où la règle générale ne s'applique pas.)
Pourquoi complètement stopper un programme lorsque quelque chose ne se
passe pas bien?
Si ce qui ne se passe pas bien, c'est qu'on ne sait plus l'état du
programme, ni ce qu'il fait, il faut bien l'arrêter le plus vite
possible, avant qu'il ne fasse quelque chose de vraiment nuisible.
Ajoutez à cela une sauvegarde des données d'entrée lors de la gestion
d'erreur, et une compilation optimisée avec les symboles de debug dans
des fichiers séparés.
Lors d'un problème en production, il suffit généralement de regarder la
stacktrace pour voir ce qui s'est produit, et comment corriger le
problème.
Exactement. Et c'est ce que te donne l'assert (au moins sous Unix, mais
je crois qu'on peut en avoir pareil sous Windows). Avec un log des
entrées, évidemment, pour pouvoir réproduire l'erreur.
J'accorde le fait qu'il sera toujours nécessaire d'arriver à reproduire
le bug avec un test unitaire,
Disons qu'il ne faut pas faire la moindre modification dans les sources
du programme avant de pouvoir déclencher l'erreur dans un programme de
test. Qui en suite fera partie de tes tests de regression.
mais ce n'est pas toujours le cas (si une frame de niveau supérieur est
corrompue, il se peut que le programme "lache" lors d'appels un peu
plus bas, ou après plusieurs retours ...
Oui, mais quelque chose à corrompu la frame de niveau supérieur. C'est
ça l'erreur qu'il faut chercher, et c'est pour ça qu'il faut un log des
entrées, pour pouvoir récréer l'état qui a déclenché l'erreur.
Sans compter que quand une stack est foirée, printf ne fait pas
toujours son boulot comme il faut.
Exactement. Et quand la stack est corrompue, rémonter une exception ne
march pas comme il faut.
[..,]Ce que je remarque en me relisant, c'est que pour du petit bug, la
trace peut suffir, mais je ne pense pas dans tous les cas. Pour du
"bon" bug (SIGSEGV/SIGPIPE, problèmes de synchros entre threads,
freeze, etc.), le débugueur apporte un confort inégalé,
C'est justement ce genre de problèmes où le deboggueur ne marche pas.
Parce qu'il change les temps, et donc l'exécution du programme.
On Fri, 25 Dec 2009 17:20:55 -0600, BOUCNIAUX Benjamin
:quand je développe, je base toutes mes gestions d'erreur sur des
exceptions
Attention, quand tu lances une exception, pas mal de destructeurs sont
appelés. Par conséquent, lancer une exception présuppose que le
programme est dans un état assez stable pour ce faire. Si tu t'aperçois
qu'un de tes objets est dans un état incohérent, il vaut mieux arrêter
tout de suite le programme, sans appeler le destructeur de cet objet.
On Fri, 25 Dec 2009 17:20:55 -0600, BOUCNIAUX Benjamin
<boucniaux.b@polux-hosting.com>:
quand je développe, je base toutes mes gestions d'erreur sur des
exceptions
Attention, quand tu lances une exception, pas mal de destructeurs sont
appelés. Par conséquent, lancer une exception présuppose que le
programme est dans un état assez stable pour ce faire. Si tu t'aperçois
qu'un de tes objets est dans un état incohérent, il vaut mieux arrêter
tout de suite le programme, sans appeler le destructeur de cet objet.
On Fri, 25 Dec 2009 17:20:55 -0600, BOUCNIAUX Benjamin
:quand je développe, je base toutes mes gestions d'erreur sur des
exceptions
Attention, quand tu lances une exception, pas mal de destructeurs sont
appelés. Par conséquent, lancer une exception présuppose que le
programme est dans un état assez stable pour ce faire. Si tu t'aperçois
qu'un de tes objets est dans un état incohérent, il vaut mieux arrêter
tout de suite le programme, sans appeler le destructeur de cet objet.
Et en plus, cette manière de stopper violemment un programme fonctionnera
"bien" sous Unix. Sous winchiotte, en deux semaines, la station n'aura
plus de mémoire
Et en plus, cette manière de stopper violemment un programme fonctionnera
"bien" sous Unix. Sous winchiotte, en deux semaines, la station n'aura
plus de mémoire
Et en plus, cette manière de stopper violemment un programme fonctionnera
"bien" sous Unix. Sous winchiotte, en deux semaines, la station n'aura
plus de mémoire
J'explique juste que je ne fais pas d'assert.
Que tu lances une exception, fasse un assert ou retourne un code
d'erreur, voire fasse un exit(), tu agis face à une erreur, peu importe
laquelle.
La encore, c'est discutable: tu veux ouvrir une socket, mais le sd est
négatif, et tu fais un assert.
J'explique juste que je ne fais pas d'assert.
Que tu lances une exception, fasse un assert ou retourne un code
d'erreur, voire fasse un exit(), tu agis face à une erreur, peu importe
laquelle.
La encore, c'est discutable: tu veux ouvrir une socket, mais le sd est
négatif, et tu fais un assert.
J'explique juste que je ne fais pas d'assert.
Que tu lances une exception, fasse un assert ou retourne un code
d'erreur, voire fasse un exit(), tu agis face à une erreur, peu importe
laquelle.
La encore, c'est discutable: tu veux ouvrir une socket, mais le sd est
négatif, et tu fais un assert.
On Sat, 26 Dec 2009 08:00:56 -0600, BOUCNIAUX Benjamin
:Et en plus, cette manière de stopper violemment un programme
fonctionnera "bien" sous Unix. Sous winchiotte, en deux semaines, la
station n'aura plus de mémoire
J'imagine que tu parles là de Windows 3.1, voire peut-être des
bricolages qu'étaient Windows 95, 98 et ME. Heureusement, tous ceux-là
font partie du passé. Windows NT (NT4, 2000, XP, etc.) sont de vrais OS
(même si l'interface utilisateur est parfois bâclée).
On Sat, 26 Dec 2009 08:00:56 -0600, BOUCNIAUX Benjamin
<boucniaux.b@polux-hosting.com>:
Et en plus, cette manière de stopper violemment un programme
fonctionnera "bien" sous Unix. Sous winchiotte, en deux semaines, la
station n'aura plus de mémoire
J'imagine que tu parles là de Windows 3.1, voire peut-être des
bricolages qu'étaient Windows 95, 98 et ME. Heureusement, tous ceux-là
font partie du passé. Windows NT (NT4, 2000, XP, etc.) sont de vrais OS
(même si l'interface utilisateur est parfois bâclée).
On Sat, 26 Dec 2009 08:00:56 -0600, BOUCNIAUX Benjamin
:Et en plus, cette manière de stopper violemment un programme
fonctionnera "bien" sous Unix. Sous winchiotte, en deux semaines, la
station n'aura plus de mémoire
J'imagine que tu parles là de Windows 3.1, voire peut-être des
bricolages qu'étaient Windows 95, 98 et ME. Heureusement, tous ceux-là
font partie du passé. Windows NT (NT4, 2000, XP, etc.) sont de vrais OS
(même si l'interface utilisateur est parfois bâclée).
Euh pas forcément, sous XP je suis régulièrement amené à rebooter mon
poste par manque de ressources, et ce meme après avoir fermé toutes les
applications
Euh pas forcément, sous XP je suis régulièrement amené à rebooter mon
poste par manque de ressources, et ce meme après avoir fermé toutes les
applications
Euh pas forcément, sous XP je suis régulièrement amené à rebooter mon
poste par manque de ressources, et ce meme après avoir fermé toutes les
applications
On Sat, 26 Dec 2009 07:48:39 -0600, BOUCNIAUX Benjamin
:J'explique juste que je ne fais pas d'assert. Que tu lances une
exception, fasse un assert ou retourne un code d'erreur, voire fasse un
exit(), tu agis face à une erreur, peu importe laquelle.
Il y a quand même deux types d'erreurs très différents :
- Une erreur de données en entrée. Ton programme doit (au mieux) se
démerder pour en informer l'utilisateur. Pas de problème, les exceptions
sont adéquates.
- Une erreur de programmation. Ton programme ne fonctionne pas, parce
que tu as oublié un détail quelconque. Essayer de continuer à tourner
n'a aucun sens (d'autant que la pile est peut-être corrompue) ; mieux
vaut s'arrêter tout de suite.
La encore, c'est discutable: tu veux ouvrir une socket, mais le sd est
négatif, et tu fais un assert.
Ben justement, là c'est une question de données incorrectes (l'OS
t'envoie une information "socket invalide").
On Sat, 26 Dec 2009 07:48:39 -0600, BOUCNIAUX Benjamin
<boucniaux.b@polux-hosting.com>:
J'explique juste que je ne fais pas d'assert. Que tu lances une
exception, fasse un assert ou retourne un code d'erreur, voire fasse un
exit(), tu agis face à une erreur, peu importe laquelle.
Il y a quand même deux types d'erreurs très différents :
- Une erreur de données en entrée. Ton programme doit (au mieux) se
démerder pour en informer l'utilisateur. Pas de problème, les exceptions
sont adéquates.
- Une erreur de programmation. Ton programme ne fonctionne pas, parce
que tu as oublié un détail quelconque. Essayer de continuer à tourner
n'a aucun sens (d'autant que la pile est peut-être corrompue) ; mieux
vaut s'arrêter tout de suite.
La encore, c'est discutable: tu veux ouvrir une socket, mais le sd est
négatif, et tu fais un assert.
Ben justement, là c'est une question de données incorrectes (l'OS
t'envoie une information "socket invalide").
On Sat, 26 Dec 2009 07:48:39 -0600, BOUCNIAUX Benjamin
:J'explique juste que je ne fais pas d'assert. Que tu lances une
exception, fasse un assert ou retourne un code d'erreur, voire fasse un
exit(), tu agis face à une erreur, peu importe laquelle.
Il y a quand même deux types d'erreurs très différents :
- Une erreur de données en entrée. Ton programme doit (au mieux) se
démerder pour en informer l'utilisateur. Pas de problème, les exceptions
sont adéquates.
- Une erreur de programmation. Ton programme ne fonctionne pas, parce
que tu as oublié un détail quelconque. Essayer de continuer à tourner
n'a aucun sens (d'autant que la pile est peut-être corrompue) ; mieux
vaut s'arrêter tout de suite.
La encore, c'est discutable: tu veux ouvrir une socket, mais le sd est
négatif, et tu fais un assert.
Ben justement, là c'est une question de données incorrectes (l'OS
t'envoie une information "socket invalide").
On Sat, 26 Dec 2009 08:27:34 -0600, BOUCNIAUX Benjamin
:Euh pas forcément, sous XP je suis régulièrement amené à rebooter mon
poste par manque de ressources, et ce meme après avoir fermé toutes les
applications
De quelles ressources parles-tu ?
Une fois qu'un processus est tué[*], il n'occupe plus ni mémoire, ni
processeur. Les fichiers et sockets sont également libérés. À moins que
tu aies un service dysfonctionnel ?
[*] (Bien évidemment, la présence ou non d'une fenêtre à l'écran n'est
qu'un piètre indicateur de l'état d'un processus.)
On Sat, 26 Dec 2009 08:27:34 -0600, BOUCNIAUX Benjamin
<boucniaux.b@polux-hosting.com>:
Euh pas forcément, sous XP je suis régulièrement amené à rebooter mon
poste par manque de ressources, et ce meme après avoir fermé toutes les
applications
De quelles ressources parles-tu ?
Une fois qu'un processus est tué[*], il n'occupe plus ni mémoire, ni
processeur. Les fichiers et sockets sont également libérés. À moins que
tu aies un service dysfonctionnel ?
[*] (Bien évidemment, la présence ou non d'une fenêtre à l'écran n'est
qu'un piètre indicateur de l'état d'un processus.)
On Sat, 26 Dec 2009 08:27:34 -0600, BOUCNIAUX Benjamin
:Euh pas forcément, sous XP je suis régulièrement amené à rebooter mon
poste par manque de ressources, et ce meme après avoir fermé toutes les
applications
De quelles ressources parles-tu ?
Une fois qu'un processus est tué[*], il n'occupe plus ni mémoire, ni
processeur. Les fichiers et sockets sont également libérés. À moins que
tu aies un service dysfonctionnel ?
[*] (Bien évidemment, la présence ou non d'une fenêtre à l'écran n'est
qu'un piètre indicateur de l'état d'un processus.)
Le Sat, 26 Dec 2009 04:11:13 -0800, James Kanze a écrit :
> On Dec 25, 11:20 pm, BOUCNIAUX Benjamin hosting.com >
> wrote:
>> Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
>> > Le 30-11-2009, AG a écrit :
>> >> Je travaille dans une équipe de personnes pour lesquels
>> >> le débugger n'est pas l'outil qui tombe sous le sens
>> >> lorsqu'il s'agit de trouver les bugs des outils qu'ils
>> >> ont développé.
>> > Mais est-ce grave ? J'avoue ne quasiment jamais me servir
>> > de débugger. Je travaille surtout en
>> > précondition/invariant (assert) + tests unitaires, et
>> > éventuellement valgrind quand je soupsonne une corruption
>> > mémoire
>> > Et une fois le bug identifié, à grand coups de cerr +
>> > assert.
>> > D'autant que souvent, le bug n'apparait pas en mode débug ;-)
>> Pour ma part, je ne pense pas qu'une technique soit mieux
>> qu'une autre. Le tout, c'est d'avoir la capacité à trouver
>> son bug, quelque soit la manière. Partant de ce postulat,
>> pourquoi ne pas simplement utiliser l'équivalent de ces
>> différentes manières?
>> Je m'explique : quand je développe, je base toutes mes
>> gestions d'erreur sur des exceptions, y compris les
>> controles appliqués (valeurs acceptées, pointeurs non NULL,
>> retours de syscalls corrects, etc.).
> En somme, tu considères qu'une erreur de frappe de la part
> d'un utilisateur s'assimile à une violation d'un invariant
> dans le code.
J'explique juste que je ne fais pas d'assert.
Que tu lances une exception, fasse un assert ou retourne un
code d'erreur, voire fasse un exit(), tu agis face à une
erreur, peu importe laquelle.
>> Pour chaque exception levée/reçue, j'ajoute une trace (via
>> une macro, désactivable donc) qui résulte en une stack
>> trace complète dans les logs. Ca fait un peu java, mais
>> c'est très clair. Au bout de la stack trace (cad au niveau
>> le plus haut de la gestion d'erreur, qui n'est pas
>> forcément main() ), j'affiche le message d'erreur qui est
>> remonté ; une manière élégante de voir d'où est venu le
>> bug, et en plus d'où venaient les appels à cette
>> fonctionnalité. Et cela permet également d'implémenter des
>> préconditions/invariants.
>> Personnellement, je trouve l'assert un peu brutal, et le
>> printf trop limitatif :)
> L'assert est brutal pour une raison : il ne sert que dans le
> cas des erreurs de programmation. L'assert ne peut pas
> déclencher si ton raisonement sur le code était correct.
> C'est donc que tu ne sais plus dans quel état se trouve le
> programme.
> Dans ce cas-là, chaque instruction que tu exécutes est une
> risque. Il faut en exécuter les moins possibles. Et donc, ne
> pas exécuter les destructeurs. (En général. Comme pour tout,
> il y a des trade-offs, et il y a des cas particuliers où la
> règle générale ne s'applique pas.)
Et dans ce cas, on se retrouve avec des programmes sans aucune
robustesse. Je veux bien que ce soit le cas sur des systèmes
embarqués, mais la plupart des applications sur lesquelles
j'ai travaillé ne doivent pas s'arreter comme ça.
Je préfère un programme qui va remonter une erreur et
retourner dans un état initial tout seul, qu'un programme qui
va s'arreter tout seul.
En développant correctement, il est difficile d'avoir un
composant "instable". Soit on l'a dans son état stable, soit
on ne l'a pas du tout (la construction a échouée, ou lors
qu'il émet des erreurs, le composant de plus haut niveau le
réinitialise, et si rien n'est possible, à ce moment là, OK,
on envisage une résolution plus drastique. La philosophie
"j'ai une erreur, je coupe" était valable du temps des codes
assembleur, voire à la rigueur en C. En C++, il y a quand
meme assez de nouvelles fonctionnalites permettant de faire de
l'application robuste. Et c'est d'ailleurs un critère
important en termes de qualité logicielle.
Dans le cas de problèmes conséquents et qui ne feraient pas
planter l'appli (par exemple un stack overflow qui aurait
simplement corrompu des données sans écraser esp, eip, ebp
etc.), on peut considérer que l'application est dans un état
instable.
Hors, si l'assert est capable de déterminer que la donnée est
corrompue, une autre gestion d'erreur en est capable aussi.
Si c'est possible, pourquoi ne pas placer tout le code
accédant à ces données dans un try, avec une précond. "mes
données ne sont pas corrompues". Si les données sont
corrompues, le code ne sera pas exécuté, et une erreur sera
enregistrée, sans faire dérouler d'instructions à risque.
Cela permettra surtout de continuer d'autres traitements.
Encore une fois, ça dépend de la manière de coder.
>> Pourquoi complètement stopper un programme lorsque quelque
>> chose ne se passe pas bien?
> Si ce qui ne se passe pas bien, c'est qu'on ne sait plus
> l'état du programme, ni ce qu'il fait, il faut bien
> l'arrêter le plus vite possible, avant qu'il ne fasse
> quelque chose de vraiment nuisible.
La encore, c'est discutable: tu veux ouvrir une socket, mais
le sd est négatif, et tu fais un assert.
Résultat, ton appli a coupé en disant "Je ne peux pas ouvrir
la connexion", et a perdu les données à envoyer. Le fait de
lancer une exception permettra plus de souplesse par
l'exécution du code "catch", notamment la possibilité de
sauvegarder les données à envoyer pour une émission
ultérieure, et ce après un certain nombre de tentatives de
reconnexion.
Maintenant, je suis d'accord sur le fait que certaines erreurs
seront trop critiques pour etre rattrapables. Cela dit, quel
est le pourcentage de cas de ce genre d'erreur?
Si les preconditions sont bien implémentées, l'erreur ne se
produira meme pas pendant le traitement, mais avant. Et je
pense sincerement qu'il vaut mieux qu'un composant attrape une
exception et dise par exemple "Traitement impossible, en
attente du suivant", plutot que "Traitement impossible,
arret".
>> Ajoutez à cela une sauvegarde des données d'entrée lors de
>> la gestion d'erreur, et une compilation optimisée avec les
>> symboles de debug dans des fichiers séparés.
>> Lors d'un problème en production, il suffit généralement de
>> regarder la stacktrace pour voir ce qui s'est produit, et
>> comment corriger le problème.
> Exactement. Et c'est ce que te donne l'assert (au moins sous
> Unix, mais je crois qu'on peut en avoir pareil sous
> Windows). Avec un log des entrées, évidemment, pour pouvoir
> réproduire l'erreur.
Grumpf, il me semblait qu'assert affichait une trace pour la
seule frame d'où il a été lancé, mais je dois me tromper ...
Je te parle plus de stack trace du genre:
2009/10/11-16:10:58 ; WARNING : Exception received in void
tModuleLoader<tObjectType>::OpenLib() [with tObjectType = Basic*] at .. /
include/module_loader.h:332
2009/10/11-16:10:58 ; WARNING : Exception received in tObjectType
tModuleLoader<tObjectType>::oCreateObject(tParam, std::basic_string<char,
std::char_traits<char>, std::allocator<char> >) [with tParam = char,
tObjectType = Basic*] at ../include/module_loader.h:129
2009/10/11-16:10:58 ; ERROR : FATAL: Fatal exception received in int main
(int, char**) at module_loader.cpp:69 message on next line:
Exception thrown: Can't load the module A: A: cannot open shared object
file: No such file or directory
Dans ces cas là, en plus du log il faut surtout un dump du
process, ce qui sera exploitable avec un debugueur, pas avec
du printf.
>> Sans compter que quand une stack est foirée, printf ne fait
>> pas toujours son boulot comme il faut.
> Exactement. Et quand la stack est corrompue, rémonter une
> exception ne march pas comme il faut.
Tout a fait. De toute facon, quand une stack est corrompue, il
vaut mieux ne pas attendre grand chose, et utiliser le
débugueur.
> [..,]
>> Ce que je remarque en me relisant, c'est que pour du petit
>> bug, la trace peut suffir, mais je ne pense pas dans tous
>> les cas. Pour du "bon" bug (SIGSEGV/SIGPIPE, problèmes de
>> synchros entre threads, freeze, etc.), le débugueur apporte
>> un confort inégalé,
> C'est justement ce genre de problèmes où le deboggueur ne
> marche pas. Parce qu'il change les temps, et donc
> l'exécution du programme.
Sauf que le débugueur a la possibilité de s'attacher, et donc de trac er
ce qu'il se passe une fois que le problème a ou a eu lieu.
Avec les fichiers de débug séparés, on lance le meme binaire qu'en
production, et donc on se rapproche au maximum des conditions.
De toute facon, si tu fais tes tests hors production (ce qui est
normalement le cas), tu changes ton environnement, et donc
potentiellement les temps d'exécution avec.
Pour ma part, j'ai déjà repris des applications multithreadées
faites par d'autres <développeurs> (et complètement buguées),
sans débugueur ça n'aurait pas été possible. Tous les bugs
corrigés faisaient parti des catégories citées ci-dessus.
Le Sat, 26 Dec 2009 04:11:13 -0800, James Kanze a écrit :
> On Dec 25, 11:20 pm, BOUCNIAUX Benjamin <boucniau...@polux- hosting.com >
> wrote:
>> Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
>> > Le 30-11-2009, AG <hey...@gmail.com> a écrit :
>> >> Je travaille dans une équipe de personnes pour lesquels
>> >> le débugger n'est pas l'outil qui tombe sous le sens
>> >> lorsqu'il s'agit de trouver les bugs des outils qu'ils
>> >> ont développé.
>> > Mais est-ce grave ? J'avoue ne quasiment jamais me servir
>> > de débugger. Je travaille surtout en
>> > précondition/invariant (assert) + tests unitaires, et
>> > éventuellement valgrind quand je soupsonne une corruption
>> > mémoire
>> > Et une fois le bug identifié, à grand coups de cerr +
>> > assert.
>> > D'autant que souvent, le bug n'apparait pas en mode débug ;-)
>> Pour ma part, je ne pense pas qu'une technique soit mieux
>> qu'une autre. Le tout, c'est d'avoir la capacité à trouver
>> son bug, quelque soit la manière. Partant de ce postulat,
>> pourquoi ne pas simplement utiliser l'équivalent de ces
>> différentes manières?
>> Je m'explique : quand je développe, je base toutes mes
>> gestions d'erreur sur des exceptions, y compris les
>> controles appliqués (valeurs acceptées, pointeurs non NULL,
>> retours de syscalls corrects, etc.).
> En somme, tu considères qu'une erreur de frappe de la part
> d'un utilisateur s'assimile à une violation d'un invariant
> dans le code.
J'explique juste que je ne fais pas d'assert.
Que tu lances une exception, fasse un assert ou retourne un
code d'erreur, voire fasse un exit(), tu agis face à une
erreur, peu importe laquelle.
>> Pour chaque exception levée/reçue, j'ajoute une trace (via
>> une macro, désactivable donc) qui résulte en une stack
>> trace complète dans les logs. Ca fait un peu java, mais
>> c'est très clair. Au bout de la stack trace (cad au niveau
>> le plus haut de la gestion d'erreur, qui n'est pas
>> forcément main() ), j'affiche le message d'erreur qui est
>> remonté ; une manière élégante de voir d'où est venu le
>> bug, et en plus d'où venaient les appels à cette
>> fonctionnalité. Et cela permet également d'implémenter des
>> préconditions/invariants.
>> Personnellement, je trouve l'assert un peu brutal, et le
>> printf trop limitatif :)
> L'assert est brutal pour une raison : il ne sert que dans le
> cas des erreurs de programmation. L'assert ne peut pas
> déclencher si ton raisonement sur le code était correct.
> C'est donc que tu ne sais plus dans quel état se trouve le
> programme.
> Dans ce cas-là, chaque instruction que tu exécutes est une
> risque. Il faut en exécuter les moins possibles. Et donc, ne
> pas exécuter les destructeurs. (En général. Comme pour tout,
> il y a des trade-offs, et il y a des cas particuliers où la
> règle générale ne s'applique pas.)
Et dans ce cas, on se retrouve avec des programmes sans aucune
robustesse. Je veux bien que ce soit le cas sur des systèmes
embarqués, mais la plupart des applications sur lesquelles
j'ai travaillé ne doivent pas s'arreter comme ça.
Je préfère un programme qui va remonter une erreur et
retourner dans un état initial tout seul, qu'un programme qui
va s'arreter tout seul.
En développant correctement, il est difficile d'avoir un
composant "instable". Soit on l'a dans son état stable, soit
on ne l'a pas du tout (la construction a échouée, ou lors
qu'il émet des erreurs, le composant de plus haut niveau le
réinitialise, et si rien n'est possible, à ce moment là, OK,
on envisage une résolution plus drastique. La philosophie
"j'ai une erreur, je coupe" était valable du temps des codes
assembleur, voire à la rigueur en C. En C++, il y a quand
meme assez de nouvelles fonctionnalites permettant de faire de
l'application robuste. Et c'est d'ailleurs un critère
important en termes de qualité logicielle.
Dans le cas de problèmes conséquents et qui ne feraient pas
planter l'appli (par exemple un stack overflow qui aurait
simplement corrompu des données sans écraser esp, eip, ebp
etc.), on peut considérer que l'application est dans un état
instable.
Hors, si l'assert est capable de déterminer que la donnée est
corrompue, une autre gestion d'erreur en est capable aussi.
Si c'est possible, pourquoi ne pas placer tout le code
accédant à ces données dans un try, avec une précond. "mes
données ne sont pas corrompues". Si les données sont
corrompues, le code ne sera pas exécuté, et une erreur sera
enregistrée, sans faire dérouler d'instructions à risque.
Cela permettra surtout de continuer d'autres traitements.
Encore une fois, ça dépend de la manière de coder.
>> Pourquoi complètement stopper un programme lorsque quelque
>> chose ne se passe pas bien?
> Si ce qui ne se passe pas bien, c'est qu'on ne sait plus
> l'état du programme, ni ce qu'il fait, il faut bien
> l'arrêter le plus vite possible, avant qu'il ne fasse
> quelque chose de vraiment nuisible.
La encore, c'est discutable: tu veux ouvrir une socket, mais
le sd est négatif, et tu fais un assert.
Résultat, ton appli a coupé en disant "Je ne peux pas ouvrir
la connexion", et a perdu les données à envoyer. Le fait de
lancer une exception permettra plus de souplesse par
l'exécution du code "catch", notamment la possibilité de
sauvegarder les données à envoyer pour une émission
ultérieure, et ce après un certain nombre de tentatives de
reconnexion.
Maintenant, je suis d'accord sur le fait que certaines erreurs
seront trop critiques pour etre rattrapables. Cela dit, quel
est le pourcentage de cas de ce genre d'erreur?
Si les preconditions sont bien implémentées, l'erreur ne se
produira meme pas pendant le traitement, mais avant. Et je
pense sincerement qu'il vaut mieux qu'un composant attrape une
exception et dise par exemple "Traitement impossible, en
attente du suivant", plutot que "Traitement impossible,
arret".
>> Ajoutez à cela une sauvegarde des données d'entrée lors de
>> la gestion d'erreur, et une compilation optimisée avec les
>> symboles de debug dans des fichiers séparés.
>> Lors d'un problème en production, il suffit généralement de
>> regarder la stacktrace pour voir ce qui s'est produit, et
>> comment corriger le problème.
> Exactement. Et c'est ce que te donne l'assert (au moins sous
> Unix, mais je crois qu'on peut en avoir pareil sous
> Windows). Avec un log des entrées, évidemment, pour pouvoir
> réproduire l'erreur.
Grumpf, il me semblait qu'assert affichait une trace pour la
seule frame d'où il a été lancé, mais je dois me tromper ...
Je te parle plus de stack trace du genre:
2009/10/11-16:10:58 ; WARNING : Exception received in void
tModuleLoader<tObjectType>::OpenLib() [with tObjectType = Basic*] at .. /
include/module_loader.h:332
2009/10/11-16:10:58 ; WARNING : Exception received in tObjectType
tModuleLoader<tObjectType>::oCreateObject(tParam, std::basic_string<char,
std::char_traits<char>, std::allocator<char> >) [with tParam = char,
tObjectType = Basic*] at ../include/module_loader.h:129
2009/10/11-16:10:58 ; ERROR : FATAL: Fatal exception received in int main
(int, char**) at module_loader.cpp:69 message on next line:
Exception thrown: Can't load the module A: A: cannot open shared object
file: No such file or directory
Dans ces cas là, en plus du log il faut surtout un dump du
process, ce qui sera exploitable avec un debugueur, pas avec
du printf.
>> Sans compter que quand une stack est foirée, printf ne fait
>> pas toujours son boulot comme il faut.
> Exactement. Et quand la stack est corrompue, rémonter une
> exception ne march pas comme il faut.
Tout a fait. De toute facon, quand une stack est corrompue, il
vaut mieux ne pas attendre grand chose, et utiliser le
débugueur.
> [..,]
>> Ce que je remarque en me relisant, c'est que pour du petit
>> bug, la trace peut suffir, mais je ne pense pas dans tous
>> les cas. Pour du "bon" bug (SIGSEGV/SIGPIPE, problèmes de
>> synchros entre threads, freeze, etc.), le débugueur apporte
>> un confort inégalé,
> C'est justement ce genre de problèmes où le deboggueur ne
> marche pas. Parce qu'il change les temps, et donc
> l'exécution du programme.
Sauf que le débugueur a la possibilité de s'attacher, et donc de trac er
ce qu'il se passe une fois que le problème a ou a eu lieu.
Avec les fichiers de débug séparés, on lance le meme binaire qu'en
production, et donc on se rapproche au maximum des conditions.
De toute facon, si tu fais tes tests hors production (ce qui est
normalement le cas), tu changes ton environnement, et donc
potentiellement les temps d'exécution avec.
Pour ma part, j'ai déjà repris des applications multithreadées
faites par d'autres <développeurs> (et complètement buguées),
sans débugueur ça n'aurait pas été possible. Tous les bugs
corrigés faisaient parti des catégories citées ci-dessus.
Le Sat, 26 Dec 2009 04:11:13 -0800, James Kanze a écrit :
> On Dec 25, 11:20 pm, BOUCNIAUX Benjamin hosting.com >
> wrote:
>> Le Mon, 30 Nov 2009 13:27:49 +0000, Marc Boyer a écrit :
>> > Le 30-11-2009, AG a écrit :
>> >> Je travaille dans une équipe de personnes pour lesquels
>> >> le débugger n'est pas l'outil qui tombe sous le sens
>> >> lorsqu'il s'agit de trouver les bugs des outils qu'ils
>> >> ont développé.
>> > Mais est-ce grave ? J'avoue ne quasiment jamais me servir
>> > de débugger. Je travaille surtout en
>> > précondition/invariant (assert) + tests unitaires, et
>> > éventuellement valgrind quand je soupsonne une corruption
>> > mémoire
>> > Et une fois le bug identifié, à grand coups de cerr +
>> > assert.
>> > D'autant que souvent, le bug n'apparait pas en mode débug ;-)
>> Pour ma part, je ne pense pas qu'une technique soit mieux
>> qu'une autre. Le tout, c'est d'avoir la capacité à trouver
>> son bug, quelque soit la manière. Partant de ce postulat,
>> pourquoi ne pas simplement utiliser l'équivalent de ces
>> différentes manières?
>> Je m'explique : quand je développe, je base toutes mes
>> gestions d'erreur sur des exceptions, y compris les
>> controles appliqués (valeurs acceptées, pointeurs non NULL,
>> retours de syscalls corrects, etc.).
> En somme, tu considères qu'une erreur de frappe de la part
> d'un utilisateur s'assimile à une violation d'un invariant
> dans le code.
J'explique juste que je ne fais pas d'assert.
Que tu lances une exception, fasse un assert ou retourne un
code d'erreur, voire fasse un exit(), tu agis face à une
erreur, peu importe laquelle.
>> Pour chaque exception levée/reçue, j'ajoute une trace (via
>> une macro, désactivable donc) qui résulte en une stack
>> trace complète dans les logs. Ca fait un peu java, mais
>> c'est très clair. Au bout de la stack trace (cad au niveau
>> le plus haut de la gestion d'erreur, qui n'est pas
>> forcément main() ), j'affiche le message d'erreur qui est
>> remonté ; une manière élégante de voir d'où est venu le
>> bug, et en plus d'où venaient les appels à cette
>> fonctionnalité. Et cela permet également d'implémenter des
>> préconditions/invariants.
>> Personnellement, je trouve l'assert un peu brutal, et le
>> printf trop limitatif :)
> L'assert est brutal pour une raison : il ne sert que dans le
> cas des erreurs de programmation. L'assert ne peut pas
> déclencher si ton raisonement sur le code était correct.
> C'est donc que tu ne sais plus dans quel état se trouve le
> programme.
> Dans ce cas-là, chaque instruction que tu exécutes est une
> risque. Il faut en exécuter les moins possibles. Et donc, ne
> pas exécuter les destructeurs. (En général. Comme pour tout,
> il y a des trade-offs, et il y a des cas particuliers où la
> règle générale ne s'applique pas.)
Et dans ce cas, on se retrouve avec des programmes sans aucune
robustesse. Je veux bien que ce soit le cas sur des systèmes
embarqués, mais la plupart des applications sur lesquelles
j'ai travaillé ne doivent pas s'arreter comme ça.
Je préfère un programme qui va remonter une erreur et
retourner dans un état initial tout seul, qu'un programme qui
va s'arreter tout seul.
En développant correctement, il est difficile d'avoir un
composant "instable". Soit on l'a dans son état stable, soit
on ne l'a pas du tout (la construction a échouée, ou lors
qu'il émet des erreurs, le composant de plus haut niveau le
réinitialise, et si rien n'est possible, à ce moment là, OK,
on envisage une résolution plus drastique. La philosophie
"j'ai une erreur, je coupe" était valable du temps des codes
assembleur, voire à la rigueur en C. En C++, il y a quand
meme assez de nouvelles fonctionnalites permettant de faire de
l'application robuste. Et c'est d'ailleurs un critère
important en termes de qualité logicielle.
Dans le cas de problèmes conséquents et qui ne feraient pas
planter l'appli (par exemple un stack overflow qui aurait
simplement corrompu des données sans écraser esp, eip, ebp
etc.), on peut considérer que l'application est dans un état
instable.
Hors, si l'assert est capable de déterminer que la donnée est
corrompue, une autre gestion d'erreur en est capable aussi.
Si c'est possible, pourquoi ne pas placer tout le code
accédant à ces données dans un try, avec une précond. "mes
données ne sont pas corrompues". Si les données sont
corrompues, le code ne sera pas exécuté, et une erreur sera
enregistrée, sans faire dérouler d'instructions à risque.
Cela permettra surtout de continuer d'autres traitements.
Encore une fois, ça dépend de la manière de coder.
>> Pourquoi complètement stopper un programme lorsque quelque
>> chose ne se passe pas bien?
> Si ce qui ne se passe pas bien, c'est qu'on ne sait plus
> l'état du programme, ni ce qu'il fait, il faut bien
> l'arrêter le plus vite possible, avant qu'il ne fasse
> quelque chose de vraiment nuisible.
La encore, c'est discutable: tu veux ouvrir une socket, mais
le sd est négatif, et tu fais un assert.
Résultat, ton appli a coupé en disant "Je ne peux pas ouvrir
la connexion", et a perdu les données à envoyer. Le fait de
lancer une exception permettra plus de souplesse par
l'exécution du code "catch", notamment la possibilité de
sauvegarder les données à envoyer pour une émission
ultérieure, et ce après un certain nombre de tentatives de
reconnexion.
Maintenant, je suis d'accord sur le fait que certaines erreurs
seront trop critiques pour etre rattrapables. Cela dit, quel
est le pourcentage de cas de ce genre d'erreur?
Si les preconditions sont bien implémentées, l'erreur ne se
produira meme pas pendant le traitement, mais avant. Et je
pense sincerement qu'il vaut mieux qu'un composant attrape une
exception et dise par exemple "Traitement impossible, en
attente du suivant", plutot que "Traitement impossible,
arret".
>> Ajoutez à cela une sauvegarde des données d'entrée lors de
>> la gestion d'erreur, et une compilation optimisée avec les
>> symboles de debug dans des fichiers séparés.
>> Lors d'un problème en production, il suffit généralement de
>> regarder la stacktrace pour voir ce qui s'est produit, et
>> comment corriger le problème.
> Exactement. Et c'est ce que te donne l'assert (au moins sous
> Unix, mais je crois qu'on peut en avoir pareil sous
> Windows). Avec un log des entrées, évidemment, pour pouvoir
> réproduire l'erreur.
Grumpf, il me semblait qu'assert affichait une trace pour la
seule frame d'où il a été lancé, mais je dois me tromper ...
Je te parle plus de stack trace du genre:
2009/10/11-16:10:58 ; WARNING : Exception received in void
tModuleLoader<tObjectType>::OpenLib() [with tObjectType = Basic*] at .. /
include/module_loader.h:332
2009/10/11-16:10:58 ; WARNING : Exception received in tObjectType
tModuleLoader<tObjectType>::oCreateObject(tParam, std::basic_string<char,
std::char_traits<char>, std::allocator<char> >) [with tParam = char,
tObjectType = Basic*] at ../include/module_loader.h:129
2009/10/11-16:10:58 ; ERROR : FATAL: Fatal exception received in int main
(int, char**) at module_loader.cpp:69 message on next line:
Exception thrown: Can't load the module A: A: cannot open shared object
file: No such file or directory
Dans ces cas là, en plus du log il faut surtout un dump du
process, ce qui sera exploitable avec un debugueur, pas avec
du printf.
>> Sans compter que quand une stack est foirée, printf ne fait
>> pas toujours son boulot comme il faut.
> Exactement. Et quand la stack est corrompue, rémonter une
> exception ne march pas comme il faut.
Tout a fait. De toute facon, quand une stack est corrompue, il
vaut mieux ne pas attendre grand chose, et utiliser le
débugueur.
> [..,]
>> Ce que je remarque en me relisant, c'est que pour du petit
>> bug, la trace peut suffir, mais je ne pense pas dans tous
>> les cas. Pour du "bon" bug (SIGSEGV/SIGPIPE, problèmes de
>> synchros entre threads, freeze, etc.), le débugueur apporte
>> un confort inégalé,
> C'est justement ce genre de problèmes où le deboggueur ne
> marche pas. Parce qu'il change les temps, et donc
> l'exécution du programme.
Sauf que le débugueur a la possibilité de s'attacher, et donc de trac er
ce qu'il se passe une fois que le problème a ou a eu lieu.
Avec les fichiers de débug séparés, on lance le meme binaire qu'en
production, et donc on se rapproche au maximum des conditions.
De toute facon, si tu fais tes tests hors production (ce qui est
normalement le cas), tu changes ton environnement, et donc
potentiellement les temps d'exécution avec.
Pour ma part, j'ai déjà repris des applications multithreadées
faites par d'autres <développeurs> (et complètement buguées),
sans débugueur ça n'aurait pas été possible. Tous les bugs
corrigés faisaient parti des catégories citées ci-dessus.