1

yop,


Je me demande comment gérer proprement les erreurs d'un programme. Evidemment, j'ai des solutions théoriques, mais elles sont toutes pénibles (sauf une, ne rien gérer grin).

Donc, les deux grandes idées qui me viennent :

1. chaque fonction renvoie ou une valeur particulière (ie NULL si on attend un pointeur) ou un booléen pour dire si elle 'na pas rencontré d'erreur. Ca devient vite monstrueux, parce que chaque appel de fonction où il se passe la moindre allocation, ou le moindre appel de lib, prend tout de suite 4 lignes, au niveau source c'est franchement moche, pénible à écrire et difficile à lire.

2. on utlise setjmp/longjmp. Pour pas trop se pourrir la vie comme dans la première méthode, on n'en fait qu'un dans main (), qui constitue donc un catchall. Problème, si A() appelle B() qui appelle C() qui ...., et que Z() fait un longjmp, tout ce qui aura commencé à être initialisé/alloué/ouvert dans les fonctions A-Y sera perdu.

3. on utilise des listes pour stocker tous les types de données ouverts. Par exemple, si mon programme utilise intensivement des textures, j'ai un objet qui a une liste de toutes mes textures, et qui sera en mesure de tout libérer en cas de souci. Mais ça fait multiplier les listes de ci et ça.


Donc, les avantages :
1 permet de ne pas égarer la moindre ressource en cas d'erreur fatale
2 permet de programmer de manière très simple, parce que basiquement, on ne vérifie rien, un un catchall s'occupe de nettoyer tout ce qu'il peut
3 permet de ne rien perdre

MAIS :
1 est dur à lire, pénible à implémenter, parce que tous les appels de fonctions peurvent échouer : gestion d'erreur tous les trois lignes
2 vous assure de perdre quelques données dans la nature en cas de problème (mais est-ce réellement grave avec un kernel moderne ??)
3 vous oblige à implémenter une multitude de petits objets (vive les "templates" en C, _Generic et autres joyeusetés au passage \o/), et au final vous vous retrouvez avec un code aussi crade qu'en 1 (tous les appels qui créent des ressources sont vérifiés)


Et vous, comment faites-vous ? Est-ce que, tout simplement, vous vous prenez pas la tête, vous codez à coup d'assert et vous faites un abort si ça déconne ici ou là ? grin
avatar
<<< Kernel Extremist©®™ >>>
Feel the power of (int16) !

2

1- top
2- #vomi#
3- chew

(mon sentiment personnel (et en fait, faire 1+3 n'est pas du luxe, mais ce n'est que mon avis))
avatar
Que cache le pays des Dieux ? - Forum Ghibli - Forum Littéraire

La fin d'un monde souillé est venue. L'oiseau blanc plane dans le ciel annonçant le début d'une longue ère de purification. Détachons-nous à jamais de notre vie dans ce monde de souffrance. Ô toi l'oiseau blanc, l'être vêtu de bleu, guide nous vers ce monde de pureté. - Sutra originel dork.

3

Han, j'ai fait mon programme en 1+3, mais ça peut amener à des redondances grin

Tu fais comment pour gérer la lisibilité, au passage, t'utilises des macros ?

Merci beaucoup de ton avis ! top
avatar
<<< Kernel Extremist©®™ >>>
Feel the power of (int16) !

4

Ouais, la solution 2 tu peux oublier direct si tu veux garder un semblant de propreté dans tes sources je pense tongue

Comme dit Xi, 1 + 3 est probablement une bonne idée. Pense aussi à atexit(), qui est bien pratique pour pouvoir quitter proprement sans trop se prendre la tête. (Ah, s'il existait un équivalent au niveau fonction en C... #rêve#)
Folco (./1) :
Est-ce que, tout simplement, vous vous prenez pas la tête, vous codez à coup d'assert et vous faites un abort si ça déconne ici ou là ? grin
Mince, comment tu as deviné ? grin
Bon, depuis un certain temps, la majorité des applis PC que je fais sont des outils que je suis le seul à utiliser ou presque, donc c'est acceptable (bien que fainéant, j'avoue). Pour les produits embarqués sur lesquels je travaille, c'est quasiment toujours soit des erreurs transitoires où il "suffit" de réessayer (problème de comm temporaire par exemple), soit des erreurs graves où il n'y a pas grand-chose à faire à part signaler (et éventuellement rebooter) - et dans ce cas, s'il n'y a pas de stockage persistant ou truc du même style, l'arrêt "propre" n'apporte rien par rapport à l'arrêt brutal, à part de la complexité supplémentaire.
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

