Revenir d'un bloc finally en Java

177

J'ai été récemment surpris de constater qu'il est possible d'avoir une instruction return dans un bloc finally en Java.

Il semble que beaucoup de gens pensent que c'est une mauvaise chose à faire comme décrit dans « Ne pas revenir dans une clause enfin ». En grattant un peu plus profondément, j'ai également trouvé que `` le retour de Java n'est pas toujours '', ce qui montre des exemples assez horribles d'autres types de contrôle de flux dans les blocs finally.

Donc, ma question est la suivante: quelqu'un peut-il me donner un exemple où une instruction return (ou un autre contrôle de flux) dans un bloc finally produit un code meilleur / plus lisible?

Matt Sheppard
la source

Réponses:

90

Les exemples que vous avez fournis sont une raison suffisante pour ne pas utiliser le contrôle de flux de finally.

Même s'il existe un exemple artificiel où c'est «mieux», considérez le développeur qui doit maintenir votre code plus tard et qui pourrait ne pas être conscient des subtilités. Ce pauvre développeur pourrait même être vous ...

Jason Cohen
la source
5
Sûr. Je suppose que je demande au cas où quelqu'un pourrait me donner un exemple vraiment convaincant du côté du bien.
Matt Sheppard
@MattSheppard dans les daos, je vais souvent enregistrer la sortie d'une requête dans un try-finally
Blake
148

Il y a des années, j'ai eu vraiment du mal à trouver un bogue causé par cela. Le code était quelque chose comme:

Object problemMethod() {
    Object rtn = null;
    try {
        rtn = somethingThatThrewAnException();
    }
    finally {
        doSomeCleanup();
        return rtn;
    }
}

Ce qui s'est passé, c'est que l'exception a été levée dans un autre code. Il était capturé et consigné et renvoyé dans la somethingThatThrewAnException()méthode. Mais l'exception n'était pas propagée par le passé problemMethod(). Après un LONG temps à regarder cela, nous l'avons finalement retracé jusqu'à la méthode de retour. La méthode de retour dans le bloc finally empêchait essentiellement l'exception qui s'est produite dans le bloc try de se propager même si elle n'était pas interceptée.

Comme d'autres l'ont dit, bien qu'il soit légal de revenir d'un bloc finally selon la spécification Java, c'est une mauvaise chose et ne devrait pas être fait.

John Meagher
la source
Où doit-on mettre le retour alors?
parsecer
@parsecer Je dirais juste après avoir appelé quelque choseThatThrewAnException () à l'intérieur du bloc try
Tiago Sippert
@parsecer, ?? Faites-le simplement de la manière habituelle, après le finalement.
Pacerier
21

javac vous avertira de retour dans enfin si vous utilisez le -Xlint: finally. À l'origine, javac n'émettait aucun avertissement - si quelque chose ne va pas avec le code, il ne devrait pas être compilé. Malheureusement, la rétrocompatibilité signifie qu'une sottise ingénieuse imprévue ne peut être interdite.

Des exceptions peuvent être lancées à partir de blocs finally, mais dans ce cas, le comportement affiché est presque certainement ce que vous voulez.

Tom Hawtin - ligne de pêche
la source
13

L'ajout de structures de contrôle et de retours aux blocs finally {} ne sont qu'un autre exemple d'abus «juste parce que vous pouvez» qui sont dispersés dans pratiquement tous les langages de développement. Jason avait raison de suggérer que cela pourrait facilement devenir un cauchemar de maintenance - les arguments contre les retours anticipés des fonctions s'appliquent davantage à ce cas de «retours tardifs».

Enfin, les blocs existent dans un seul but, pour vous permettre de ranger complètement après vous-même, peu importe ce qui s'est passé dans tout le code précédent. Principalement, il s'agit de fermer / libérer des pointeurs de fichiers, des connexions à la base de données, etc., bien que je puisse le voir s'étirer pour dire l'ajout d'un audit sur mesure.

Tout ce qui affecte le retour de la fonction doit se trouver dans le bloc try {}. Même si vous aviez une méthode par laquelle vous avez vérifié un état externe, effectué une opération qui prend du temps, puis vérifié à nouveau cet état au cas où il deviendrait invalide, vous voudriez toujours la deuxième vérification à l'intérieur du try {} - si elle était finalement à l'intérieur {} et l'opération longue a échoué, vous vérifieriez alors cet état une seconde fois inutilement.

Ian
la source
6

Un test Groovy simple:

public class Instance {

  List<String> runningThreads = new ArrayList<String>()

  void test(boolean returnInFinally) {

    println "\ntest(returnInFinally: $returnInFinally)"
    println "--------------------------------------------------------------------------"
    println "before execute"
    String result = execute(returnInFinally, false)
    println "after execute -> result: " + result
    println "--------------------------------------------------------------------------"

    println "before execute"
    try {
      result = execute(returnInFinally, true)
      println "after execute -> result: " + result
    } catch (Exception ex) {
      println "execute threw exception: " + ex.getMessage()
    }  
    println "--------------------------------------------------------------------------\n"

  }

  String execute(boolean returnInFinally, boolean throwError) {
      String thread = Thread.currentThread().getName()
      println "...execute(returnInFinally: $returnInFinally, throwError: $throwError) - thread: $thread"
      runningThreads.add(thread)
      try {
        if (throwError) {
          println "...error in execute, throw exception"
          throw new Exception("as you liked :-)")
        }
        println "...return 'OK' from execute"
        return "OK"
      } finally {
        println "...pass finally block"
        if (returnInFinally) return "return value from FINALLY ^^"
        // runningThreads.remove(thread)
      }
  }
}

Instance instance = new Instance()
instance.test(false)
instance.test(true)

Production:

test(returnInFinally: false)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: OK
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
execute threw exception: as you liked :-)
-----------------------------------------------------------------------------


test(returnInFinally: true)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------

Question:

Un point intéressant pour moi était de voir comment Groovy gère les retours implicites. Dans Groovy, il est possible de "retourner" à partir d'une méthode en laissant simplement une valeur à la fin (sans retour). Que pensez-vous qu'il se passe, si vous décommentez la ligne runningThreads.remove (..) dans l'instruction finally - est-ce que cela écrasera la valeur de retour normale ("OK") et couvrira l'exception?!

Prof. Ondino
la source
0

Revenir de l'intérieur d'un finallybloc entraînera exceptionsla perte.

Une instruction return dans un bloc finally provoquera la suppression de toute exception qui pourrait être levée dans le bloc try ou catch.

Selon la spécification du langage Java:

Si l'exécution du bloc try se termine brusquement pour une autre raison R, alors le bloc finally est exécuté, puis il y a un choix:

   If the finally block completes normally, then the try statement
   completes  abruptly for reason R.

   If the finally block completes abruptly for reason S, then the try
   statement  completes abruptly for reason S (and reason R is
   discarded).

Remarque: selon JLS 14.17 - une instruction de retour se termine toujours brusquement.

Ankur Lathi
la source