Tại sao dấu ngoặc được yêu cầu để thử bắt?


38

Trong các ngôn ngữ khác nhau (ít nhất là Java, cũng nghĩ là C #?) Bạn có thể làm những việc như

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

Vì vậy, khi tôi chỉ có một tuyên bố, tôi không cần thêm phạm vi mới { }. Tại sao tôi không thể làm điều này với thử bắt?

try
    singleStatement;
catch(Exception e)
    singleStatement;

Có điều gì đặc biệt về thử bắt đòi hỏi phải luôn có phạm vi mới hoặc thứ gì đó không? Và nếu vậy, trình biên dịch có thể sửa nó không?


3
Chọn nit ở đây: Nói một cách nghiêm túc cho forcác bộ phận nên được đặt tên giống như initial, conditionstepinitialkhông cần xác định một biến và stepkhông cần phải là một số gia.
Joachim Sauer

4
như một lưu ý phụ D không yêu cầu niềng răng xung quanh câu lệnh đơn thử các khối bắt
ratchet freak

13
Tôi nghĩ rằng câu hỏi thực sự là cách khác: tại sao một số cấu trúc cho phép tuyên bố "trần trụi"? Niềng răng nên bắt buộc ở mọi nơi để thống nhất.
ChúZeiv

3
@UncleZeiv - sẽ không nhất quán nếu bạn cho rằng những gì diễn ra sau ifluôn luôn là một câu lệnh và nhiều câu lệnh được bao quanh bởi dấu ngoặc nhọn bao gồm một câu lệnh. Nhưng dù sao tôi cũng là một trong những người luôn đặt niềng răng, vì vậy ...
ghê tởm

1
Tôi cũng có xu hướng viết niềng răng, nhưng tôi không muốn viết chúng khi tôi có các điểm thoát trực tiếp như throwreturn, như @detly đã nói, nếu bạn coi niềng răng là một nhóm các câu lệnh, tôi không tìm thấy nó không nhất quán Tôi chưa bao giờ hiểu "rất nhiều lỗi mã hóa" được đề cập bởi mọi người là gì. Mọi người cần bắt đầu chú ý đến những gì họ làm, sử dụng sự chú ý đúng mức và có các bài kiểm tra đơn vị: P không bao giờ có vấn đề với điều này ...
Svish

Câu trả lời:


23

IMO, chúng được bao gồm trong Java và C # chủ yếu vì chúng đã tồn tại trong C ++. Câu hỏi thực sự là tại sao C ++ lại như vậy. Theo Thiết kế và tiến hóa của C ++ (§16.3):

Các trytừ khóa là hoàn toàn không cần thiết và như vậy là { }dấu ngoặc trừ trường hợp nhiều câu lệnh đang thực sự được sử dụng trong một thử khối hoặc một handler. Ví dụ, việc cho phép:

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

Tuy nhiên, tôi thấy điều này rất khó để giải thích rằng sự dư thừa đã được đưa ra để cứu nhân viên hỗ trợ khỏi những người dùng bối rối.

Chỉnh sửa: Về lý do tại sao điều này sẽ gây nhầm lẫn, tôi nghĩ rằng người ta chỉ cần nhìn vào các xác nhận không chính xác trong câu trả lời của @Tom Jeffery (và đặc biệt là số lượng phiếu bầu đã nhận được) để nhận ra rằng sẽ có vấn đề. Đối với trình phân tích cú pháp, điều này thực sự không khác gì so với khớp elses với ifthiếu niềng răng để buộc các nhóm khác, tất cả catch các mệnh đề sẽ khớp với gần đây nhất throw. Đối với những kẻ lạc lõng bao gồm nó, finallycác mệnh đề cũng sẽ làm như vậy. Từ quan điểm của trình phân tích cú pháp, điều này hầu như không đủ khác biệt với tình huống hiện tại cần chú ý - đặc biệt, vì các ngữ pháp hiện nay, thực sự không có gì để nhóm các catchmệnh đề lại với nhau - các dấu ngoặc đơn nhóm các câu lệnh được điều khiển bởicatch mệnh đề, không phải là mệnh đề bắt chính chúng.

Từ quan điểm viết một trình phân tích cú pháp, sự khác biệt gần như quá nhỏ để nhận thấy. Nếu chúng ta bắt đầu với một cái gì đó như thế này:

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

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

Sau đó, sự khác biệt sẽ là giữa:

try_clause: 'try' statement

và:

try_clause: 'try' compound_statement

Tương tự như vậy, đối với các mệnh đề bắt:

catch_clause: 'catch' catch_arg statement

so với

catch_clause: 'catch' catch_arg compound_statement

Định nghĩa của một khối thử / bắt hoàn chỉnh hoàn toàn không cần phải thay đổi. Dù bằng cách nào, nó sẽ là một cái gì đó như:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[Ở đây tôi đang sử dụng [whatever]để chỉ ra một cái gì đó tùy chọn và tôi bỏ qua cú pháp finally_clausevì tôi không nghĩ nó có liên quan gì đến câu hỏi.]

Ngay cả khi bạn không cố gắng tuân theo tất cả các định nghĩa ngữ pháp giống như Yacc ở đó, điểm có thể được tóm tắt khá dễ dàng: câu lệnh cuối cùng (bắt đầu bằng try_block) là catchmệnh đề khớp với trymệnh đề - và nó vẫn chính xác là giống nhau cho dù niềng răng có cần thiết hay không.

Để nhắc lại / tóm tắt: các dấu ngoặc nhóm lại với nhau những điều khoản kiểm soát bởi các catchs, nhưng làm không nhóm catchlà bản thân họ. Như vậy, những cái niềng răng đó hoàn toàn không có tác dụng gì khi quyết định cái nào catchđi với cái nào try. Đối với trình phân tích cú pháp / trình biên dịch, tác vụ cũng dễ dàng (hoặc khó) như nhau. Mặc dù vậy, câu trả lời của @ Tom (và số lượng phiếu bầu tăng lên mà nó nhận được) cung cấp bằng chứng rộng rãi về thực tế rằng một sự thay đổi như vậy gần như chắc chắn sẽ gây nhầm lẫn cho người dùng.


OP đang hỏi về các dấu ngoặc quanh cả khối thử và khối bắt , trong khi điều này dường như là tham chiếu đến những người xung quanh thử (đoạn đầu tiên có thể được hiểu là tham chiếu cả hai, nhưng mã chỉ minh họa trước đây) ... Bạn có thể làm rõ?
Shog9

@ Mr.CRT: một "khối bắt" nếu không sẽ được gọi là "xử lý", trong đó xem trích dẫn ở trên.
Jerry Coffin

3
bah, chỉ cần chỉnh sửa bình luận của tôi để loại bỏ sự mơ hồ đó . Điều tôi nhận được là đây có thể là một câu trả lời hiệu quả hơn Tom (ở trên) nếu nó có độ dài lớn hơn để minh họa cách cấu trúc không có khung có thể hoạt động, nhưng theo cách khó hiểu (nhận xét của Billy về câu trả lời của Tom để ngụ ý rằng nó không thể làm việc, mà tôi tin là không chính xác).
Shog9

@JerryCoffin Cảm ơn bạn đã mở rộng câu trả lời của bạn: bây giờ nó rõ ràng hơn nhiều đối với tôi.

try return g(); catch(xxii) error("G() goofed: xxii");vẫn còn gọn gàng IMO
alfC

19

Trong một câu trả lời về lý do tại sao các dấu ngoặc được yêu cầu cho một số cấu trúc câu lệnh đơn nhưng không phải là các câu lệnh khác , Eric Lippert đã viết:

Có một số nơi mà C # yêu cầu một khối các câu lệnh thay vì cho phép một câu lệnh "trần trụi". Họ đang:

  • phần thân của một phương thức, hàm tạo, hàm hủy, hàm truy cập thuộc tính, hàm truy cập sự kiện hoặc hàm truy cập chỉ mục.
  • khối của một vùng thử, bắt, cuối cùng, được kiểm tra, không được kiểm tra hoặc không an toàn.
  • khối của một tuyên bố lambda hoặc phương thức ẩn danh
  • khối của một câu lệnh if hoặc loop nếu khối trực tiếp chứa một khai báo biến cục bộ. (Nghĩa là "while (x! = 10) int y = 123;" là bất hợp pháp; bạn phải thực hiện khai báo.)

Trong mỗi trường hợp này, có thể đưa ra một ngữ pháp không rõ ràng (hoặc heuristic để phân biệt một ngữ pháp mơ hồ) cho tính năng trong đó một tuyên bố không phân biệt là hợp pháp. Nhưng vấn đề sẽ là gì? Trong mỗi tình huống bạn đang mong đợi để xem nhiều câu lệnh; tuyên bố duy nhất là trường hợp hiếm, không có khả năng. Có vẻ như nó không thực sự xứng đáng để làm cho ngữ pháp không rõ ràng cho những trường hợp rất khó xảy ra này.

Nói cách khác, chi phí cao hơn cho nhóm biên dịch để thực hiện nó hơn là hợp lý, vì lợi ích cận biên mà nó sẽ cung cấp.


13

Tôi nghĩ rằng đó là để tránh các vấn đề phong cách khác. Sau đây sẽ là mơ hồ ...

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?

Nó có thể có nghĩa này:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

Hoặc là...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 

24
Sự mơ hồ này áp dụng cho nếucho bằng nhau. Câu hỏi là: Tại sao nó được phép cho câu lệnh ifswitch nhưng không cho thử / bắt ?
Dipan Mehta

3
@Dipan điểm công bằng. Tôi tự hỏi nếu đó đơn giản chỉ là vấn đề của Java / C # khi cố gắng phù hợp với các ngôn ngữ cũ hơn như C bằng cách cho phép các ifs không có dấu ngoặc. Trong khi đó try / Catch là một cấu trúc mới hơn, do đó các nhà thiết kế ngôn ngữ nghĩ rằng nó ổn khi phá vỡ truyền thống.
Tom Jefferys

Tôi đã cố gắng trả lời bắt buộc ít nhất là cho C và C ++.
Dipan Mehta

6
@DipanMehta: Bởi vì không có nhiều mệnh đề có thể lủng lẳng khác cho ifvụ án. Thật dễ dàng để chỉ nói "tốt, người khác liên kết với trong cùng nếu" và được thực hiện với nó. Nhưng để thử / bắt mà không hiệu quả.
Billy ONeal

2
@Billy: Tôi nghĩ rằng bạn đang che giấu sự mơ hồ tiềm ẩn trong nếu / nếu khác ... Thật dễ dàng để nói "thật dễ dàng để nói" - nhưng đó là bởi vì có một quy tắc khó khăn để giải quyết sự mơ hồ rằng, tình cờ, không tuân theo cấu trúc nhất định mà không sử dụng dấu ngoặc. Câu trả lời của Jerry ngụ ý đây một lựa chọn có ý thức được thực hiện để tránh nhầm lẫn, nhưng chắc chắn nó có thể đã hoạt động - giống như nó "hoạt động" nếu / nếu khác.
Shog9

1

Tôi nghĩ lý do chính là có rất ít bạn có thể làm trong C # mà sẽ cần một khối thử / bắt chỉ có một dòng. (Tôi không thể nghĩ về bất kỳ ngay bây giờ trên đỉnh đầu của tôi). Bạn có thể có một điểm hợp lệ về mặt khối bắt, ví dụ: một câu lệnh một dòng để ghi nhật ký một cái gì đó nhưng về khả năng đọc thì nó có ý nghĩa hơn (ít nhất là với tôi) để yêu cầu {}.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.