5

Et puis un "arret propre" dans le cadre d'un OS "moderne" n'a quasiment aucun interet, vu que les zones alloue ou fichiers ouverts sont ferme par l'OS proprement.

Je parle bien des cas exceptionel, pas pendant le fonctionnement normal de l'appli.

Chercher a nettoyer inutilement au moment d'une exception qui va quitter brutalement le soft n'a peu ou prou d’intérêt.
Bien sur, faire en sorte bien sur que un fichier ouvert sur lequel on a écrit reste cohérent après un abort est important, mais ce n'est pas le même problème.
avatar
Proud to be CAKE©®™


GCC4TI importe qui a problème en Autriche, pour l'UE plus et une encore de correspours nucléaire, ce n'est pas ytre d'instérier. L'état très même contraire, toujours reconstruire un pouvoir une choyer d'aucrée de compris le plus mite de genre, ce n'est pas moins)
Stalin est l'élection de la langie.

6

Godzil: ça dépend de la durée de vie du process... Pour un démon par exempl, vaut mieux fermer proprement quand t'as plus besoin d'une ressource, mais j'ai bien vu que tu parlais des cas exceptionnels. Un cleanup explicite peut quand même servir, par exemple, pour un protocole réseau ou quand tu travailles avec des ressources distantes et qu'un timeout coté serveur n'a pas été prévu (ce n'est pas forcément un bug).
Folco (./2) :
on utlise setjmp/longjmp.

MER IL ES FOU!
Folco (./2) :
le moindre appel de lib, prend tout de suite 4 lignes, au niveau source c'est franchement moche, pénible à écrire et difficile à lire.

pas d'accord c'est peut être un peu plus long à écrire mais c'est de loin le plus propre, et pas du tout moche.

J'utilise parfois des "goto errout" pour sauter au point où on libère la ressource :

fopen()
if fail -> exit
des trucs qui peuvent foirer
si fail -> goto errout
d'autres trucs qui peuvent foirer
si fail -> goto errout
acquisition des ressources B
d'autres trucs qui peuvent foirer autrement
si fail -> goto errout2
tout est ok
(eventuellement fclose() si plus utile)
return WIN;

errout2:
liberation ressources acquises en B
ici ça continue tout seul

errout:
fclose()
return FAIL


Avant de me gotoeuler dessus la raison est d'éviter la séquence de

if(fail) {
fclose()
liberation_des_autres_ressources()
}

à répétition.

7

squalyl (./6) :
Godzil: ça dépend de la durée de vie du process... Pour un démon par exemple, vaut mieux fermer proprement quand t'as plus besoin d'une ressource, mais j'ai bien vu que tu parlais des cas exceptionnels. Un cleanup explicite peut quand même servir, par exemple, pour un protocole réseau ou quand tu travailles avec des ressources distantes et qu'un timeout coté serveur n'a pas été prévu (ce n'est pas forcément un bug).

