2010-04-13 4 views
37

On m'a dit qu'il y avait un peu de surcharge dans l'utilisation du mécanisme Java try-catch. Donc, bien qu'il soit nécessaire de mettre des méthodes qui lancent une exception vérifiée dans un bloc try pour gérer l'exception possible, il est conseillé de limiter la taille du bloc try pour ne contenir que les opérations pouvant générer des exceptions.Est-ce que java essaye de bloquer les blocs aussi étroitement que possible?

Je ne suis pas si sûr que c'est une conclusion raisonnable. Considérez les deux implémentations ci-dessous d'une fonction qui traite un fichier texte spécifié.

Même s'il est vrai que le premier entraîne des frais inutiles, je le trouve beaucoup plus facile à suivre. Il est moins clair où exactement les exceptions viennent seulement de regarder les déclarations, mais les commentaires montrent clairement quelles déclarations sont responsables.

La seconde est beaucoup plus longue et compliquée que la première. En particulier, le bon idiome de lecture de ligne du premier doit être modifié pour correspondre à l'appel readLine dans un bloc try. Quelle est la meilleure pratique pour gérer les exceptions dans une fonction où plusieurs exceptions pourraient être levées dans sa définition?

Celui-ci contient tout le code de traitement dans le bloc try:

void processFile(File f) 
{ 
    try 
    { 
    // construction of FileReader can throw FileNotFoundException 
    BufferedReader in = new BufferedReader(new FileReader(f)); 

    // call of readLine can throw IOException 
    String line; 
    while ((line = in.readLine()) != null) 
    { 
     process(line); 
    } 
    } 
    catch (FileNotFoundException ex) 
    { 
    handle(ex); 
    } 
    catch (IOException ex) 
    { 
    handle(ex); 
    } 
} 

Celui-ci ne contient que les méthodes qui jettent des exceptions dans les blocs try:

void processFile(File f) 
{ 
    FileReader reader; 
    try 
    { 
    reader = new FileReader(f); 
    } 
    catch (FileNotFoundException ex) 
    { 
    handle(ex); 
    return; 
    } 

    BufferedReader in = new BufferedReader(reader); 

    String line; 
    while (true) 
    { 
    try 
    { 
     line = in.readLine(); 
    } 
    catch (IOException ex) 
    { 
     handle(ex); 
     break; 
    } 

    if (line == null) 
    { 
     break; 
    } 

    process(line); 
    } 
} 
+10

