¿Por qué se requieren corchetes para try-catch?

38

En varios lenguajes (Java al menos, piense también C #?) Puede hacer cosas como

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

Entonces, cuando tengo solo una declaración, no necesito agregar un nuevo alcance con { }. ¿Por qué no puedo hacer esto con try-catch?

try
    singleStatement;
catch(Exception e)
    singleStatement;

¿Hay algo especial en try-catch que requiera tener siempre un nuevo alcance o algo así? Y si es así, ¿no podría el compilador arreglar eso?

Svish
fuente
3
Nit-picking here: Estrictamente hablando de forlas partes debe llamarse algo así initial, conditiony stepcomo initialno necesita definir una variable y stepno necesita ser un incremento.
Joachim Sauer el
44
como nota al margen D no requiere llaves alrededor de la declaración única, intente atrapar bloques
monstruo de trinquete el
13
Creo que la verdadera pregunta es al revés: ¿por qué algunas construcciones permiten declaraciones "desnudas"? Las llaves deben ser obligatorias en todas partes para mantener la consistencia.
UncleZeiv
3
@UncleZeiv: no es inconsistente si considera que lo que sigue ifes siempre una declaración única, y las declaraciones múltiples encerradas entre llaves comprenden una declaración única. Pero soy uno de los que siempre ponen los frenos de todos modos, así que ...
detly
1
Tiendo a escribir las llaves también, pero me gusta no tener que escribirlas cuando tengo puntos de salida directos como throwy return, y como dijo @detly, si consideras las llaves como un solo grupo de declaraciones, no lo encuentro inconsistente tampoco. Nunca he entendido cuáles son estos "muchos errores de codificación" mencionados por la gente. Las personas deben comenzar a prestar atención a lo que hacen, usar la sangría adecuada y hacerse pruebas de unidad: P nunca tuvo un problema con esto ...
Svish

Respuestas:

23

En mi opinión, están incluidos en Java y C # principalmente porque ya existían en C ++. La verdadera pregunta, entonces, es por qué C ++ es así. De acuerdo con el diseño y la evolución de C ++ (§16.3):

La trypalabra clave es completamente redundante y también lo son los { }corchetes, excepto cuando se usan varias declaraciones en un bloque de prueba o un controlador. Por ejemplo, hubiera sido trivial permitir:

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

Sin embargo, encontré esto tan difícil de explicar que se introdujo la redundancia para salvar al personal de soporte de usuarios confusos.

Editar: En cuanto a por qué esto sería confuso, creo que uno solo tiene que mirar las afirmaciones incorrectas en la respuesta de @Tom Jeffery (y, especialmente, el número de votos positivos que ha recibido) para darse cuenta de que habría un problema. Para el analizador, esto realmente no es diferente de hacer coincidir elses con ifllaves que carecen de s para forzar otra agrupación, todas las catch cláusulas coincidirían con las más recientes throw. Para aquellos lenguajes erróneos que lo incluyen, las finallycláusulas harían lo mismo. Desde el punto de vista del analizador, esto no es lo suficientemente diferente de la situación actual como para darse cuenta, en particular, como están las gramáticas ahora, realmente no hay nada para agrupar las catchcláusulas; los corchetes agrupan las declaraciones controladas por elcatch cláusulas, no las cláusulas de captura en sí.

Desde el punto de vista de escribir un analizador sintáctico, la diferencia es casi demasiado pequeña para notarla. Si comenzamos con algo como esto:

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

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

Entonces la diferencia sería entre:

try_clause: 'try' statement

y:

try_clause: 'try' compound_statement

Del mismo modo, para las cláusulas catch:

catch_clause: 'catch' catch_arg statement

vs.

catch_clause: 'catch' catch_arg compound_statement

Sin embargo, la definición de un bloque try / catch completo no necesitaría cambiar en absoluto. De cualquier manera, sería algo como:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[Aquí estoy usando [whatever]para indicar algo opcional, y estoy dejando de lado la sintaxis para un, finally_clauseya que no creo que tenga ninguna relación con la pregunta.]

Incluso si no intenta seguir toda la definición de gramática similar a Yacc allí, el punto puede resumirse con bastante facilidad: esa última declaración (comenzando con try_block) es aquella en la que las catchcláusulas se combinan con las trycláusulas, y sigue siendo exactamente el igual si se requieren o no las llaves.

Para reiterar / resumir: las llaves agrupan las declaraciones controladas por la catchs, pero no agrupan las catchs mismas. Como tal, esos aparatos no tienen absolutamente ningún efecto al decidir cuál catchva con qué try. Para el analizador / compilador, la tarea es igualmente fácil (o difícil) de cualquier manera. A pesar de esto, la respuesta de Tom @ (y el número de up-votos que ha recibido) proporciona amplia demostración del hecho de que un tal cambio haría con los usuarios es casi seguro que confundirlo.

Jerry Coffin
fuente
El OP pregunta por los corchetes alrededor del bloque try y catch , mientras que esto parece estar haciendo referencia a los que rodean el try (se puede entender que el primer párrafo hace referencia a ambos, pero el código ilustra solo el primero) ... ¿Puede ¿aclarar?
Shog9
@ Mr.CRT: un "bloque de captura" de otro modo se conocería como un "controlador", sobre el cual ver la cita anterior.
Jerry Coffin
3
bah, acabo de editar mi comentario para eliminar esa ambigüedad. A lo que me refiero es que esta podría ser una respuesta más efectiva que la de Tom (arriba) si se hiciera todo lo posible para ilustrar cómo la construcción sin soporte podría haber funcionado, pero de una manera confusa (el comentario de Billy sobre la respuesta de Tom parece implicar que no podría haber funcionado, lo que creo que es incorrecto).
Shog9
@JerryCoffin Gracias por ampliar su respuesta: ahora es mucho más claro para mí.
try return g(); catch(xxii) error("G() goofed: xxii");debería haber sido ordenado todavía IMO
alfC
19

En una respuesta sobre por qué se requieren corchetes para algunas construcciones de una sola declaración pero no para otras , Eric Lippert escribió:

Hay varios lugares donde C # requiere un bloque de sentencias entre paréntesis en lugar de permitir una declaración "desnuda". Son:

  • El cuerpo de un método, constructor, destructor, descriptor de acceso de propiedad, descriptor de acceso de evento o descriptor de acceso.
  • El bloque de un intento, captura, finalmente, región marcada, no marcada o insegura.
  • el bloque de una declaración lambda o método anónimo
  • el bloque de una instrucción if o loop si el bloque contiene directamente una declaración de variable local. (Es decir, "while (x! = 10) int y = 123;" es ilegal; tienes que preparar la declaración).

En cada uno de estos casos, sería posible llegar a una gramática inequívoca (o heurística para desambiguar una gramática ambigua) para la característica en la que una sola declaración sin corchetes es legal. ¿Pero cuál sería el punto? En cada una de esas situaciones, espera ver múltiples declaraciones; las declaraciones individuales son el caso raro e improbable. Parece que realmente no vale la pena hacer que la gramática sea inequívoca para estos casos muy poco probables.

En otras palabras, era más costoso para el equipo compilador implementarlo de lo que estaba justificado, por el beneficio marginal que proporcionaría.

Robert Harvey
fuente
13

Creo que es para evitar colgar otros problemas de estilo. Lo siguiente sería ambiguo ...

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?

Podría significar esto:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

O...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 
Tom Jefferys
fuente
24
Esta ambigüedad se aplica a si y por igual. La pregunta es: ¿por qué se permite la declaración if y switch pero no para try / catch ?
Dipan Mehta
3
@Dipan punto justo. Me pregunto si se trata simplemente de que Java / C # intente ser coherente con lenguajes más antiguos como C al permitir ifs no arriostrados. Mientras que try / catch es una construcción más nueva, por lo tanto, los diseñadores de lenguaje pensaron que estaba bien romper con la tradición.
Tom Jefferys el
Intenté responder a la compulsión al menos para C y C ++.
Dipan Mehta
66
@DipanMehta: Debido a que no hay múltiples posibles cláusulas colgantes para el ifcaso. Es fácil decir "bueno, si no, se une al más profundo si" y terminar con eso. Pero para try / catch eso no funciona.
Billy ONeal
2
@Billy: Creo que estás pasando por alto la posible ambigüedad presente en if / if else ... Es fácil decir "es fácil decir", pero eso es porque hay una regla difícil para resolver la ambigüedad que, por cierto, no permite ciertas construcciones sin el uso de corchetes. La respuesta de Jerry implica que esta fue una elección consciente hecha para evitar confusiones, pero seguramente podría haber funcionado, tal como "funciona" para si / si más.
Shog9
1

Creo que la razón principal es que hay muy poco que pueda hacer en C # que necesite un bloque try / catch que sea solo una línea. (No puedo pensar en ninguna en este momento en la parte superior de mi cabeza). Puede tener un punto válido en términos del bloque catch, por ejemplo, una instrucción de una línea para registrar algo, pero en términos de legibilidad tiene más sentido (al menos para mí) requerir {}.

Jetti
fuente