web-dev-qa-db-fra.com

Devrait-on essayer ... attraper dans ou en dehors d'une boucle?

J'ai une boucle qui ressemble à quelque chose comme ça:

for (int i = 0; i < max; i++) {
    String myString = ...;
    float myNum = Float.parseFloat(myString);
    myFloats[i] = myNum;
}

C'est le contenu principal d'une méthode dont le seul but est de renvoyer le tableau de floats. Je veux que cette méthode retourne null s'il y a une erreur, alors je mets la boucle dans un bloc try...catch, comme ceci:

try {
    for (int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    }
} catch (NumberFormatException ex) {
    return null;
}

Mais alors j'ai aussi pensé à mettre le bloc try...catch dans la boucle, comme ceci:

for (int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
    } catch (NumberFormatException ex) {
        return null;
    }
    myFloats[i] = myNum;
}

Y a-t-il une raison, performance ou autre, pour préférer l'un par rapport à l'autre?


Edit: Le consensus semble être qu'il est plus propre de mettre la boucle à l'intérieur du try/catch, éventuellement à l'intérieur de sa propre méthode. Cependant, il y a toujours un débat sur ce qui est plus rapide. Quelqu'un peut-il tester cela et revenir avec une réponse unifiée?

168
Michael Myers

Très bien, après Jeffrey L Whitledge a déclaré qu’il n’y avait pas de différence de performance (à partir de 1997), j’ai fait un essai. J'ai couru ce petit repère:

public class Main {

    private static final int NUM_TESTS = 100;
    private static int ITERATIONS = 1000000;
    // time counters
    private static long inTime = 0L;
    private static long aroundTime = 0L;

    public static void main(String[] args) {
        for (int i = 0; i < NUM_TESTS; i++) {
            test();
            ITERATIONS += 1; // so the tests don't always return the same number
        }
        System.out.println("Inside loop: " + (inTime/1000000.0) + " ms.");
        System.out.println("Around loop: " + (aroundTime/1000000.0) + " ms.");
    }
    public static void test() {
        aroundTime += testAround();
        inTime += testIn();
    }
    public static long testIn() {
        long start = System.nanoTime();
        Integer i = tryInLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static long testAround() {
        long start = System.nanoTime();
        Integer i = tryAroundLoop();
        long ret = System.nanoTime() - start;
        System.out.println(i); // don't optimize it away
        return ret;
    }
    public static Integer tryInLoop() {
        int count = 0;
        for (int i = 0; i < ITERATIONS; i++) {
            try {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            } catch (NumberFormatException ex) {
                return null;
            }
        }
        return count;
    }
    public static Integer tryAroundLoop() {
        int count = 0;
        try {
            for (int i = 0; i < ITERATIONS; i++) {
                count = Integer.parseInt(Integer.toString(count)) + 1;
            }
            return count;
        } catch (NumberFormatException ex) {
            return null;
        }
    }
}

J'ai vérifié le bytecode résultant à l'aide de javap pour m'assurer que rien n'était en ligne.

Les résultats ont montré que, en supposant des optimisations JIT non significatives, Jeffrey est correct; il y a absolument pas de différence de performances sous Java 6, le client Sun VM} _ (je n'avais pas accès aux autres versions). La différence de temps totale est de l'ordre de quelques millisecondes sur l'ensemble du test.

Par conséquent, la seule considération est ce qui semble le plus propre. Je trouve que la deuxième manière est moche, je vais donc rester soit dans la première manière, soit dans celle de Ray Hayes .

46
Michael Myers

PERFORMANCE:

Il n'y a absolument aucune différence de performance quant à l'emplacement des structures try/catch. En interne, ils sont implémentés en tant que table de plages de codes dans une structure créée lors de l'appel de la méthode. Pendant que la méthode est en cours d'exécution, les structures try/catch sont complètement hors de propos sauf si une projection survient, puis l'emplacement de l'erreur est comparé à la table.

Voici une référence: http://www.javaworld.com/javaworld/jw-01-1997/jw-01-hood.html

La table est décrite à peu près à mi-chemin.

120
Jeffrey L Whitledge

Performance: comme Jeffrey a déclaré dans sa réponse, en Java, cela ne fait pas beaucoup de différence.

En règle générale, pour assurer la lisibilité du code, le choix de l'endroit où intercepter l'exception dépend de si vous souhaitez que la boucle continue ou non. 

Dans votre exemple, vous êtes revenu sur une exception. Dans ce cas, je mettrais le try/catch autour de la boucle. Si vous voulez simplement attraper une mauvaise valeur mais poursuivre le traitement, mettez-le à l'intérieur. 

La troisième manière: vous pouvez toujours écrire votre propre méthode statique ParseFloat et avoir la gestion des exceptions traitée dans cette méthode plutôt que votre boucle. Rendre la gestion des exceptions isolée de la boucle elle-même!

class Parsing
{
    public static Float MyParseFloat(string inputValue)
    {
        try
        {
            return Float.parseFloat(inputValue);
        }
        catch ( NumberFormatException e )
        {
            return null;
        }
    }

    // ....  your code
    for(int i = 0; i < max; i++) 
    {
        String myString = ...;
        Float myNum = Parsing.MyParseFloat(myString);
        if ( myNum == null ) return;
        myFloats[i] = (float) myNum;
    }
}
68
Ray Hayes

Bien que les performances puissent être identiques et que l'apparence soit meilleure soit très subjective, il existe encore une très grande différence de fonctionnalités. Prenons l'exemple suivant:

Integer j = 0;
    try {
        while (true) {
            ++j;

            if (j == 20) { throw new Exception(); }
            if (j%4 == 0) { System.out.println(j); }
            if (j == 40) { break; }
        }
    } catch (Exception e) {
        System.out.println("in catch block");
    }

La boucle while se trouve à l'intérieur du bloc try catch, la variable 'j' est incrémentée jusqu'à atteindre 40, imprimée lorsque j mod 4 vaut zéro et une exception est levée lorsque j atteint 20.

Avant tout détail, voici l'autre exemple:

Integer i = 0;
    while (true) {
        try {
            ++i;

            if (i == 20) { throw new Exception(); }
            if (i%4 == 0) { System.out.println(i); }
            if (i == 40) { break; }

        } catch (Exception e) { System.out.println("in catch block"); }
    }

Même logique que ci-dessus, la seule différence est que le bloc try/catch est maintenant dans la boucle while. 

Voici la sortie (en try/catch):

4
8
12 
16
in catch block

Et l'autre sortie (try/catch in while):

4
8
12
16
in catch block
24
28
32
36
40

Là vous avez une différence assez importante: 

pendant que vous essayez/attrapez des pauses hors de la boucle

try/catch in pendant que la boucle reste active

13
seBaka28

Je suis d'accord avec tous les messages de performance et de lisibilité. Cependant, il y a des cas où cela compte vraiment. Quelques personnes ont mentionné cela, mais il serait peut-être plus facile de voir avec des exemples.

Considérons cet exemple légèrement modifié:

public static void main(String[] args) {
    String[] myNumberStrings = new String[] {"1.2345", "asdf", "2.3456"};
    ArrayList asNumbers = parseAll(myNumberStrings);
}

public static ArrayList parseAll(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        myFloats.add(new Float(numberStrings[i]));
    }
    return myFloats;
}