"Optimisation prématurée" est une expression utilisée pour décrire une situation où un programmeur laisse les considérations de performance affecter la conception d'un morceau de code. Cela peut entraîner une conception qui n'est pas aussi propre qu'elle aurait pu l'être ou un code incorrect, car le code est compliqué par l'optimisation et le programmeur est distrait par l'optimisation. - Wikipedia [http://en.wikipedia.org/wiki/Program_optimization#When_to_optimize] –

+2

"Nous devrions oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tous les maux. nos opportunités dans ce 3% critique. " - Knuth [http://stackoverflow.com/questions/211414/is-premature-optimization-really-the-root-of-all-evil] –

Répondre

45

La prémisse de base ici est false: la taille d'un bloc try ne fait aucune différence de performance. Les performances sont affectées par la levée des exceptions lors de l'exécution, indépendamment de la taille du bloc try. Toutefois, la réduction des blocs try peut conduire à de meilleurs programmes.

Vous pouvez intercepter des exceptions pour récupérer et continuer, ou vous pouvez les attraper simplement pour les signaler à l'appelant (ou à un humain, via une interface utilisateur). Dans le premier cas, les échecs que vous pouvez récupérer sont souvent très spécifiques, ce qui conduit à des blocs try plus petits. Dans le second cas, lorsqu'une exception est interceptée afin qu'elle puisse être encapsulée par une autre exception et renvoyée, ou affichée à l'utilisateur, les petits blocs try signifient que vous savez plus précisément quelle opération a échoué, et le plus haut contexte de niveau dans lequel cet appel a été fait. Cela vous permet de créer des rapports d'erreurs plus spécifiques.

Bien sûr, il y a & hellip; exceptions (désolé!) à ces directives. Par exemple, dans certains cas, des rapports d'erreurs très spécifiques pourraient constituer un problème de sécurité.


Il pourrait être utile de savoir quel effet un bloc try a le code compilé. Cela ne change rien aux instructions compilées! (Bien sûr, le bloc catch correspondant le fait, car c'est comme n'importe quel autre code.)

Un bloc try crée une entrée dans la table d'exception associée à la méthode. Cette table contient une gamme de compteurs d'instructions source, un type d'exception et une instruction de destination. Lorsqu'une exception est déclenchée, cette table est examinée pour voir s'il existe une entrée avec un type correspondant et une plage qui inclut l'instruction ayant généré l'exception. Si c'est le cas, l'exécution se branche sur le numéro de destination correspondant.

La chose importante à réaliser est que cette table n'est pas consultée (et n'a aucun effet sur l'exécution de la performance) à moins que ce soit nécessaire. (Négliger un peu de surcharge dans le chargement de la classe.)

+6

Exactement. Il convient également de noter, pour mémoire, que le surcoût associé à try-catch ne se produit que si une exception est levée ou si le bloc a une clause finally, comme détaillé dans la spécification de machine virtuelle http://java.sun.com/docs/ books/jvms/second_edition/html/Compiling.doc.html # 9934. – ig0774

+0

@ ig0774 Donc, si j'ajoute un bloc finally comme 'finally {in.close(); } ', alors le surcoût associé prend effet? D'après ce que je comprends de la spécification, la clause finally ajoute simplement une instruction supplémentaire au bloc try compilé pour appeler le bloc finally en tant que sous-programme avant de revenir normalement. –

+0

+1 pour la référence à la spécification, btw. –

1

il y a très très peu d'avantages pour la 2ème méthode. Après tout, si vous réussissez à ouvrir un fichier mais que vous ne pouvez pas le lire, il y a quelque chose qui ne va pas dans votre ordinateur. donc savoir que l'exception io provient de la méthode readLine() est très rarement utile. aussi comme vous le savez, différentes exceptions sont levées pour des problèmes différents de toute façon (FileNotFoundException, etc)

tant que vous l'étendez avec un bloc «logique», à savoir l'ouverture, la lecture et la fermeture d'un fichier en 1 Go, je voudrais aller avec la première méthode. il est beaucoup plus simple à lire et, surtout lorsqu'il s'agit d'E/S, les cycles de processeur utilisés par le surdébit d'essai seraient minimes, voire inexistants.

0

La seconde méthode générera une erreur de compilation que reader n'a peut-être pas été initialisée. Vous pouvez contourner cela en l'initialisant à null, mais cela signifie simplement que vous pourriez obtenir un NPE, et cela ne présente aucun avantage.

+0

Aucune erreur de ce type ne sera générée. L'instruction 'return' à la fin du bloc' catch' s'assure que le lecteur sera initialisé ou jamais lu! Bien sûr, j'ai seulement ajouté cette instruction après que le compilateur ait émis exactement l'erreur que vous décrivez. :) –

+0

Tout à fait - j'ai raté ça. Dieu merci, le compilateur est là pour comprendre ces choses pour nous. :) –

11

On m'a dit qu'il y avait une certaine surcharge dans l'utilisation du mécanisme Java try-catch.

Absolument. Et il y a aussi les frais généraux pour les appels de méthode. Mais vous ne devriez pas mettre tout votre code dans une méthode. Pour éviter l'optimisation prématurée, l'accent devrait être mis sur la facilité de lecture, l'organisation, etc. Les constructions de langage impactent rarement les performances autant que l'organisation du système et le choix des algorithmes.

Pour moi, le premier est le plus facile à lire. Mettre les blocs try autour du code spécifique qui peut générer une exception, rend, à mon avis, plus facile à lire.

1

Vous êtes susceptible de vouloir afficher un message différent pour chaque erreur et fournir des instructions à l'utilisateur, qui seront différentes selon l'endroit où l'erreur se produit. Cependant, le problème de performance auquel la plupart des gens font référence est lié à l'augmentation de l'exception, pas au bloc try lui-même. En d'autres termes, tant que vous n'avez jamais soulevé d'erreur, le bloc try n'affecte pas sensiblement les performances. Vous ne devriez pas considérer un bloc try juste comme une autre construction de contrôle de flux et déclencher une erreur pour passer au travers de votre code. C'est ce que vous voulez éviter.

+2

Juste une note supplémentaire sur votre dernier paragraphe. Si vous avez un try/catch * inside * d'une boucle, vous pouvez attraper une exception et continuer la boucle. Si votre try/catch est * en dehors * de la boucle, votre looping sera annulé. L'un ou l'autre pourrait être ce que vous voulez et influencera l'endroit où vous mettrez le try/catch. –

3

Non. La seule chose que vous devriez considérer est de savoir où vous pouvez raisonnablement gérer l'exception et quelles ressources vous devez récupérer (avec enfin).

+0

+1 pour avoir mentionné le nettoyage des ressources. Dans l'OP seule la première implémentation se prête à ajouter une seule clause 'finally' qui ferme le fichier. –

+0

+1 pour mentionner le nettoyage. Jusqu'à présent, je n'avais jamais appelé 'close' sur les objets' Reader' que j'utilisais. Honte sur moi! –

+0

Dans quelle mesure Java 7 try-with-resources change-t-il vos préférences? –

2

Ceci est l'optimisation prématurée à son pire. Ne fais pas ça. «Nous devrions oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal» - Knuth.