Pourquoi les crochets sont-ils nécessaires pour try-catch?

38

Dans différentes langues (au moins en Java, pensez aussi en C #?), Vous pouvez faire des choses comme

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

Ainsi, lorsque je n'ai qu'une déclaration, je n'ai pas besoin d'ajouter une nouvelle portée avec { }. Pourquoi ne puis-je pas faire cela avec try-catch?

try
    singleStatement;
catch(Exception e)
    singleStatement;

Y at-il quelque chose de spécial dans try-catch qui nécessite de toujours avoir une nouvelle portée ou quelque chose? Et si oui, le compilateur ne pourrait-il pas résoudre ce problème?

Svish
la source
3
Tatillons ici: à proprement parler pour forles parties doit être nommé quelque chose comme initial, conditionet stepcomme initialnécessité de ne pas définir une variable et stepne doit pas être une augmentation.
Joachim Sauer
4
comme note de côté D ne nécessite pas d'accolades autour de la déclaration unique try catch block
ratchet freak
13
Je pense que la vraie question est l'inverse: pourquoi certains concepts permettent-ils des déclarations "nues"? Les accolades devraient être obligatoires partout pour la cohérence.
UncleZeiv
3
@UncleZeiv - Ce n'est pas incohérent si vous considérez que ce qui suit ifest toujours une instruction unique, et que plusieurs instructions entourées d'accolades constituent une seule instruction. Mais je suis un de ceux qui mettent toujours les accolades de toute façon, alors ...
désinvolture
1
J'ai aussi tendance à écrire les accolades, mais j'aime ne pas avoir à les écrire lorsque j'ai des points de sortie directs comme throwet return, et comme @detly, si vous considérez les accolades comme un seul groupe d'énoncés, je ne le trouve pas. incohérent non plus. Je n'ai jamais compris ce que sont ces "nombreuses erreurs de codage" mentionnées par les gens. Les gens doivent commencer à faire attention à ce qu'ils font, à bien utiliser l'indentation et à passer des tests unitaires: P n'a jamais eu de problème avec ça ...
Samedi

Réponses:

23

Messieurs, ils sont inclus dans Java et C # principalement parce qu’ils existaient déjà en C ++. La vraie question est donc de savoir pourquoi C ++ est ainsi. Selon la conception et l'évolution de C ++ (§16.3):

Le trymot clé est complètement redondant, de même que les { }crochets, sauf lorsque plusieurs instructions sont réellement utilisées dans un bloc try ou un gestionnaire. Par exemple, il aurait été trivial de permettre:

int f()
{
    return g() catch(xxii) { // not C++
        error("G() goofed: xxii");
        return 22;
    };
}

Cependant, j'ai trouvé cela tellement difficile à expliquer que la redondance avait été introduite pour éviter que le personnel de support ne soit confus.

Edit: Quant à savoir pourquoi cela serait source de confusion, je pense qu'il suffit de regarder les affirmations incorrectes contenues dans la réponse de Tom Tom Jeffery (et, en particulier, le nombre de votes positifs qu'il a reçus) pour se rendre compte qu'il y aurait un problème. Pour l'analyseur, cela ne diffère vraiment pas de l'appariement de elses avec des ifaccolades s manquantes pour forcer un autre groupe, toutes les catch clauses correspondraient aux plus récentes throw. Les clauses qui en font partie comprennent les finallyclauses mal faites. Du point de vue de l'analyseur, ceci est à peine assez différent de la situation actuelle pour s'en rendre compte - en particulier, à l'heure actuelle, les grammaires ne permettent pas de regrouper les catchclauses - les crochets regroupent les instructions contrôlées par lecatch clauses, pas les clauses de capture eux-mêmes.

Du point de vue de l'écriture d'un analyseur syntaxique, la différence est presque trop minime pour être remarquée. Si nous commençons avec quelque chose comme ceci:

simple_statement: /* won't try to cover all of this */
                ;

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

Alors la différence serait entre:

try_clause: 'try' statement

et:

try_clause: 'try' compound_statement

De même, pour les clauses de capture:

catch_clause: 'catch' catch_arg statement

contre.

catch_clause: 'catch' catch_arg compound_statement

