Dlaczego w try-catch potrzebne są nawiasy?

38

W różnych językach (przynajmniej Java, myślisz również C #?) Możesz robić takie rzeczy

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

Więc kiedy mam tylko jedną instrukcję, nie muszę dodawać nowego zakresu { }. Dlaczego nie mogę tego zrobić za pomocą try-catch?

try
    singleStatement;
catch(Exception e)
    singleStatement;

Czy jest coś specjalnego w try-catch, która zawsze wymaga posiadania nowego zakresu lub czegoś takiego? A jeśli tak, to czy kompilator nie może tego naprawić?

Svish
źródło
3
Wybieranie nitów tutaj: Ściśle mówiąc, dla forczęści należy nazwać coś w rodzaju initial, conditioni stepponieważ initialnie musi definiować zmiennej i stepnie musi być przyrostem.
Joachim Sauer
4
na marginesie D nie wymaga nawiasów klamrowych wokół pojedynczej instrukcji spróbuj złapać bloki
maniak ratchet
13
Myślę, że prawdziwe pytanie jest odwrotne: dlaczego niektóre konstrukcje pozwalają na „nagie” stwierdzenia? Szelki powinny być wszędzie obowiązkowe dla zachowania spójności.
UncleZeiv,
3
@UncleZeiv - nie jest niespójne, jeśli weźmiesz pod uwagę, że to, co następuje, ifjest zawsze pojedynczą instrukcją, a wiele instrukcji zawartych w nawiasach klamrowych zawiera jedną instrukcję. Ale i tak jestem jednym z tych, którzy zawsze
zakładają aparat
1
Mam tendencję do pisania nawiasów klamrowych, ale nie lubię ich pisać, gdy mam bezpośrednie punkty wyjścia, takie jak throwi return, jak powiedziałem @detly, jeśli uznasz nawiasy klamrowe za jedną grupę instrukcji, nie znajdę go niekonsekwentne. Nigdy nie zrozumiałem, jakie są te „masowe błędy kodowania”, o których wspominają ludzie. Ludzie muszą zacząć zwracać uwagę na to, co robią, stosować odpowiednie wcięcie i przeprowadzać testy jednostkowe: P nigdy nie miał z tym problemu ...
Svish

Odpowiedzi:

23

IMO, są zawarte w Javie i C # głównie dlatego, że już istniały w C ++. Prawdziwe pytanie brzmi zatem, dlaczego C ++ jest w ten sposób. Zgodnie z projektem i ewolucją C ++ (§ 16.3):

Słowo trykluczowe jest całkowicie zbędne, podobnie jak { }nawiasy klamrowe, chyba że w instrukcji try-block lub module obsługi znajduje się wiele instrukcji. Na przykład zezwolenie na:

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

Jednak tak trudno mi było wytłumaczyć, że wprowadzono nadmiarowość, aby uratować personel pomocniczy przed zdezorientowanymi użytkownikami.

Edycja: Jeśli chodzi o to, dlaczego byłoby to mylące, myślę, że wystarczy spojrzeć na niepoprawne twierdzenia w odpowiedzi @ Toma Jeffery'ego (a zwłaszcza na liczbę głosów, jakie otrzymało), aby zdać sobie sprawę, że będzie problem. Dla parsera tak naprawdę nie różni się to od dopasowania elses do ifs - brak nawiasów klamrowych do wymuszenia innego grupowania, wszystkie catch klauzule pasowałyby do najnowszej throw. W przypadku tych źle wprowadzonych języków, które go zawierają, finallyklauzule zrobiłyby to samo. Z punktu widzenia parsera nie jest to wystarczająco różniące się od obecnej sytuacji, aby zauważyć - w szczególności, ponieważ gramatyka stoi teraz, tak naprawdę nie ma nic do grupowania catchklauzul - nawiasy grupują instrukcje sterowane przezcatch klauzule, a nie same klauzule catch.

Z punktu widzenia pisania parsera różnica jest prawie zbyt mała, aby zauważyć. Jeśli zaczniemy od czegoś takiego:

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

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

Wtedy różnica byłaby między:

try_clause: 'try' statement

i:

try_clause: 'try' compound_statement

Podobnie w przypadku klauzul połowowych:

catch_clause: 'catch' catch_arg statement

vs.

catch_clause: 'catch' catch_arg compound_statement

Jednak definicja kompletnego bloku try / catch nie musiałaby się wcale zmieniać. Tak czy inaczej, byłoby to coś takiego:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[Tutaj używam [whatever]do wskazania czegoś opcjonalnego i pomijam składnię, finally_clauseponieważ nie sądzę, żeby miało to jakikolwiek związek z pytaniem.]

Nawet jeśli nie starają się przestrzegać wszystkich Yacc-jak tam definicji gramatyki, punkt można podsumować dość łatwo: to ostatnie stwierdzenie (począwszy try_block) to jeden gdzie catchklauzule uzyskać dopasowane z tryklauzul - i pozostaje dokładnie to samo, czy nawiasy klamrowe są wymagane, czy nie.

Powtórzmy / Podsumowując: grupa szelki wraz oświadczenia kontrolowanych przez tych catchs, ale czy nie Group catchs sami. Jako takie, nawiasy klamrowe nie mają absolutnie żadnego wpływu na decyzję, która catchz nich idzie try. Dla parsera / kompilatora zadanie jest równie łatwe (lub trudne) w obu kierunkach. Pomimo tego, @ Toma odpowiedzi (oraz liczby głosów up-to odebrane) zapewnia wystarczającą demonstrację faktu, że taka zmiana would prawie na pewno błąd użytkowników.

Jerry Coffin
źródło
OP pyta o nawiasy kwadratowe wokół bloku try i catch , podczas gdy wydaje się, że odnosi się to do nawiasów wokół try (pierwszy akapit można rozumieć jako odnoszący się do obu, ale kod ilustruje tylko ten pierwszy) ... Czy możesz wyjaśniać?
Shog9
@ Mr.CRT: „blok przechwytywania” byłby inaczej znany jako „moduł obsługi”, o którym patrz cytat powyżej.
Jerry Coffin
3
bah, właśnie edytowałem mój komentarz, aby usunąć dwuznaczność. Mówię o tym, że może to być bardziej skuteczna odpowiedź niż odpowiedź Toma (powyżej), jeśli dołożyłaby większych starań, aby zilustrować, jak konstrukcja bez wspornika mogłaby działać, ale w sposób mylący (wydaje się, że komentarz Billy'ego do odpowiedzi Toma sugerować, że to nie mogło zadziałać, co moim zdaniem jest nieprawidłowe).
Shog9
@JerryCoffin Dzięki za poszerzenie twojej odpowiedzi: teraz jest dla mnie o wiele jaśniej.
try return g(); catch(xxii) error("G() goofed: xxii");mogłoby być jeszcze fajnie IMO
alfC
19

W odpowiedzi na pytanie, dlaczego w niektórych konstrukcjach z pojedynczą instrukcją są wymagane nawiasy klamrowe , Eric Lippert napisał:

Istnieje wiele miejsc, w których C # wymaga zbrojonego bloku instrukcji, a nie zezwalania na „nagą” instrukcję. Oni są:

  • treść metody, konstruktora, destruktora, akcesorium właściwości, akcesorium zdarzeń lub akcesorium indeksującego.
  • blok próby, złapania, wreszcie sprawdzonego, niezaznaczonego lub niebezpiecznego regionu.
  • blok instrukcji lambda lub metody anonimowej
  • blok instrukcji if lub loop, jeśli blok zawiera bezpośrednio deklarację zmiennej lokalnej. (To znaczy „while (x! = 10) int y = 123;” jest nielegalne; musisz przygotować deklarację).

W każdym z tych przypadków można byłoby wymyślić jednoznaczną gramatykę (lub heurystykę w celu ujednoznacznienia dwuznacznej gramatyki) dla cechy, w której pojedyncze nieskrępowane stwierdzenie jest legalne. Ale jaki byłby sens? W każdej z tych sytuacji oczekuje się wyświetlenia wielu instrukcji; pojedyncze stwierdzenia to rzadki, mało prawdopodobny przypadek. Wydaje się, że naprawdę nie warto sprawiać, by gramatyka była jednoznaczna w tych bardzo mało prawdopodobnych przypadkach.

Innymi słowy, wdrożenie go przez zespół kompilatora kosztowało więcej niż było to uzasadnione, ze względu na marginalną korzyść.

Robert Harvey
źródło
13

Myślę, że ma to na celu uniknięcie problemów związanych ze stylem. Poniższe byłyby dwuznaczne ...

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?

Może to oznaczać:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

Lub...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 
Tom Jefferys
źródło
24
Ta dwuznaczność dotyczy czy i na równi. Pytanie brzmi: dlaczego jest dozwolona instrukcja if i switch , ale nie try / catch ?
Dipan Mehta
3
@Dipan fair point. Zastanawiam się, czy jest to po prostu kwestia Java / C #, która stara się być spójna ze starszymi językami, takimi jak C, zezwalając na niesztywnione ifs. Podczas gdy try / catch jest nowszą konstrukcją, projektanci języków uznali, że zerwanie z tradycją jest w porządku.
Tom Jefferys
Próbowałem odpowiedzieć na kompulsję przynajmniej dla C i C ++.
Dipan Mehta
6
@DipanMehta: Ponieważ nie ma wielu możliwych wiszących klauzul else dla ifsprawy. Łatwo jest po prostu powiedzieć „cóż, reszta wiąże się z najbardziej wewnętrznym, jeśli” i zrobić to. Ale dla try / catch to nie działa.
Billy ONeal,
2
@Billy: Myślę, że zastanawiasz się nad potencjalną niejednoznacznością występującą w przypadku, jeśli / jeśli jeszcze ... Łatwo jest powiedzieć „łatwo jest po prostu powiedzieć” - ale to dlatego, że istnieje trudna zasada rozwiązywania niejasności, która, nawiasem mówiąc, nie pozwala niektóre konstrukcje bez użycia nawiasów. Odpowiedź Jerry'ego sugeruje, że był to świadomy wybór dokonany w celu uniknięcia zamieszania, ale na pewno mógł on zadziałać - tak jak „działa” w przypadku, gdy / jeśli inaczej.
Shog9
1

Myślę, że głównym powodem jest to, że niewiele można zrobić w języku C #, który wymagałby bloku try / catch, który jest tylko jedną linią. (Nie mogę teraz wymyślić żadnego z głowy). Możesz mieć ważny punkt pod względem bloku catch, np. Instrukcja jednowierszowa, aby coś zarejestrować, ale pod względem czytelności bardziej sensowne (przynajmniej dla mnie) jest wymaganie {}.

Jetti
źródło