Si vous voulez que la méthode parseAll () renvoie null s'il y a des erreurs (comme dans l'exemple d'origine), vous devriez placer le try/catch à l'extérieur comme ceci:

public static ArrayList parseAll1(String[] numberStrings){
    ArrayList myFloats = new ArrayList();
    try{
        for(int i = 0; i < numberStrings.length; i++){
            myFloats.add(new Float(numberStrings[i]));
        }
    } catch (NumberFormatException nfe){
        //fail on any error
        return null;
    }
    return myFloats;
}

En réalité, vous devriez probablement renvoyer une erreur ici au lieu de null, et en général, je n'aime pas les retours multiples, mais vous voyez l'idée.

D'un autre côté, si vous voulez que les problèmes soient ignorés et que vous analysiez toutes les chaînes qu'il peut, vous pouvez placer la commande try/catch à l'intérieur de la boucle comme suit:

public static ArrayList parseAll2(String[] numberStrings){
    ArrayList myFloats = new ArrayList();

    for(int i = 0; i < numberStrings.length; i++){
        try{
            myFloats.add(new Float(numberStrings[i]));
        } catch (NumberFormatException nfe){
            //don't add just this one
        }
    }

    return myFloats;
}
13
Matt N

Comme déjà mentionné, la performance est la même. Cependant, l'expérience utilisateur n'est pas nécessairement identique. Dans le premier cas, vous échouerez rapidement (c'est-à-dire après la première erreur). Toutefois, si vous placez le bloc try/catch dans la boucle, vous pouvez capturer toutes les erreurs qui seraient créées pour un appel donné à la méthode. Lors de l'analyse d'un tableau de valeurs à partir de chaînes contenant des erreurs de formatage, il est tout à fait possible que vous souhaitiez pouvoir présenter toutes les erreurs à l'utilisateur afin qu'il n'ait pas besoin d'essayer de les corriger une par une. .

5
user19810

Si le tout ou rien échoue, le premier format prend tout son sens. Si vous voulez pouvoir traiter/renvoyer tous les éléments non défaillants, vous devez utiliser le second formulaire. Ce sont mes critères de base pour choisir entre les méthodes. Personnellement, si c'est tout ou rien, je n'utiliserais pas le deuxième formulaire.

4
Joe Skora

Tant que vous êtes conscient de ce que vous devez accomplir dans la boucle, vous pouvez placer la capture d'essai en dehors de la boucle. Mais il est important de comprendre que la boucle se terminera dès que l'exception se produira et que cela ne sera pas toujours ce que vous voudrez. C'est en fait une erreur très courante dans les logiciels basés sur Java. Les utilisateurs doivent traiter un certain nombre d'éléments, par exemple vider une file d'attente, et s'appuyer faussement sur une instruction externe try/catch gérant toutes les exceptions possibles. Ils pourraient également ne gérer qu'une exception spécifique à l'intérieur de la boucle et ne pas s'attendre à ce qu'une autre exception se produise . Ensuite, si une exception survient qui n'est pas gérée à l'intérieur de la boucle, la boucle sera "pré-lancée", elle se terminera éventuellement prématurément et l'instruction de capture externe gère l'exception.

Si la boucle avait pour rôle dans la vie de vider une file d'attente, cette boucle pourrait très probablement se terminer avant que cette file d'attente ne soit réellement vidée. Faute très commune.

Dans vos exemples, il n'y a pas de différence fonctionnelle. Je trouve votre premier exemple plus lisible.

2
Jamie

Vous devriez préférer la version externe à la version interne. Ceci est juste une version spécifique de la règle, déplacez tout élément en dehors de la boucle que vous pouvez déplacer en dehors de la boucle. En fonction du compilateur IL et du compilateur JIT, vos deux versions peuvent ou non se retrouver avec des caractéristiques de performances différentes.

Sur une autre note, vous devriez probablement regarder float.TryParse ou Convert.ToFloat.

2
Orion Adrian

Si vous mettez le try/catch dans la boucle, vous continuerez à boucler après une exception. Si vous le mettez en dehors de la boucle, vous vous arrêtez dès qu'une exception est levée.

2
Joel Coehoorn

Mon point de vue serait que les blocs try/catch sont nécessaires pour assurer une gestion correcte des exceptions, mais la création de tels blocs a des implications en termes de performances. Comme les boucles contiennent des calculs répétitifs intensifs, il est déconseillé de placer des blocs try/catch dans les boucles. De plus, il semble que là où cette condition est remplie, c'est souvent "Exception" ou "RuntimeException" qui est intercepté. RuntimeException étant pris dans le code doit être évité. Encore une fois, si vous travaillez dans une grande entreprise, il est essentiel de consigner correctement cette exception ou d'empêcher qu'elle ne se produise. Le point entier de cette description est PLEASE AVOID USING TRY-CATCH BLOCKS IN LOOPS

1
surendrapanday

Si c'est à l'intérieur, vous gagnerez le temps système de la structure try/catch N fois, par opposition à une seule fois à l'extérieur.


Chaque fois qu'une structure Try/Catch est appelée, elle ajoute une surcharge à l'exécution de la méthode. Juste le peu de tiques de mémoire et de processeur nécessaires pour gérer la structure. Si vous exécutez une boucle 100 fois, et par hypothèse, supposons que le coût est d'un tick par appel try/catch, le fait d'essayer le programme Try/Catch dans la boucle vous coûte 100 ticks, au lieu de 1 tick si en dehors de la boucle.

1
Stephen Wrighton

la configuration d'un cadre de pile spécial pour try/catch ajoute un temps système supplémentaire, mais la machine virtuelle Java peut éventuellement détecter le fait que vous retournez et l'optimiser.

en fonction du nombre d'itérations, la différence de performance sera probablement négligeable.

Cependant, je suis d'accord avec les autres pour dire que le fait de le placer hors de la boucle donne à la boucle un aspect plus net.

S'il est possible que vous souhaitiez poursuivre le traitement plutôt que de quitter s'il existe un nombre non valide, vous voudriez que le code soit dans la boucle.

1
Matt

Le point essentiel des exceptions est d'encourager le premier style: laisser le traitement des erreurs être consolidé et traité une fois, pas immédiatement sur tous les sites d'erreur possibles.

1
wnoise

J'aime ajouter mon propre 0.02c à propos de deux considérations contradictoires lorsque l'on examine le problème général du positionnement de la gestion des exceptions:

  1. La responsabilité "plus large" du bloc try-catch (c’est-à-dire en dehors de la boucle dans votre cas) signifie que lorsque vous modifiez le code ultérieurement, vous pouvez ajouter par erreur une ligne gérée par votre bloc catch existant; peut-être involontairement. Dans votre cas, cela est moins probable car vous attrapez explicitement un NumberFormatException

  2. Plus la responsabilité du bloc try-catch est "étroite", plus la refactorisation devient difficile. En particulier lorsque (comme dans votre cas) vous exécutez une instruction "non locale" à l'intérieur du bloc catch (l'instruction return null). 