Cependant, la définition d'un bloc try / catch complet n'aurait pas besoin de changer du tout. De toute façon, ce serait quelque chose comme:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[Ici, j'utilise [whatever]pour indiquer quelque chose d’optionnel, et je laisse de côté la syntaxe pour un finally_clausepuisque je ne pense pas que cela ait une incidence sur la question.]

Même si vous n'essayez pas de suivre toute la définition de la grammaire, à la manière de Yacc, le point peut être résumé assez facilement: cette dernière instruction (commençant par try_block) est celle où les catchclauses sont mises en correspondance avec des tryclauses - et cela reste exactement la même chose. même si les accolades sont nécessaires ou non.

Pour répéter / résumer: les accolades regroupent les instructions contrôlées par le catchs, mais ne les regroupent pascatch elles-mêmes. En tant que tels, ces accolades n’ont absolument aucun effet sur le choix du catchchoix try. Pour l'analyseur / compilateur, la tâche est également facile (ou difficile) dans les deux cas. Malgré cela, la réponse @ Tom (et le nombre de votes up-il a reçu) fournit la démonstration suffisante du fait que le changement d' un tel feriez utilisateurs presque certainement embrouiller.

Jerry Coffin
la source
Le PO demande quels sont les crochets autour du bloc try et catch , alors que cela semble faire référence à ceux entourant l’ essai (le premier paragraphe pourrait être compris comme faisant référence à la fois, mais le code n’illustre que le premier) ... Pouvez-vous clarifier?
Shog9
@ Mr.CRT: un "bloc de capture" serait autrement appelé "gestionnaire", à propos duquel voir la citation ci-dessus.
Jerry Coffin
3
bah, je viens de modifier mon commentaire pour lever cette ambiguïté. Je veux en venir au fait que cela pourrait être une réponse plus efficace que celle de Tom (ci-dessus) si elle donnait plus de détails pour illustrer comment la construction sans crochets aurait pu fonctionner, mais de manière confuse (le commentaire de Billy sur la réponse de Tom semble pour impliquer que cela n'aurait pas pu fonctionner, ce qui, à mon avis, est inexact).
Shog9
@ JerryCoffin Merci d'avoir élargi votre réponse: c'est beaucoup plus clair pour moi maintenant.
try return g(); catch(xxii) error("G() goofed: xxii");ce qui aurait été soigné encore IMO
alfC
19

Dans une réponse à la question de savoir pourquoi les crochets sont obligatoires pour certaines constructions à déclaration unique et non pour d'autres , Eric Lippert a écrit:

Il y a un certain nombre d'endroits où C # nécessite un bloc d'instructions renforcé plutôt que de permettre une instruction "nue". Elles sont:

  • le corps d'une méthode, d'un constructeur, d'un destructeur, d'un accesseur de propriété, d'un accesseur d'événement ou d'un indexeur.
  • le bloc d'une zone à essayer, attraper, enfin, cochée, non contrôlée ou non sécurisée.
  • le bloc d'une instruction lambda ou méthode anonyme
  • le bloc d'une instruction if ou loop si le bloc contient directement une déclaration de variable locale. (C’est-à-dire que "while (x! = 10) int y = 123;" est illégal; vous devez vous préparer à la déclaration.)

Dans chacun de ces cas, il serait possible de proposer une grammaire non ambiguë (ou des heuristiques permettant de lever l'ambiguïté d'une grammaire ambiguë) pour la caractéristique où une seule déclaration non masquée est légale. Mais quel serait le but? Dans chacune de ces situations, vous vous attendez à voir plusieurs déclarations. les déclarations simples sont le cas rare et peu probable. Il semble que cela ne vaut pas vraiment la peine de rendre la grammaire sans ambiguïté pour ces cas très improbables.

En d’autres termes, son implémentation coûtait plus cher à l’équipe de compilation que ce qui était justifié, pour le bénéfice marginal qu’elle apporterait.

Robert Harvey
la source
13

Je pense que c'est pour éviter les problèmes de style. Ce qui suit serait ambigu ...

try
    // Do stuff
try
    // Do  more stuff
catch(MyException1 e1)
    // Handle fist exception
catch(MyException2 e2)
    // So which try does this catch belong to?
finally
    // and who does this finally block belong to?

Cela pourrait signifier ceci:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

Ou...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 
Tom Jefferys
la source
24
Cette ambiguïté vaut pour si et pour autant. La question qui se pose est la suivante: pourquoi autorise-t-il une instruction if et switch , mais pas pour try / catch ?
Dipan Mehta
3
@Dipan fair point. Je me demande s’il s’agit simplement de Java / C # qui essaie d’être cohérent avec les langages plus anciens tels que le C en autorisant des if non renforcés. Considérant que try / catch est une construction plus récente, les concepteurs de langage ont donc jugé bon de rompre avec la tradition.
Tom Jefferys
J'ai essayé de répondre à la contrainte au moins pour C et C ++.
Dipan Mehta
6
@DipanMehta: Parce qu'il n'y a pas plusieurs clauses else possibles pour l' ifaffaire. Il est facile de simplement dire "bien le reste se lie au plus profond si" et en finir avec cela. Mais pour essayer / attraper cela ne fonctionne pas.
Billy ONeal
2
@Billy: Je pense que vous passez sous silence l'ambiguïté potentielle qui existe si / sinon ... C'est facile de dire "c'est facile de dire" - mais c'est parce qu'il y a une règle difficile pour résoudre l'ambiguïté qui, accessoirement, interdit certaines constructions sans l'utilisation de crochets. La réponse de Jerry implique que c'était un choix conscient fait pour éviter toute confusion, mais cela aurait sûrement fonctionné - tout comme cela "fonctionne" pour si / sinon.
Shog9
1

Je pense que la raison principale est qu’il n’ya que très peu de choses que vous puissiez faire en C # qui aurait besoin d’un bloc try / catch qui n’est qu’une ligne. (Je n'arrive pas à penser au meilleur de ma tête en ce moment). Vous pouvez avoir un argument valable en termes de bloc catch, par exemple une instruction d'une ligne pour enregistrer quelque chose, mais en termes de lisibilité, il est plus logique (du moins pour moi) d'exiger {}.

Jetti
la source