Les analyseurs de code statique comme Fortify "se plaignent" lorsqu'une exception peut être levée à l'intérieur d'un bloc finally
, disant que Using a throw statement inside a finally block breaks the logical progression through the try-catch-finally
. Normalement, je suis d'accord avec cela. Mais récemment, je suis tombé sur ce code:
SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} catch (...) {
//exception handling
} finally {
if (writer!= null) writer.close();
}
Maintenant, si la writer
ne peut pas être fermée correctement, la méthode writer.close()
lèvera une exception. Une exception doit être levée car (très probablement) le fichier n'a pas été enregistré après l'écriture.
Je pourrais déclarer une variable supplémentaire, la définir en cas d'erreur lors de la fermeture de writer
et lever une exception après le bloc finally. Mais ce code fonctionne bien et je ne sais pas s'il faut le changer ou non.
Quels sont les inconvénients de lever une exception à l'intérieur du bloc finally
?
Fondamentalement, les clauses finally
sont là pour garantir la libération correcte d'une ressource. Cependant, si une exception est levée à l'intérieur du bloc finally, cette garantie disparaît. Pire, si votre bloc de code principal lève une exception, l'exception levée dans le bloc finally
la masquera. Il semblerait que l'erreur ait été causée par l'appel à close
, pas pour la vraie raison.
Certaines personnes suivent un modèle désagréable de gestionnaires d'exceptions imbriquées, avalant toutes les exceptions levées dans le bloc finally
.
SomeFileWriter writer = null;
try {
//init the writer
//write into the file
} finally {
if (writer!= null) {
try {
writer.close();
} catch (...) {
}
}
}
Dans les anciennes versions de Java, vous pouvez "simplifier" ce code en encapsulant les ressources dans des classes qui effectuent ce nettoyage "sûr" pour vous. Un bon ami à moi crée une liste de types anonymes, chacun fournissant la logique de nettoyage de leurs ressources. Ensuite, son code parcourt simplement la liste et appelle la méthode dispose dans le bloc finally
.
Ce que Travis Parks a dit est vrai que les exceptions dans le bloc finally
consommeront toutes les valeurs de retour ou exceptions du try...catch
blocs.
Si vous utilisez Java 7, cependant, le problème peut être résolu en utilisant un essayer avec des ressources bloquer. Selon les documents, tant que votre ressource implémente Java.lang.AutoCloseable
(la plupart des auteurs/lecteurs de bibliothèque le font maintenant), le bloc try-with-resources le fermera pour vous. L'avantage supplémentaire ici est que toute exception qui se produit lors de sa fermeture sera supprimée, permettant à la valeur de retour ou à l'exception d'origine de passer.
De
FileWriter writer = null;
try {
writer = new FileWriter("myFile.txt");
writer.write("hello");
} catch(...) {
// return/throw new exception
} finally {
writer.close(); // an exception would consume the catch block's return/exception
}
À
try (FileWriter writer = new FileWriter("myFile.txt")) {
writer.write("hello");
} catch(...) {
// return/throw new exception, always gets returned even if writer fails to close
}
http://docs.Oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
Cela va être davantage une réponse conceptuelle à la raison pour laquelle ces avertissements peuvent exister même avec des approches d'essayer avec des ressources. Ce n'est malheureusement pas non plus le type de solution facile que vous espérez atteindre.
La récupération d'erreur ne peut pas échouer
finally
modélise un flux de contrôle post-transaction qui est exécuté, que la transaction réussisse ou échoue.
Dans le cas d'échec, finally
capture la logique qui est exécutée au milieu de récupération après une erreur, avant qu'elle ne soit complètement récupérée ( avant d'atteindre notre destination catch
).
Imaginez le problème conceptuel qu'il présente pour rencontrer une erreur au milieu de la récupération d'une erreur.
Imaginez un serveur de base de données où nous essayons de valider une transaction, et il échoue à mi-chemin (par exemple, le serveur a manqué de mémoire au milieu). Maintenant, le serveur veut revenir à la transaction comme si de rien n'était. Imaginez cependant qu'il rencontre encore une autre erreur dans le processus de retour en arrière. Maintenant, nous finissons par avoir une transaction à moitié engagée dans la base de données - l'atomicité et la nature indivisible de la transaction sont désormais rompues, et l'intégrité de la base de données sera désormais compromise.
Ce problème conceptuel existe dans tous les langages traitant des erreurs, qu'il s'agisse de C avec propagation manuelle de code d'erreur, ou de C++ avec exceptions et destructeurs, ou Java avec exceptions et finally
.
finally
ne peut pas échouer dans les langages qui le fournissent de la même manière que les destructeurs ne peuvent pas échouer en C++ dans le processus de rencontrer des exceptions.
La seule façon d'éviter ce problème conceptuel et difficile est de s'assurer que le processus d'annulation des transactions et de libération des ressources au milieu ne peut pas rencontrer une exception/erreur récursive.
Donc, la seule conception sûre ici est une conception où writer.close()
ne peut pas échouer. Il existe généralement des moyens dans la conception pour éviter les scénarios où de telles choses peuvent échouer au milieu de la récupération, ce qui le rend impossible.
C'est malheureusement le seul moyen - la récupération d'erreur ne peut pas échouer. Le moyen le plus simple de garantir cela est de rendre ces types de fonctions de "libération des ressources" et "d’effets inverses" incapables d’échouer. Ce n'est pas facile - une récupération correcte des erreurs est difficile et malheureusement difficile à tester. Mais le moyen d'y parvenir est de s'assurer que les fonctions qui "détruisent", "ferment", "arrêtent", "reculent", etc. ne peuvent pas rencontrer d'erreur externe dans le processus, car ces fonctions devront souvent être appelé au milieu de la récupération d'une erreur existante.
Exemple: enregistrement
Supposons que vous souhaitiez enregistrer des éléments dans un bloc finally
. Cela va souvent être un énorme problème à moins que la journalisation ne puisse pas échouer . La journalisation peut presque certainement échouer, car il peut vouloir ajouter plus de données au fichier, et cela peut facilement trouver de nombreuses raisons d'échouer.
Donc, la solution ici est de faire en sorte que toute fonction de journalisation utilisée dans les blocs finally
ne puisse pas lancer à l'appelant (il pourrait être en mesure de échouer, mais il ne jettera pas). Comment pourrions-nous faire cela? Si votre langue permet de lancer dans le contexte de enfin à condition qu'il y ait un bloc try/catch imbriqué, ce serait une façon d'éviter de lancer à l'appelant en avalant des exceptions et en les transformant en codes d'erreur, par ex. Peut-être que la journalisation peut être effectuée dans un processus ou un thread distinct qui peut échouer séparément et en dehors d'un déroulement de pile de récupération d'erreur existant. Tant que vous pouvez communiquer avec ce processus sans la possibilité de rencontrer une erreur, cela serait également sans danger pour les exceptions, car le problème de sécurité n'existe que dans ce scénario si nous lançons récursivement à partir de même thread .
Dans ce cas, nous pouvons nous en sortir avec l'échec de la journalisation à condition qu'il ne lance pas, car ne pas se connecter et ne rien faire n'est pas la fin du monde (il n'y a pas de fuite de ressources ou de réduction des effets secondaires, par exemple).
Quoi qu'il en soit, je suis sûr que vous pouvez déjà commencer à imaginer à quel point il est incroyablement difficile de vraiment sécuriser un logiciel. Il n'est peut-être pas nécessaire de rechercher cela au maximum dans tous les logiciels, sauf les plus critiques. Mais il vaut la peine de prendre note de la façon de vraiment atteindre la sécurité d'exception, car même les auteurs de bibliothèque très polyvalents cherchent souvent ici et détruisent toute la sécurité d'exception de votre application en utilisant la bibliothèque.
SomeFileWriter
Si SomeFileWriter
peut jeter à l'intérieur close
, alors je dirais que c'est généralement incompatible avec la gestion des exceptions, sauf si vous n'essayez jamais de la fermer dans un contexte qui implique la récupération d'une exception existante. Si le code est hors de votre contrôle, nous pourrions être SOL mais il vaudrait la peine d'informer les auteurs de ce problème flagrant de sécurité d'exception. S'il est sous votre contrôle, ma principale recommandation est pour s'assurer que sa fermeture ne peut pas échouer par tous les moyens nécessaires.
Imaginez si un système d'exploitation ne parvient pas à fermer un fichier. Désormais, tout programme qui tente de fermer un fichier à l'arrêt ne pourra pas s'arrêter . Que sommes-nous censés faire maintenant, simplement maintenir l'application ouverte et dans les limbes (probablement non), simplement divulguer la ressource de fichier et ignorer le problème (peut-être d'accord si ce n'est pas si critique)? La conception la plus sûre: faites en sorte qu'il soit impossible de ne pas fermer un fichier.
Je pense que c'est quelque chose que vous devrez aborder au cas par cas. Dans certains cas, ce que l'analyseur dit est correct en ce que le code que vous avez n'est pas très bon et a besoin d'être repensé. Mais il peut y avoir d'autres cas où lancer ou même relancer pourrait être la meilleure chose. Ce n'est pas quelque chose que vous pouvez mandater.