Por que são necessários suportes para try-catch?

38

Em várias linguagens (pelo menos em Java, pense também em C #?), Você pode fazer coisas como

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

Portanto, quando tenho apenas uma declaração, não preciso adicionar um novo escopo { }. Por que não posso fazer isso com try-catch?

try
    singleStatement;
catch(Exception e)
    singleStatement;

Existe algo especial no try-catch que exige sempre um novo escopo ou algo assim? E se sim, o compilador não poderia consertar isso?

Svish
fonte
3
Nit-picking aqui: Estritamente falando para foras partes deve ser nomeado algo como initial, conditione stepcomo initialnão precisa definir uma variável e stepnão precisa ser um incremento.
Joachim Sauer #
4
como uma nota lateral D não requer chaves em torno dos blocos catch único try
catraca aberração
13
Penso que a verdadeira questão é o contrário: por que algumas construções permitem declarações "nuas"? Os aparelhos devem ser obrigatórios em todos os lugares para obter consistência.
UncleZeiv 3/11
3
@UncleZeiv - não é inconsistente se você considerar que o que se segue ifé sempre uma única declaração, e várias declarações entre chaves incluem uma única declaração. Mas eu sou um daqueles que sempre colocam o aparelho de qualquer maneira, então ...
detly
1
Eu também escrevo os aparelhos, mas gosto de não precisar escrevê-los quando tenho pontos de saída diretos como throwe return, e como @detly disse, se você considerar os aparelhos como um único grupo de instruções, não o acho. inconsistente também. Eu nunca entendi o que são esses "muitos erros de codificação" mencionados pelas pessoas. As pessoas precisam começar a prestar atenção ao que eles fazem, usar recuo adequado e ter testes de unidade: P nunca tive um problema com isso ...
Svish

Respostas:

23

IMO, eles estão incluídos em Java e C # principalmente porque já existiam em C ++. A verdadeira questão, então, é por que o C ++ é assim. De acordo com o Design e a evolução do C ++ (§16.3):

A trypalavra-chave é completamente redundante e os { }colchetes, exceto quando várias instruções são realmente usadas em um bloco de tentativa ou manipulador. Por exemplo, teria sido trivial permitir:

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

No entanto, achei tão difícil explicar que a redundância foi introduzida para salvar a equipe de suporte de usuários confusos.

Edit: Por que isso seria confuso, acho que é preciso apenas olhar para as afirmações incorretas na resposta de Tom Jeffery (e, principalmente, o número de votos recebidos) para perceber que haveria um problema. Para o analisador, isso realmente não é diferente de combinar elses com ifs - faltando chaves para forçar outro agrupamento, todas as catch cláusulas corresponderiam às mais recentes throw. Para aqueles idiomas desonestos que o incluem, as finallycláusulas fazem o mesmo. Do ponto de vista do analisador, isso dificilmente é diferente da situação atual para notar - em particular, como as gramáticas estão atualmente, não há realmente nada para agrupar as catchcláusulas - os colchetes agrupam as declarações controladas pelocatch cláusulas catch, não as próprias cláusulas catch.

Do ponto de vista de escrever um analisador, a diferença é quase pequena demais para se notar. Se começarmos com algo assim:

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

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

Então a diferença seria entre:

try_clause: 'try' statement

e:

try_clause: 'try' compound_statement

Da mesma forma, para cláusulas de captura:

catch_clause: 'catch' catch_arg statement

vs.

catch_clause: 'catch' catch_arg compound_statement

A definição de um bloco completo de tentativa / captura não precisaria ser alterada. De qualquer forma, seria algo como:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[Aqui estou usando [whatever]para indicar algo opcional, e estou deixando de fora a sintaxe por um tempo, finally_clausepois acho que isso não tem nenhuma relação com a questão.]

Mesmo se você não tentar seguir toda a definição gramatical semelhante ao Yacc, o ponto pode ser resumido com bastante facilidade: essa última declaração (começando com try_block) é aquela em que as catchcláusulas são correspondidas às trycláusulas - e permanece exatamente o mesmo se as chaves são necessárias ou não.

Para reiterar / resumir: o grupo de chaves em conjunto as declarações controladas por os catchs, mas fazer não agrupar a catchsi s. Como tal, esses aparelhos não têm absolutamente nenhum efeito sobre a decisão de qual catchvai com qual try. Para o analisador / compilador, a tarefa é igualmente fácil (ou difícil) de qualquer maneira. Apesar disso, @ resposta de Tom (e o número de up-votos é recebido) fornece ampla demonstração do fato de que tal mudança um faria usuários quase certamente Confuse.

Jerry Coffin
fonte
O OP está perguntando sobre os colchetes em torno do bloco try e catch , enquanto isso parece referenciar aqueles em torno do try (o primeiro parágrafo pode ser entendido como referência a ambos, mas o código ilustra apenas o anterior) ... esclarecer?
Shog9
@ Mr. CRR: um "bloco de captura" seria conhecido como "manipulador", sobre o qual veja a citação acima.
Jerry Coffin
3
bah, apenas editei meu comentário para remover essa ambiguidade. O que estou entendendo é que essa poderia ser uma resposta mais eficaz do que a de Tom (acima) se fosse mais longe para ilustrar como a construção sem colchetes poderia ter funcionado, mas de uma maneira confusa (o comentário de Billy sobre a resposta de Tom parece implicar que não poderia ter funcionado, o que acredito estar incorreto).
Shog9
@JerryCoffin Obrigado por expandir sua resposta: agora está muito mais claro para mim.
try return g(); catch(xxii) error("G() goofed: xxii");ainda teria sido limpo IMO
alfC
19

Em uma resposta sobre por que os colchetes são necessários para algumas construções de declaração única, mas não para outras , Eric Lippert escreveu:

Há vários lugares em que o C # requer um bloco de instruções entre chaves em vez de permitir uma declaração "nua". Eles são:

  • o corpo de um método, construtor, destruidor, acessador de propriedades, acessador de eventos ou acessador de indexador.
  • o bloco de uma tentativa, captura, finalmente, região marcada, desmarcada ou insegura.
  • o bloco de uma instrução lambda ou método anônimo
  • o bloco de uma instrução if ou loop se o bloco contiver diretamente uma declaração de variável local. (Ou seja, "enquanto (x! = 10) int y = 123;" é ilegal; você precisa preparar a declaração.)

Em cada um desses casos, seria possível criar uma gramática inequívoca (ou heurística para desambiguar uma gramática ambígua) para o recurso em que uma única declaração não abreviada é legal. Mas qual seria o objetivo? Em cada uma dessas situações, você espera ver várias instruções; declarações únicas são o caso raro e improvável. Parece que não vale a pena tornar a gramática inequívoca para esses casos muito improváveis.

Em outras palavras, era mais caro para a equipe do compilador implementá-lo do que o justificado, pelo benefício marginal que ele proporcionaria.

Robert Harvey
fonte
13

Eu acho que é para evitar outros problemas de estilo. O seguinte seria ambíguo ...

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?

Isso pode significar isso:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

Ou...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 
Tom Jefferys
fonte
24
Essa ambiguidade se aplica a se e para igualmente. A pergunta é: por que é permitido a declaração if e switch , mas não a tentativa / captura ?
Dipan Mehta 3/11
3
@Dipan fair point. Gostaria de saber se é simplesmente uma questão de Java / C # tentar ser consistente com linguagens mais antigas, como C, permitindo ifs sem suporte. Considerando que try / catch é uma construção mais recente, os projetistas de linguagem acharam aceitável romper com a tradição.
precisa
Tentei responder à compulsão pelo menos para C e C ++.
Dipan Mehta
6
@DipanMehta: porque não há várias cláusulas possíveis de pendências no ifcaso. É fácil dizer "bem, o outro se liga ao mais íntimo se" e acabar com isso. Mas para try / catch que não funciona.
Billy ONeal
2
@ Billy: Eu acho que você está encobrindo a ambiguidade potencial presente em se / se mais ... É fácil dizer "é fácil dizer" - mas é porque existe uma regra difícil para resolver a ambiguidade que, aliás, não permite certas construções sem o uso de colchetes. A resposta de Jerry implica que essa foi uma escolha consciente feita para evitar confusão, mas certamente poderia ter funcionado - assim como "funciona" para o caso / se mais.
Shog9
1

Eu acho que o principal motivo é que há muito pouco que você pode fazer em C # que precisaria de um bloco try / catch que seja apenas uma linha. (Eu não consigo pensar em nada agora no topo da minha cabeça). Você pode ter um ponto válido em termos do bloco catch, por exemplo, uma declaração de uma linha para registrar algo, mas em termos de legibilidade, faz mais sentido (pelo menos para mim) exigir {}.

Jetti
fonte