Bien sur c'est pour ça que j'ai précisé pour un exemple d'un fichier ouvert avec des choses écrites, mais sa s'applique des que la sortie brutale d'une app peu impacter des donnée utilisateurs ou d'autres process (qu'ils soit locaux ou distant) je pensais et parlait (pour ne pas se faire chier) que dans le cas ou l'app ne peux que faire un abort violent, et que ça n'a aucune conséquence externe de laisse l'OS nettoyer (et il le ferra mieux qu'une app en train de planter)

Sinon le coup du goto est, *je pense* la meilleurs facons de faire, et le kernel linux en use, et abuse, si l'on puis dire ainsi.
int fonction(char *blah) { int ret = FAILED; if (DoingFoo() == failed) { goto exit; } char *allocated = CreateSomething(); if (AnotherThing() == failed) { goto exit_clean; } ret = OK; goto exit; exit_clean: Cleanup(allocated); exit: printf("%s: Exiting with ret=%d (allocated = %p)", __FUNC__, ret, allocated); blah = allocated; return ret; }

L'enorme avantage de cette technique, c'est le point de sortie UNIQUE, ou on a besoin de ne mettre qu'une seule fois les affichages/hook/breakpoint de debug on sais qu'on sort toujours de la.

Ensuite, certain decrient goto, et appelent au meutre si on l'utilise, mais goto est une bonne instruction utilise correctement, cad ne pas utilise goto pour remplacer un appel de fonction. La en l'occurent, goto est utilise pour les cas exceptionnel, ou pour sortir rapidement, et proprement, d'une boucle lourde.

(edit: le bug qui met des \ partout pour remplacer des retours chariots existe toujours..)
Exemple ce code du kernel:
static int nkbd_connect(struct serio *serio, struct serio_driver *drv) { struct nkbd *nkbd; struct input_dev *input_dev; int err = -ENOMEM; int i; nkbd = kzalloc(sizeof(struct nkbd), GFP_KERNEL); input_dev = input_allocate_device(); if (!nkbd || !input_dev) goto fail1; nkbd->serio = serio; nkbd->dev = input_dev; snprintf(nkbd->phys, sizeof(nkbd->phys), "%s/input0", serio->phys); memcpy(nkbd->keycode, nkbd_keycode, sizeof(nkbd->keycode)); input_dev->name = "Newton Keyboard"; input_dev->phys = nkbd->phys; input_dev->id.bustype = BUS_RS232; input_dev->id.vendor = SERIO_NEWTON; input_dev->id.product = 0x0001; input_dev->id.version = 0x0100; input_dev->dev.parent = &serio->dev; input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); input_dev->keycode = nkbd->keycode; input_dev->keycodesize = sizeof(unsigned char); input_dev->keycodemax = ARRAY_SIZE(nkbd_keycode); for (i = 0; i < 128; i++) set_bit(nkbd->keycode[i], input_dev->keybit); clear_bit(0, input_dev->keybit); serio_set_drvdata(serio, nkbd); err = serio_open(serio, drv); if (err) goto fail2; err = input_register_device(nkbd->dev); if (err) goto fail3; return 0; fail3: serio_close(serio); fail2: serio_set_drvdata(serio, NULL); fail1: input_free_device(input_dev); kfree(nkbd); return err; }

D'ailleurs la fin aurait pu être 'simplifie' en if (err) goto fail3; err = 0; goto exit; fail3: serio_close(serio); fail2: serio_set_drvdata(serio, NULL); fail1: input_free_device(input_dev); kfree(nkbd); exit: return err; }
avatar
Proud to be CAKE©®™


GCC4TI importe qui a problème en Autriche, pour l'UE plus et une encore de correspours nucléaire, ce n'est pas ytre d'instérier. L'état très même contraire, toujours reconstruire un pouvoir une choyer d'aucrée de compris le plus mite de genre, ce n'est pas moins)
Stalin est l'élection de la langie.

8

Pas besoin des goto hein, des if peuvent faire l'affaire :
BOOL maFonction() {
    FILE *in, *out, *tmp;
    BOOL success = NO;

    if (in = fopen("hello.txt", "r")) {
        if (out = fopen("helloBetter.txt", "w")) {
            if (tmp = fopen(tmpname(), "w")) {
                ...
                success = YES;
                fclose(tmp);
            }
            fclose(out);
        }
        fclose(in);
    }

    if (!success) {
        printf("On a merdouillé quelque part…\n");
    }
    return success;
}
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

9

Sauf que sur un truc simple c'est facile les If, des que tu as plus de 5 ou 6 test a faire, bonjour l'indentation...
avatar
Proud to be CAKE©®™


GCC4TI importe qui a problème en Autriche, pour l'UE plus et une encore de correspours nucléaire, ce n'est pas ytre d'instérier. L'état très même contraire, toujours reconstruire un pouvoir une choyer d'aucrée de compris le plus mite de genre, ce n'est pas moins)
Stalin est l'élection de la langie.

10

Je connais trois façons de faire ça :

- les if() imbriqués, mais ça devient vite illisible. Certains diront que si tu as plus de 5/6 étapes, il faut de toute façon splitter en plusieurs fonctions, mais je ne suis pas d'accord (pour moi, c'est contreproductif de créer des fonctions qui ne sont appelées qu'une fois et qui sont indissociables d'une autre fonction : ça éparpille le code, et comme on ne peut pas partager de variables locales on doit les passer en paramètres et c'est lourd)