1
oxbow_lakes

Cela dépend de la gestion des échecs. Si vous voulez juste ignorer les éléments d'erreur, essayez à l'intérieur:

for(int i = 0; i < max; i++) {
    String myString = ...;
    try {
        float myNum = Float.parseFloat(myString);
        myFloats[i] = myNum;
    } catch (NumberFormatException ex) {
        --i;
    }
}

Dans tous les autres cas, je préférerais essayer à l'extérieur. Le code est plus lisible, plus propre. Peut-être serait-il préférable de lancer une exception IllegalArgumentException dans le cas d'erreur si la valeur null est renvoyée.

1
Arne Burmeister

mets le dedans. Vous pouvez continuer le traitement (si vous le souhaitez) ou vous pouvez générer une exception utile qui indique au client la valeur de myString et l'index du tableau contenant la valeur incorrecte. Je pense que NumberFormatException vous indiquera déjà la mauvaise valeur, mais le principe est de placer toutes les données utiles dans les exceptions que vous lancez. Pensez à ce qui pourrait vous intéresser dans le débogueur à ce stade du programme. 

Considérer: 

try {
   // parse
} catch (NumberFormatException nfe){
   throw new RuntimeException("Could not parse as a Float: [" + myString + 
                              "] found at index: " + i, nfe);
} 

