Je viens de vivre une expérience de dépannage assez pénible en résolvant du code ressemblant à ceci:
try {
doSomeStuff()
doMore()
} finally {
doSomeOtherStuff()
}
Le problème était difficile à résoudre car doSomeStuff () lançait une exception, ce qui a à son tour provoqué le déclenchement d'une exception par doSomeOtherStuff (). La deuxième exception (levée par le bloc finally) a été levée dans mon code, mais elle n'a pas de descripteur sur la première exception (levée à partir de doSomeStuff ()), qui était la véritable cause du problème.
Si le code l’avait dit, le problème aurait été évident:
try {
doSomeStuff()
doMore()
} catch (Exception e) {
log.error(e);
} finally {
doSomeOtherStuff()
}
Voici donc ma question:
Un bloc finally utilisé sans aucun bloc catch est-il un anti-modèle Java bien connu? (Cela semble certainement être une sous-classe pas très apparente de l'anti-pattern évidemment bien connu "Ne gobez pas les exceptions!")
En général, non, ce n'est pas un anti-modèle. Enfin, l’objectif final est de s’assurer que tout est nettoyé, qu’une exception soit levée ou non. Le point essentiel de la gestion des exceptions est que, si vous ne pouvez pas vous en occuper, vous le laissez figer à quelqu'un qui le peut, grâce à la gestion des exceptions de signalisation hors bande relativement propre. Si vous devez vous assurer que les éléments sont nettoyés si une exception est générée, mais que vous ne pouvez pas gérer correctement l'exception dans l'étendue actuelle, c'est exactement la bonne chose à faire. Vous voudrez peut-être être un peu plus prudent pour vous assurer que votre bloc final ne soit pas lancé.
Je pense que le vrai "anti-motif" ici fait quelque chose dans un bloc finally
qui peut lancer, ne pas avoir un piège.
Pas du tout.
Qu'est-ce qui ne va pas, c'est le code à l'intérieur de la fin.
Rappelez-vous que finalement, il sera toujours exécuté et qu'il est risqué (comme vous venez de le voir) de mettre quelque chose qui pourrait jeter une exception.
Il n'y a absolument rien de mal à essayer avec un finalement et pas de piège. Considérer ce qui suit:
InputStream in = null;
try {
in = new FileInputStream("file.txt");
// Do something that causes an IOException to be thrown
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
// Nothing we can do.
}
}
}
Si une exception est levée et que ce code ne sait pas comment le gérer, l'exception doit faire remonter la pile d'appels vers l'appelant. Dans ce cas, nous souhaitons toujours nettoyer le flux, je pense donc qu'il est parfaitement logique de disposer d'un bloc try sans prise.
Je pense que c’est loin d’être un anti-modèle et c’est quelque chose que je fais très souvent quand il est essentiel de libérer les ressources obtenues lors de l’exécution de la méthode.
Une chose que je fais lorsque je traite des descripteurs de fichier (pour l'écriture) consiste à vider le flux avant de le fermer à l'aide de la méthode IOUtils.closeQuietly, qui ne génère pas d'exceptions:
OutputStream os = null;
OutputStreamWriter wos = null;
try {
os = new FileOutputStream(...);
wos = new OutputStreamWriter(os);
// Lots of code
wos.flush();
os.flush();
finally {
IOUtils.closeQuietly(wos);
IOUtils.closeQuietly(os);
}
J'aime le faire de cette façon pour les raisons suivantes:
À mon avis, il est plus vrai que finally
avec un catch
indique un type de problème. Le langage de ressource est très simple:
acquire
try {
use
} finally {
release
}
En Java, vous pouvez avoir une exception à peu près n'importe où. Souvent, l’acquisition jette une exception vérifiée, la façon sensée de le gérer est de mettre un piège autour du comment. N'essayez pas de vérifications nulles hideuses.
Si vous voulez vraiment être anal, vous devriez noter qu'il y a des priorités implicites parmi les exceptions. Par exemple, ThreadDeath devrait tout englober, qu’il provienne de l’acquisition/utilisation/libération. Le traitement correct de ces priorités est inesthétique.
Par conséquent, abstenez-vous de la gestion de vos ressources avec l'idiome Execute Around.
J'utilise try/finally sous la forme suivante:
try{
Connection connection = ConnectionManager.openConnection();
try{
//work with the connection;
}finally{
if(connection != null){
connection.close();
}
}
}catch(ConnectionException connectionException){
//handle connection exception;
}
Je préfère ceci à l’essai/attrape/enfin (+ imbriqué essaie/attrape dans l’ultime). Je pense que c’est plus concis et que je ne duplique pas l’attrape (exception).
try {
doSomeStuff()
doMore()
} catch (Exception e) {
log.error(e);
} finally {
doSomeOtherStuff()
}
Ne faites pas cela non plus… vous avez juste caché plus de bogues (enfin, pas exactement cachés… mais rendu plus difficile leur gestion. Lorsque vous attrapez Exception, vous attrapez également n'importe quelle RuntimeException (comme NullPointer et ArrayIndexOutOfBounds) .
En général, attrapez les exceptions que vous devez attraper (exceptions vérifiées) et traitez les autres au moment du test. Les exceptions RuntimeExceptions sont conçues pour être utilisées pour les erreurs de programmation - et les erreurs de programmation sont des choses qui ne devraient pas se produire dans un programme correctement débogué.
try-finally
peut vous aider à réduire le code copier-coller dans le cas où une méthode comporte plusieurs instructions return
. Prenons l'exemple suivant (Android Java):
boolean doSomethingIfTableNotEmpty(SQLiteDatabase db) {
Cursor cursor = db.rawQuery("SELECT * FROM table", null);
if (cursor != null) {
try {
if (cursor.getCount() == 0) {
return false;
}
} finally {
// this will get executed even if return was executed above
cursor.close();
}
}
// database had rows, so do something...
return true;
}
S'il n'y a pas de clause finally
, vous devrez peut-être écrire cursor.close()
deux fois: juste avant return false
et également après la clause if
environnante.
Je dirais qu'un bloc d'essai sans bloc d'arrêt est un anti-motif. Dire "Ne pas avoir enfin une attrape" est un sous-ensemble de "Ne pas essayer sans une attrape".
Essayer/Enfin est un moyen de libérer correctement les ressources. Le code dans le bloc finally ne doit JAMAIS être lancé car il ne doit agir que sur les ressources ou l'état acquis AVANT l'entrée dans le bloc try.
En passant, je pense que log4J est presque un anti-modèle.
SI VOUS VOULEZ INSPECTER UN PROGRAMME EN COURS D'UTILISATION, UTILISEZ UN OUTIL D'INSPECTION APPROPRIÉ (c'est-à-dire un débogueur, un IDE ou, dans un sens extrême, un code d'octet tisserand, mais NE METTEZ PAS DE DÉCLARATION DE CONNEXION DANS TOUTES LES LIGNES!).
Dans les deux exemples que vous présentez, le premier semble correct. Le second inclut le code de l'enregistreur et introduit un bogue. Dans le deuxième exemple, vous supprimez une exception si les deux premières instructions en déclenchent une (c’est-à-dire que vous l’attrapez et vous la connectez, mais ne la revérifiez pas. C’est quelque chose que je trouve très courant dans l’utilisation de log4j et qui pose un réel problème de conception d’application. avec votre modification, vous faites échouer le programme d'une manière très difficile à gérer pour le système puisque vous avancez comme si vous n'aviez jamais eu d'exception (un peu comme VB de base sur l'erreur, reprenez construction suivante) .