- utiliser une ou des variables de contrôle (par exemple, un booléen isOK qui indique s'il faut que l'exécution continue). Ça évite les goto et les points de sortie multiples, qui sont interdits dans certaines normes, mais je trouve ça dangereux : on a vite fait d'oublier de mettre à jour ou de tester ces variables annexes.

- la solution à base de goto, que je privilégie. Ça fait hurler les intégristes, mais pour moi c'est le moins risqué et le plus lisible si c'est bien fait.

C'est pour ça que je parlais d'un équivalent de atexit() pour les fonctions, ça permettrait d'éviter tout ce bazar crade et de pouvoir faire un return() où on veut tout en étant certain que le "nettoyage" sera effectué.
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

11

Godzil (./9) :
Sauf que sur un truc simple c'est facile les If, des que tu as plus de 5 ou 6 test a faire, bonjour l'indentation...

Perso c'est ce que j'utilise et ça n'a jamais été un problème. Après pour ma part j'ai l'impression que je découpe beaucoup plus les fonctions que certains (avec des noms qui peuvent se lire comme du texte), du coup ça m'arrive rarement d'avoir tant de if imbriqués. Cela dit la solution du goto est bien aussi hein, mais plutôt à partir de 3 ou 4 options je pense, et se méfier quand le code à l'intérieur est vraiment énorme et des variables à effet de bord comme le 'err' dans ton code. Sans ça je privilégierais le if perso.
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

12

Zerosquare (./10) :
Je connais trois façons de faire ça :

- les if() imbriqués, mais ça devient vite illisible. Certains diront que si tu as plus de 5/6 étapes, il faut de toute façon splitter en plusieurs fonctions, mais je ne suis pas d'accord (pour moi, c'est contreproductif de créer des fonctions qui ne sont appelées qu'une fois et qui sont indissociables d'une autre fonction : ça éparpille le code, et comme on ne peut pas partager de variables locales on doit les passer en paramètres et c'est lourd)


pencil

- utiliser une ou des variables de contrôle (par exemple, un booléen isOK qui indique s'il faut que l'exécution continue). Ça évite les goto et les points de sortie multiples, qui sont interdits dans certaines normes, mais je trouve ça dangereux : on a vite fait d'oublier de mettre à jour ou de tester ces variables annexes.


pencil

- la solution à base de goto, que je privilégie. Ça fait hurler les intégristes, mais pour moi c'est le moins risqué et le plus lisible si c'est bien fait.


pencil

C'est pour ça que je parlais d'un équivalent de atexit() pour les fonctions, ça permettrait d'éviter tout ce bazar crade et de pouvoir faire un return() où on veut tout en étant certain que le "nettoyage" sera effectué.


Hum je me demande si avec quelques macros bien pensée on ne pourrais pas...

Brunni: Oui c'est ce que je dit, pour quelques tests, c'est peut etre plus simple un if, des que tu depasse un certain nombre dans une fonction, c'est vite le bordel..
Et aucune solution n'est mauvaise, du moment que le code est lisible, mais j'ai tendance a penser la meme chose que Zero (surtout sur les gens qui te disent il faut spliter la fonction embarrassed)
avatar
Proud to be CAKE©®™


GCC4TI importe qui a problème en Autriche, pour l'UE plus et une encore de correspours nucléaire, ce n'est pas ytre d'instérier. L'état très même contraire, toujours reconstruire un pouvoir une choyer d'aucrée de compris le plus mite de genre, ce n'est pas moins)
Stalin est l'élection de la langie.

13

./8 ah non là pour moi c'est totalement illisible, et en plus t'as besoin d'un flag pour savoir si ça a merdé ou pas !

Un autre truc qu'on fait parfois au taf c'est:
 int ret = OK;

ret = action1();

if(ret==ok) {
  ret = action2();
}

if(ret==ok) {
  ret = action3();
}

return ret;

Dans les fonctions qui n'ont pas besoin de cleanup c'est top, et quand y'en a besoin on analyse les valeurs des variables, genre if(f) fclose(f);
Pour malloc ça se passe bien encore plus simplement puisque free(NULL) fait NOP.

14

Zero: je pense qu'un funcatexit est faisable smile
avatar
Proud to be CAKE©®™