En cas de besoin, vous apprécierez vraiment une exception comme celle-ci, qui contient le plus d’informations possible.

1
Kyle Dyer

Je vais mettre mes 0,02 $. Parfois, vous finissez par avoir besoin d'ajouter un "enfin" plus tard dans votre code (car qui a déjà écrit son code parfaitement la première fois?). Dans ces cas, il est tout à fait logique d’avoir l’essai/attrape en dehors de la boucle. Par exemple:

try {
    for(int i = 0; i < max; i++) {
        String myString = ...;
        float myNum = Float.parseFloat(myString);
        dbConnection.update("MY_FLOATS","INDEX",i,"VALUE",myNum);
    }
} catch (NumberFormatException ex) {
    return null;
} finally {
    dbConnection.release();  // Always release DB connection, even if transaction fails.
}

Parce que si vous obtenez une erreur ou non, vous souhaitez uniquement libérer votre connexion à la base de données (ou choisir votre type préféré de ressource ...) une fois.

1
Ogre Psalm33

Un autre aspect non mentionné ci-dessus est le fait que chaque try-catch a certains impact sur la pile, ce qui peut avoir des implications pour les méthodes récursives.

Si la méthode "outer ()" appelle la méthode "inner ()" (qui peut s'appeler de manière récursive), essayez de localiser la méthode try-catch in "outer ()" si possible. Un exemple simple de "crash de pile" que nous utilisons dans une classe de performance échoue à environ 6 400 images lorsque try-catch est dans la méthode interne et à environ 11 600 quand il est dans la méthode externe.

Dans le monde réel, cela peut poser problème si vous utilisez le modèle Composite et que vous avez des structures imbriquées complexes et volumineuses.

1
Jeff Hill

Si vous voulez intercepter Exception à chaque itération ou vérifier à quelle itération Exception est levée et intercepter toutes les Exceptions dans une itération, placez essayer ... attraper dans la boucle. Cela ne cassera pas la boucle si une exception se produit et vous pouvez intercepter chaque exception à chaque itération de la boucle.

Si vous souhaitez interrompre la boucle et examiner l'exception à chaque lancement, utilisez try ... catch out of the loop. Cela cassera la boucle et exécutera les instructions après la capture (le cas échéant).

Tout dépend de vos besoins. Je préfère utiliser try ... catch dans la boucle lors du déploiement, car si Exception se produit, les résultats ne sont pas ambigus et la boucle ne se cassera pas et ne sera pas exécutée complètement.

0
Juned Khan Momin