Je suis surpris que personne n'a proposé cette alternative, donc, même si la question a été autour d'un moment, je vais l'ajouter dans: une bonne façon de traiter ce problème est d'utiliser des variables pour garder une trace de l'état actuel. C'est une technique qui peut être utilisée si oui ou non goto
est utilisé pour arriver au code de nettoyage. Comme toute technique de chiffrement, il a des avantages et des inconvénients, et ne pas être adapté à toutes les situations, mais si vous êtes de choisir un style, il est utile d'envisager, surtout si vous voulez éviter goto
sans terminer profondément imbriqués if
s.
L'idée de base est que, pour chaque action de nettoyage qui doivent être prises, il est une variable dont la valeur nous permet de savoir si le nettoyage doit être fait ou pas.
Je vais vous montrer l' goto
version tout d'abord, parce qu'il est plus proche du code dans la question d'origine.
int foo(int bar)
{
int return_value = 0;
int something_done = 0;
int stuff_inited = 0;
int stuff_prepared = 0;
/*
* Prepare
*/
if (do_something(bar)) {
something_done = 1;
} else {
goto cleanup;
}
if (init_stuff(bar)) {
stuff_inited = 1;
} else {
goto cleanup;
}
if (prepare_stuff(bar)) {
stufF_prepared = 1;
} else {
goto cleanup;
}
/*
* Do the thing
*/
return_value = do_the_thing(bar);
/*
* Clean up
*/
cleanup:
if (stuff_prepared) {
unprepare_stuff();
}
if (stuff_inited) {
uninit_stuff();
}
if (something_done) {
undo_something();
}
return return_value;
}
Un avantage de cette de plus certains de l'autre de ces techniques est que, si l'ordre de l'initialisation des fonctions est changé, le bon nettoyage arrivera encore - par exemple, à l'aide de l' switch
méthode décrite dans une autre réponse, si l'ordre d'initialisation des modifications, puis l' switch
doit être très soigneusement édité éviter d'essayer de nettoyer quelque chose n'a pas été initialisé en premier lieu.
Maintenant, certains pourraient faire valoir que cette méthode ajoute un tas de variables supplémentaires; en effet, dans ce cas, c'est vrai, mais dans la pratique souvent une variable existante déjà des pistes, ou peut être faite pour la piste, l'état requis. Par exemple, si l' prepare_stuff()
est en fait un appel à l' malloc()
, ou d' open()
, alors la variable contenant le pointeur retourné ou descripteur de fichier peut être utilisé, par exemple:
int fd = -1;
....
fd = open(...);
if (fd == -1) {
goto cleanup;
}
...
cleanup:
if (fd != -1) {
close(fd);
}
Maintenant, si nous avons également suivre le statut de l'erreur avec une variable, nous pouvons éviter goto
entièrement, et encore nettoyer correctement, sans avoir l'indentation qui devient plus profonde et plus profonde, plus d'initialisation nous avons besoin de:
int foo(int bar)
{
int return_value = 0;
int something_done = 0;
int stuff_inited = 0;
int stuff_prepared = 0;
int oksofar = 1;
/*
* Prepare
*/
if (oksofar) { /* NB This "if" statement is optional (it always executes) but included for consistency */
if (do_something(bar)) {
something_done = 1;
} else {
oksofar = 0;
}
}
if (oksofar) {
if (init_stuff(bar)) {
stuff_initede = 1;
} else {
oksofar = 0;
}
}
if (oksofar) {
if (prepare_stuff(bar)) {
stuff_prepared = 1;
} else {
oksofar = 0;
}
}
/*
* Do the thing
*/
if (oksofar) {
return_value = do_the_thing(bar);
}
/*
* Clean up
*/
if (stuff_prepared) {
unprepare_stuff();
}
if (stuff_inited) {
uninit_stuff();
}
if (something_done) {
undo_something();
}
return return_value;
}
Encore une fois, il est possible que les critiques de cette:
- N'ont pas tous ces "si"s nuire à la performance? Non, car dans le cas de réussite, vous devez faire tous les contrôles de toute façon (sinon, vous n'êtes pas la vérification de tous les cas d'erreur); et dans le cas d'échec de la plupart des compilateurs optimiser la séquence de défaut
if (oksofar)
des contrôles à un seul saut vers le code de nettoyage (GCC n'est certainement) - et dans tous les cas, l'erreur de cas, elle est moins critique pour les performances.
-
N'est-ce pas l'ajout d'une autre variable? Dans ce cas, oui, mais souvent, l' return_value
variable peut être utilisée pour jouer le rôle oksofar
est de jouer ici. Si vous structurez vos fonctions pour retourner des erreurs de manière cohérente, vous pouvez même éviter le deuxième if
dans chaque cas:
int return_value = 0;
if (!return_value) {
return_value = do_something(bar);
}
if (!return_value) {
return_value = init_stuff(bar);
}
if (!return_value) {
return_value = prepare_stuff(bar);
}
L'un des avantages du codage comme ça, c'est que la cohérence signifie que n'importe quel endroit où l'original programmeur a oublié de vérifier la valeur de retour colle dehors comme un pouce endolori, ce qui rend beaucoup plus facile à trouver (que l'on classe de bogues.
- C'est (encore) un style qui peut être utilisé pour résoudre ce problème. Utilisé correctement, il permet très propre, conforme au code, et, comme toute technique, dans de mauvaises mains il peut produire du code qui est de longue haleine et de confusion :-)