GCC4TI importe qui a problème en Autriche, pour l'UE plus et une encore de correspours nucléaire, ce n'est pas ytre d'instérier. L'état très même contraire, toujours reconstruire un pouvoir une choyer d'aucrée de compris le plus mite de genre, ce n'est pas moins)
Stalin est l'élection de la langie.

15

et un freeatreturn() est faisable avec alloca, que perso je n'utilise jamais grin

16

Tiens, je ne pensais pas que cette question soulèverait autant d'intérêt. Perso, ça me fait gamberger à fond à chaque programme...
Pour ce qui est de mon cas, on est dans une application locale qui s'occupe de données/fichiers/ressources locales qui ne sont pas utilisés par un autre. C'est donc du très simple.

Merci à tous pour vos avis en tout cas, vos restours et expériences m'intéressent grandement. smile

squalyl (./6) :
Avant de me gotoeuler dessus la raison est d'éviter la séquence de

if(fail) {
fclose()
liberation_des_autres_ressources()
}

(grin)

J'ai plusieurs manières de contourner ça, mais aucune ne me satisfait :
int main (void)
{
    if (!InitSdl ()) {
        return EXIT_FAILURE;
    }
    if (!InitFont ()) {
        DeinitAll ();
        return EXIT_FAILURE;
    }
    if (!InitRessources ()) {
        DeinitAll ();
        return EXIT_FAILURE;
    }
    if (!InitGameState ()) {
        DeinitAll ();
        return EXIT_FAILURE;
    }
    if (!GameStateEngine ()) {
        DeinitAll ();
        return EXIT_FAILURE;
    }
    DeinitAll ();

#ifdef DEBUG
    PrintGSMax ();
#endif

    return EXIT_SUCCESS;
}

void DeinitAll (void)
{
    DeinitGameState ();
    DeinitRessources ();
    DeinitFont ();
    DeinitSdl ();
}

Comme tous les deinit sont appelés, ça les oblige à avoir une variable static m_init (initialisée à false évidemment) pour savoir s'ils ont été initialisés ou non. J'aime pas.

L'autre solution, c'est ça :
if (a) {
if (b) {
if (c) {
.....
....
....
....
}
closeC();
}
closeB();
}
closeA();

Ca devient bordélique quand t'initialises plus de 10 trucs ici et là.
ps -> c'est ce que propose Brunni au ./8, et j'ai été vite débordé. C'est le plus propre à l'esprit, conceptuellement parlant, mais perso j'ai pas trouvé ça commode quand le nombre de if augmente démesurément.

Godzil -> chapeau, t'as réussi à me convaincre que goto n'est pas une instruction démonique créée par les reptiliens du NOM pour nous infiltrer, je vais essayer, ça a l'air en effet très propre si on s'entient à des règmes d'utilisation très strictes. smile

Du coup, je crayonne autant que toi le ./10 de Zerosquare. smile
avatar
<<< Kernel Extremist©®™ >>>
Feel the power of (int16) !

17

(Sinon tu peux utiliser un langage plus moderne ; c'est un conseil mais on va dire que je trolle donc je n'insiste pas)

18

J'ai essayé plusieurs fois, je prends pas ça pour un troll, mais je suis trop mauvais pour utiliser du C++ ou du Java, j'arrive pas à m'abstraire au niveau du langage, tout en restant terre à terre au niveau des données, bref, je fais de la merde et je patine dans la semoule. Mais je sais bien que les exceptions et assimilées des langages modernes servent entre autre à résoudre ce genre de problème, j'en ai bien conscience...
avatar
<<< Kernel Extremist©®™ >>>
Feel the power of (int16) !

19

./2:
Je dirai déjà que ca dépend du programme que tu veux faire:
* application en ligne de commande : genre outil pour toi ?
* ou pour d'autres ?
* les autres sont-il techniquement compétents en informatique ou plutôt des fonctionnels ?
* une application graphique ?
* quelle est la criticité de ton application ? Si une erreur n'est pas détectée, quelles sont les conséquences ? Le programme produit des données incorrectes ? Des données incorrectes sont produite mais il ne le sait pas ? Effacement de données personnelles ? Brêche de sécurité ? Intrusion informatique ? Intrusion humaine ? Un homme se blesse-t-il ? Un homme meurt-il ? Beaucoup d'hommes meurent ? Une guerre nucléaire se déclenche-t-il ?
* est-ce que ton erreur est permanente ? Passagère ?
* etc.

