Les analyseurs de code statique comme Fortify "se plaignent" lorsqu'une exception peut être levée à l'intérieur d'un finally
bloc, ce qui dit cela 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 le writer
ne peut pas être fermé correctement, la writer.close()
méthode 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 s'il y avait une erreur lors de la fermeture de la 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 finally
bloc?
Réponses:
Fondamentalement, les
finally
clauses sont là pour assurer la bonne libération 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 lefinally
bloc la masquera. Il semblera que l'erreur a été provoqué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
finally
bloc.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 boucle simplement sur la liste et appelle la méthode dispose dans le
finally
bloc.la source
Ce que Travis Parks a dit est vrai que les exceptions dans le
finally
bloc consommeront toutes les valeurs de retour ou les exceptions destry...catch
blocs.Si vous utilisez Java 7, cependant, le problème peut être résolu en utilisant un bloc try-with-resources . Selon les documents, tant que votre ressource implémente
java.lang.AutoCloseable
(la plupart des écrivains / 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
À
http://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
la source
close()
. "toute exception qui se produit lors de sa fermeture sera supprimée, permettant à la valeur de retour ou à l'exception d'origine de passer" - ce n'est tout simplement pas vrai . Une exception de laclose()
méthode sera supprimée uniquement lorsqu'une autre exception sera levée du bloc try / catch. Donc, si letry
bloc ne lève aucune exception, l'exception de laclose()
méthode sera levée (et la valeur de retour ne sera pas retournée). Il peut même être capturé dans lecatch
bloc actuel .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 si 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.
la source
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 la récupération d'une erreur, avant qu'elle ne soit complètement récupérée (avant d'atteindre notrecatch
destination).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, que ce soit C avec propagation manuelle de code d'erreur, ou 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 qui
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 secondaires inversés" 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
finally
bloc. Cela va souvent être un énorme problème à moins que la journalisation ne puisse échouer . L'enregistrement 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
finally
blocs ne puisse pas être lancée à l'appelant (elle pourrait échouer, mais elle ne le lancera 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 exemple la journalisation peut être effectuée dans un autre processus ou thread 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 du même thread.Dans ce cas, nous pouvons nous en sortir avec un échec de 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 garantir véritablement la sécurité des exceptions, car même les auteurs de bibliothèques très générales tentent souvent ici et détruisent toute la sécurité des exceptions de votre application en utilisant la bibliothèque.
SomeFileWriter
Si
SomeFileWriter
peut jeter à l'intérieurclose
, alors je dirais qu'il est généralement incompatible avec la gestion des exceptions, sauf si vous n'essayez jamais de le fermer dans un contexte qui implique la récupération d'une exception existante. Si le code pour cela est hors de votre contrôle, nous pourrions être SOL mais il serait utile d'aviser les auteurs de ce problème flagrant de sécurité d'exception. Si c'est sous votre contrôle, ma principale recommandation est de veiller à ce que sa fermeture ne puisse 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 bien 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.
la source