Mengapa tanda kurung diperlukan untuk coba-tangkap?

38

Dalam berbagai bahasa (setidaknya Java, pikirkan juga C #?) Anda dapat melakukan hal-hal seperti

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

Jadi, ketika saya hanya memiliki satu pernyataan, saya tidak perlu menambahkan cakupan baru dengan { }. Mengapa saya tidak bisa melakukan ini dengan try-catch?

try
    singleStatement;
catch(Exception e)
    singleStatement;

Apakah ada sesuatu yang istimewa tentang try-catch yang mengharuskan selalu memiliki ruang lingkup baru atau sesuatu? Dan jika demikian, bukankah kompiler dapat memperbaikinya?

Svish
sumber
3
Nit-picking di sini: Sebenarnya untuk forbagian-bagian harus diberi nama seperti initial, conditiondan stepkarena initialtidak perlu mendefinisikan variabel dan steptidak perlu kenaikan.
Joachim Sauer
4
sebagai catatan tambahan D tidak memerlukan kawat gigi di sekitar satu pernyataan coba tangkap blok
ratchet freak
13
Saya pikir pertanyaan sebenarnya adalah sebaliknya: mengapa beberapa konstruk mengizinkan pernyataan "telanjang"? Kawat gigi harus wajib di mana saja untuk konsistensi.
UncleZeiv
3
@UncleZeiv - tidak konsisten jika Anda menganggap bahwa yang mengikuti ifselalu merupakan pernyataan tunggal, dan banyak pernyataan yang dilampirkan oleh kawat gigi terdiri dari satu pernyataan. Tapi saya salah satu dari mereka yang selalu memasang kawat gigi, jadi ...
detly
1
Saya cenderung menulis kawat gigi juga, tapi saya suka tidak harus menulisnya ketika saya memiliki titik keluar langsung seperti throwdan return, dan seperti @detly berkata, jika Anda menganggap kawat gigi sebagai satu kelompok pernyataan, saya tidak menemukannya tidak konsisten juga. Saya tidak pernah mengerti apa ini "banyak kesalahan pengkodean" yang disebutkan oleh orang-orang. Orang-orang perlu mulai memperhatikan apa yang mereka lakukan, menggunakan indensi yang tepat dan melakukan tes unit: P tidak pernah punya masalah dengan ini ...
Svish

Jawaban:

23

IMO, mereka termasuk dalam Java dan C # terutama karena mereka sudah ada di C ++. Pertanyaan sebenarnya adalah mengapa C ++ seperti itu. Menurut Desain dan Evolusi C ++ (§16.3):

Kata trykunci benar-benar berlebihan dan begitu pula { }tanda kurung kecuali di mana banyak pernyataan benar-benar digunakan dalam blok-coba atau penangan. Misalnya, sepele untuk mengizinkan:

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

Namun, saya menemukan ini sangat sulit untuk menjelaskan bahwa redundansi diperkenalkan untuk menyelamatkan personil pendukung dari pengguna yang bingung.

Sunting: Mengenai mengapa ini akan membingungkan, saya pikir kita hanya perlu melihat pernyataan yang salah dalam jawaban @Tom Jeffery (dan, terutama, jumlah suara yang diterima) untuk menyadari bahwa akan ada masalah. Bagi parser, ini benar-benar tidak berbeda dari mencocokkan elsedengan ifs - kurang kawat gigi untuk memaksa pengelompokan lain, semua catch klausa akan cocok dengan yang terbaru throw. Untuk bahasa-bahasa yang salah yang memasukkannya, finallyklausa akan melakukan hal yang sama. Dari sudut pandang parser, ini hampir tidak cukup berbeda dari situasi saat ini untuk diperhatikan - khususnya, karena tata bahasa berdiri sekarang, benar-benar tidak ada yang mengelompokkan catchklausa bersama - kurung mengelompokkan pernyataan yang dikendalikan olehcatch klausa, bukan tangkapan klausa sendiri.

Dari sudut pandang menulis parser, perbedaannya hampir terlalu kecil untuk diperhatikan. Jika kita mulai dengan sesuatu seperti ini:

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

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

Maka perbedaannya adalah antara:

try_clause: 'try' statement

dan:

try_clause: 'try' compound_statement

Demikian juga, untuk klausa tangkap:

catch_clause: 'catch' catch_arg statement

vs.

catch_clause: 'catch' catch_arg compound_statement

Definisi blok coba / tangkap lengkap tidak perlu diubah sama sekali. Bagaimanapun itu akan menjadi seperti:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[Di sini saya menggunakan [whatever]untuk menunjukkan sesuatu yang opsional, dan saya meninggalkan sintaks untuk finally_clausekarena saya tidak berpikir ada kaitannya dengan pertanyaan.]

Bahkan jika Anda tidak mencoba mengikuti semua definisi tata bahasa seperti Yacc di sana, intinya dapat diringkas dengan cukup mudah: bahwa pernyataan terakhir (dimulai dengan try_block) adalah pernyataan di mana catchklausa dicocokkan dengan tryklausa - dan tetap persis seperti itu. sama apakah kawat gigi diperlukan atau tidak.

Untuk mengulangi / meringkas: kelompok kawat gigi bersama-sama laporan dikendalikan oleh para catchs, tapi lakukan tidak kelompok catchs sendiri. Dengan demikian, kawat gigi itu sama sekali tidak berpengaruh pada memutuskan mana catchyang sesuai try. Untuk parser / kompiler, tugasnya sama mudah (atau sulit). Meskipun demikian, jawaban @ Tom (dan jumlah suara yang diterima) memberikan banyak bukti bahwa perubahan semacam itu hampir pasti akan membingungkan pengguna.

Jerry Coffin
sumber
OP bertanya tentang tanda kurung di sekitar coba dan blok tangkap , sementara ini tampaknya merujuk orang-orang di sekitar coba (paragraf pertama dapat dipahami untuk merujuk keduanya, tetapi kode hanya menggambarkan yang pertama) ... Bisakah Anda menjelaskan?
Shog9
@ Mr.CRT: "catch block" seharusnya dikenal sebagai "handler", yang melihat kutipan di atas.
Jerry Coffin
3
bah, hanya diedit komentar saya untuk menghapus bahwa ambiguitas. Apa yang saya maksudkan adalah bahwa ini bisa menjadi jawaban yang lebih efektif daripada jawaban Tom (di atas) jika terlalu panjang untuk menggambarkan bagaimana konstruksi tanpa braket bisa bekerja, tetapi dengan cara yang membingungkan (komentar Billy tentang jawaban Tom tampaknya menyiratkan bahwa itu tidak bisa bekerja, yang saya percaya tidak benar).
Shog9
@ JerryCoffin Terima kasih telah memperluas jawaban Anda: sekarang jauh lebih jelas bagi saya.
try return g(); catch(xxii) error("G() goofed: xxii");apakah masih rapi IMO
alfC
19

Dalam sebuah jawaban tentang mengapa tanda kurung diperlukan untuk beberapa konstruksi pernyataan tunggal tetapi tidak yang lain , Eric Lippert menulis:

Ada sejumlah tempat di mana C # membutuhkan blok pernyataan yang kuat daripada membiarkan pernyataan "telanjang". Mereka:

  • badan metode, konstruktor, destruktor, pengakses properti, pengakses acara atau pengindeks pengindeks.
  • blok daerah coba, tangkap, akhirnya, dicentang, tidak dicentang atau tidak aman.
  • blok pernyataan lambda atau metode anonim
  • blok pernyataan jika atau loop jika blok secara langsung berisi deklarasi variabel lokal. (Yaitu, "sementara (x! = 10) int y = 123;" adalah ilegal; Anda harus menguatkan deklarasi.)

Dalam masing-masing kasus ini adalah mungkin untuk menghasilkan tata bahasa yang tidak ambigu (atau heuristik untuk mengaburkan tata bahasa yang ambigu) untuk fitur di mana satu pernyataan yang tidak diikat adalah sah. Tapi apa gunanya? Dalam setiap situasi itu Anda mengharapkan untuk melihat banyak pernyataan; pernyataan tunggal adalah kasus yang jarang, tidak mungkin. Sepertinya tidak layak untuk membuat tata bahasa tidak ambigu untuk kasus-kasus yang sangat tidak mungkin ini.

Dengan kata lain, itu lebih mahal bagi tim penyusun untuk mengimplementasikannya daripada dibenarkan, untuk manfaat marjinal yang diberikannya.

Robert Harvey
sumber
13

Saya pikir itu untuk menghindari masalah gaya lain. Berikut ini akan menjadi 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?

Ini bisa berarti ini:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

Atau...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 
Tom Jefferys
sumber
24
Ambiguitas ini berlaku untuk jika dan untuk sama. Pertanyaannya adalah: Mengapa itu diizinkan untuk jika dan beralih pernyataan tetapi tidak untuk mencoba / menangkap ?
Dipan Mehta
3
@Dipan poin yang adil. Saya bertanya-tanya apakah itu hanya masalah Java / C # yang mencoba untuk konsisten dengan bahasa yang lebih tua seperti C dengan memungkinkan ifs non-bracing. Sedangkan try / catch adalah konstruk yang lebih baru, oleh karena itu para perancang bahasa menganggapnya tidak sesuai dengan tradisi.
Tom Jefferys
Saya mencoba menjawab paksaan setidaknya untuk C dan C ++.
Dipan Mehta
6
@DipanMehta: Karena tidak ada beberapa klausa lain yang menggantung untuk ifkasus ini. Sangat mudah untuk mengatakan "baik yang lain mengikat ke dalam jika" dan dilakukan dengan itu. Tetapi untuk mencoba / menangkap itu tidak berhasil.
Billy ONeal
2
@ Billy: Saya pikir Anda menyembunyikan ambiguitas potensial yang ada dalam if / if else ... Mudah untuk mengatakan "mudah untuk mengatakan" - tapi itu karena ada aturan keras untuk menyelesaikan ambiguitas yang, secara kebetulan, tidak diizinkan konstruksi tertentu tanpa menggunakan tanda kurung. Jawaban Jerry menyiratkan ini adalah pilihan sadar yang dibuat untuk menghindari kebingungan, tetapi pasti itu bisa berhasil - sama seperti "bekerja" untuk jika / jika lain.
Shog9
1

Saya pikir alasan utama adalah bahwa ada sangat sedikit yang dapat Anda lakukan di C # yang akan memerlukan blok coba / tangkap yang hanya satu baris. (Saya tidak bisa memikirkan apa pun sekarang dari atas kepala saya). Anda mungkin memiliki titik valid dalam hal blok tangkap, misalnya pernyataan satu baris untuk mencatat sesuatu tetapi dalam hal keterbacaan, lebih masuk akal (setidaknya bagi saya) untuk meminta {}.

Jetti
sumber