Tout en haut de l'échelle, il faut mettre en place une vrai stratégie de gestion des erreurs, les classer et quoi faire avec ? Ca sous-entend une gestion de tous les codes erreurs.
Tout en base de l'échelle, tu ne gères rien ou presque wink
Et au milieu bas, tu peux en gérer certains (fopen qui échoue) mais pas d'autres (fprintf qui échoue).

Pour la solution ./3: plutôt qu'un setjmp global, mieux vaut arrêter le programme dans ce cas (abort ou exit). A toi de voir si c'est embêtant ou pas de ne pas fermer proprement les connections, les fichiers, etc.

20

Godzil (./14) :
Zero: je pense qu'un funcatexit est faisable smile

Si tu n'es pas contre les gnuismes, tu as l'attribut cleanup qui fait l'affaire.
https://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html#Variable-Attributes

21

./19 Merci bien. Ca fait chier quelque part, ça veut dire que l'informatique n'est pas une science exacte, et qu'il y a un côté humain qui rentre en ligne de compte. Merde, c'était trop beau...

Bon, en fait, c'est un jeu bete et mechant, pas en reseau, rien de complique evidemment.

Mais pas principe, ça fait chier de pas vérifier les erreurs et de pas nettoyer en sortie. Le truc, c'est que pour faire bien, faut vérifier jusqu'au moindre affichage de texture, qui ne pourrait merder que si on arrachait à chaud la carte graphique. C'est chiant et inutile à faire, mais c'est pas bien de pas le faire. grin
avatar
<<< Kernel Extremist©®™ >>>
Feel the power of (int16) !

22

23

En principe pour ce genre de trucs on peut faire un wrapper (macro ou autre) qui check les codes d'erreurs de chaque retour et affiche dans la console lorsque ça ne va pas, avec un breakpoint ou quelque chose.
Je crois qu'en .NET il existe un truc pour ça d'ailleurs, quand tu as un call Win32 il check le code d'erreur et lance une exception si ça ne va pas (ce n'est pas le cas par défaut).
avatar
Highway Runners, mon jeu de racing à la Outrun qu'il est sorti le 14 décembre 2016 ! N'hésitez pas à me soutenir :)

https://itunes.apple.com/us/app/highway-runners/id964932741

24

Folco (./21) :
./19 Merci bien. Ca fait chier quelque part, ça veut dire que l'informatique n'est pas une science exacte, et qu'il y a un côté humain qui rentre en ligne de compte. Merde, c'était trop beau...
#pointnil#
Folco (./21) :
Le truc, c'est que pour faire bien, faut vérifier jusqu'au moindre affichage de texture, qui ne pourrait merder que si on arrachait à chaud la carte graphique. C'est chiant et inutile à faire, mais c'est pas bien de pas le faire. grin
Tu peux avoir des cas d'échec auxquels tu n'as pas pensé. Es-tu sûr et certain que ton affichage de texture ne peut jamais échouer ? Qu'est-ce-qui se passe par exemple si l'utilisateur fait des trucs funky pendant que ton appli tourne (genre changer de résolution, débrancher un écran à chaud, etc.) ?

Partir du principe qu'une fonction qui peut renvoyer un code d'erreur n'échouera jamais, c'est risqué, sauf si concrètement l'échec est sans conséquences (exemple : un échec de fclose() quand tu quittes le programme) ou que tu connais exactement l'implémentation de la fonction et que tu peux prouver que ça ne peut pas se produire dans ton cas d'utilisation.

En pratique on s'habitue vite à vérifier systématiquement tous les codes de retour. Et en plus de te prémunir contre les cas d'échec auxquels tu n'as pas pensé à la conception, ça permet aussi de mettre en évidence des bugs de manière anticipée.
Imagine : tu fais un malloc de n octets, avec n toujours suffisamment petit donc que tu considères que ça ne peut pas échouer (pas bien, mais bref). Si t'as un bug ailleurs qui fait que n est très grand, vérifier le code de retour de malloc() te préviendra qu'il y a un problème tout de suite, plutôt que de te retrouver avec une segmentation fault à retardement. Même dans le cas cité avant, un fclose() qui échoue, ça alerte sur le fait qu'il y a un truc pas net (cas réel : le handle passé en paramètre était pas le bon).

Après, ce n'est pas parce que tu vérifies toutes les erreurs que tu dois les gérer indépendamment et prévoir des palliatifs pour tout. À part si tu fais des trucs critiques, tu peux très bien te contenter d'avoir un gestionnaire d'erreur générique qui va fermer proprement l'appli si un truc qui n'est pas censé se produire se produit.
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

25

Si un affichage de texture échoue, ça peut aller du "il manque un sprite pendant une frame" à "l'utilisateur va devoir killer son jeu". Franchement, je me demande si ça vaut le coup de vérifier ce genre de chose. Mai bon, si tu le dis, c'est vrai que ça facilite le débogage, j'avais pas pensé à ça... Alors on va faire comme tu dis.

Maintenant, j'hésite. Macro ou pas ? Ca masque du code, j'aime pas trop sur le principe, mais c'est plus facile à lire, toute la merde syntaxique et le if qui va pas bien sont caché dans UN_NOM_DE_MACRO_EXPLICITE. A voir.
avatar
<<< Kernel Extremist©®™ >>>
Feel the power of (int16) !

26

Folco (./25) :
Si un affichage de texture échoue, ça peut aller du "il manque un sprite pendant une frame" à "l'utilisateur va devoir killer son jeu". Franchement, je me demande si ça vaut le coup de vérifier ce genre de chose. Mai bon, si tu le dis, c'est vrai que ça facilite le débogage, j'avais pas pensé à ça... Alors on va faire comme tu dis.
Tu peux par exemple juste abandonner le rendu de la frame en cours, et incrémenter un compteur. Si c'est un problème transitoire dû à un changement de résolution par exemple, ça passera inaperçu. Si tu constates que ça persiste pendant plus de X frames, tu peux mettre le jeu en pause et proposer à l'utilisateur de quitter.
Folco (./25) :
Maintenant, j'hésite. Macro ou pas ? Ca masque du code, j'aime pas trop sur le principe, mais c'est plus facile à lire, toute la merde syntaxique et le if qui va pas bien sont caché dans UN_NOM_DE_MACRO_EXPLICITE. A voir.
Je suis pour. L'alternative c'est de retaper plein de fois la même chose ou presque, ce qui pollue tes sources et augmente le risque d'introduire des erreurs évitables.

PpHd > intéressant ton truc, je connaissais pas, merci. C'est pas exactement ce que je cherche, mais ça peut être bien utile.
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo

27

Eh bien, merci beaucoup pour tous ces avis.
Aller, transcription des sources, c'est reparti grin
avatar
<<< Kernel Extremist©®™ >>>
Feel the power of (int16) !

28

c'est joli ce __attribute__((cleanup(fn))) mais je pressens toutes sortes de cochonneries avec grin

Me demandez pas, j'en sais rien précisément grin

29

Zerosquare (./24) :
En pratique on s'habitue vite à vérifier systématiquement tous les codes de retour. Et en plus de te prémunir contre les cas d'échec auxquels tu n'as pas pensé à la conception, ça permet aussi de mettre en évidence des bugs de manière anticipée. Imagine : tu fais un malloc de n octets, avec n toujours suffisamment petit donc que tu considères que ça ne peut pas échouer (pas bien, mais bref). Si t'as un bug ailleurs qui fait que n est très grand, vérifier le code de retour de malloc() te préviendra qu'il y a un problème tout de suite, plutôt que de te retrouver avec une segmentation fault à retardement. Même dans le cas cité avant, un fclose() qui échoue, ça alerte sur le fait qu'il y a un truc pas net (cas réel : le handle passé en paramètre était pas le bon).


Pour ce genre de tests, en général, je teste avec la macro assert (j'use et abuse du assert).

30

Pareil. Mais c'est pas très utilisable pour une appli finalisée, surtout si les utilisateurs sont pas des codeurs.
avatar
Zeroblog

« Tout homme porte sur l'épaule gauche un singe et, sur l'épaule droite, un perroquet. » — Jean Cocteau
« Moi je cherche plus de logique non plus. C'est surement pour cela que j'apprécie les Ataris, ils sont aussi logiques que moi ! » — GT Turbo