ทำไมต้องใช้วงเล็บในการลองจับ


38

ในภาษาต่าง ๆ (อย่างน้อย Java คิดว่า C #?) คุณสามารถทำสิ่งต่าง ๆ เช่น

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

{ }ดังนั้นเมื่อผมมีเพียงแค่หนึ่งคำสั่งผมไม่จำเป็นต้องเพิ่มขอบเขตใหม่ที่มี ทำไมฉันถึงลองทำไม่ได้?

try
    singleStatement;
catch(Exception e)
    singleStatement;

มีอะไรพิเศษเกี่ยวกับลองจับซึ่งต้องมีขอบเขตใหม่หรือบางสิ่งบางอย่างเสมอ และถ้าเป็นเช่นนั้นคอมไพเลอร์ไม่สามารถแก้ไขได้หรือไม่


3
จู้จี้จุกจิกที่นี่: พูดอย่างเคร่งครัดสำหรับforชิ้นส่วนควรตั้งชื่อสิ่งที่ต้องการinitial, conditionและstepเป็นinitialความต้องการไม่ได้กำหนดตัวแปรและstepไม่จำเป็นต้องเพิ่มขึ้น
Joachim Sauer

4
ตามหมายเหตุด้าน D ไม่ต้องการวงเล็บปีกการอบคำสั่งเดียวลองจับบล็อก
วงล้อประหลาด

13
ฉันคิดว่าคำถามจริงเป็นอีกทางหนึ่ง: ทำไมบางโครงสร้างจึงอนุญาตให้ใช้คำสั่ง "เปล่า" ควรจัดฟันทุกครั้งเพื่อความมั่นคง
UncleZeiv

3
@ UncleZeiv - มันไม่สอดคล้องกันถ้าคุณพิจารณาว่าสิ่งที่ตามมาifคือคำสั่งเดียวเสมอและหลายคำสั่งที่อยู่ในเครื่องหมายวงเล็บประกอบด้วยคำสั่งเดียว แต่ฉันเป็นหนึ่งในบรรดาผู้ที่มักจะใส่วงเล็บในอยู่แล้วดังนั้น ...
detly

1
ฉันมักจะเขียนวงเล็บปีกกาเช่นกัน แต่ฉันไม่ต้องการเขียนมันเมื่อฉันมีจุดทางออกตรงเช่นthrowและreturnและ @detly พูดว่าถ้าคุณคิดว่าวงเล็บปีกกาเป็นคำแถลงกลุ่มเดียวฉันไม่พบมัน ไม่สอดคล้องกันเช่นกัน ฉันไม่เคยเข้าใจว่าคน "ข้อผิดพลาดในการเข้ารหัส" จำนวนมากที่กล่าวถึงนี้คืออะไร คนต้องเริ่มให้ความสนใจกับสิ่งที่พวกเขาทำใช้ฟันเลื่อยที่เหมาะสมและมีการทดสอบหน่วย: P ไม่เคยมีปัญหากับเรื่องนี้ ...
Svish

คำตอบ:


23

IMO จะรวมอยู่ใน Java และ C # เป็นหลักเนื่องจากมีอยู่แล้วใน C ++ คำถามจริงคือทำไม C ++ เป็นแบบนั้น ตามการออกแบบและวิวัฒนาการของ C ++ (§16.3):

tryคำหลักซ้ำซ้อนอย่างสมบูรณ์และเพื่อให้มี{ }วงเล็บยกเว้นในกรณีที่งบหลายถูกนำมาใช้จริงในการลองบล็อกหรือจัดการ ตัวอย่างเช่นมันจะไม่สำคัญที่จะอนุญาตให้:

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

อย่างไรก็ตามฉันพบว่ามันยากที่จะอธิบายว่ามีการแนะนำความซ้ำซ้อนเพื่อช่วยเจ้าหน้าที่ฝ่ายสนับสนุนจากผู้ใช้ที่สับสน

แก้ไข: สาเหตุที่ทำให้เกิดความสับสนฉันคิดว่ามีเพียงการดูคำยืนยันที่ไม่ถูกต้องในคำตอบของ @Tom Jeffery (และโดยเฉพาะอย่างยิ่งจำนวนคะแนนที่ได้รับ) เพื่อตระหนักว่าจะมีปัญหา การแยกวิเคราะห์นี้เป็นจริงไม่แตกต่างจากการจับคู่elseS กับifs - ขาดการจัดฟันที่จะบังคับให้จัดกลุ่มอื่น ๆทุก คำสั่งจะตรงกับล่าสุดcatch throwสำหรับภาษาที่มีการใช้ภาษาที่ผิด ๆfinallyข้อนั้นจะทำเช่นเดียวกัน จากมุมมองของ parser นี่แทบจะไม่แตกต่างจากสถานการณ์ปัจจุบันที่จะสังเกตเห็น - โดยเฉพาะอย่างยิ่งเมื่อ grammars ยืนอยู่ในขณะนี้ไม่มีอะไรที่จะจัดกลุ่มcatchคำสั่งด้วยกัน - กลุ่มวงเล็บคำสั่งที่ควบคุมโดยcatch ข้อไม่จับตัวเอง

จากมุมมองของการเขียนโปรแกรมแยกวิเคราะห์ความแตกต่างนั้นเล็กเกินกว่าจะสังเกตได้ ถ้าเราเริ่มต้นด้วยสิ่งนี้:

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

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

จากนั้นความแตกต่างจะอยู่ระหว่าง:

try_clause: 'try' statement

และ:

try_clause: 'try' compound_statement

เช่นเดียวกันสำหรับข้อที่:

catch_clause: 'catch' catch_arg statement

เมื่อเทียบกับ

catch_clause: 'catch' catch_arg compound_statement

คำจำกัดความของบล็อก try / catch ที่สมบูรณ์นั้นไม่จำเป็นต้องเปลี่ยนแปลงเลย ไม่ว่าด้วยวิธีใดมันจะเป็นสิ่งที่ชอบ:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[ที่นี่ฉันใช้[whatever]เพื่อระบุสิ่งที่ไม่จำเป็นและฉันทิ้งไวยากรณ์สำหรับfinally_clauseเนื่องจากฉันไม่คิดว่ามันจะมีผลกับคำถาม]

แม้ว่าคุณจะไม่พยายามที่จะทำตามทุก Yacc เหมือนการกำหนดไวยากรณ์มีจุดสามารถสรุปได้อย่างเป็นธรรมได้อย่างง่ายดาย: ที่คำสั่งสุดท้าย (เริ่มต้นด้วยtry_block) เป็นหนึ่งในที่catchคำสั่งได้รับการจับคู่กับtryคำสั่ง - และมันยังคงอยู่ตรงเหมือนกันไม่ว่าจะเป็นเครื่องมือจัดฟันที่จำเป็นหรือไม่

ย้ำ / สรุป: วงเล็บกลุ่มกันงบควบคุมโดยcatch s แต่ทำไม่ได้กลุ่มcatchs ตัวเอง เช่นการจัดฟันเหล่านั้นได้อย่างไม่มีผลต่อการตัดสินใจที่จะไปด้วยซึ่งcatch tryสำหรับ parser / คอมไพเลอร์งานก็ง่ายเหมือนกัน (หรือยาก) ทั้งสองวิธี แม้จะมีสิ่งนี้คำตอบของ @ ทอม (และจำนวนคะแนนที่ได้รับจะเพิ่มขึ้น) แสดงให้เห็นถึงความจริงที่ว่าการเปลี่ยนแปลงเช่นนี้จะสร้างความสับสนให้กับผู้ใช้


OP กำลังถามเกี่ยวกับเครื่องหมายวงเล็บรอบทั้งลองและcatch blockในขณะที่สิ่งนี้ดูเหมือนเป็นการอ้างอิงผู้ที่อยู่รอบลอง (ย่อหน้าแรกสามารถเข้าใจการอ้างอิงทั้งสองได้ แต่รหัสแสดงเพียงอดีต) ... คุณช่วยได้ไหม ชี้แจง?
Shog9

@ Mr.CRT: "catch block" ไม่เช่นนั้นจะรู้จักกันในนาม "handler" ซึ่งจะเห็นการอ้างอิงข้างต้น
Jerry Coffin

3
Bah เพียงแก้ไขความคิดเห็นของฉันที่จะลบที่คลุมเครือ สิ่งที่ฉันได้รับคือว่านี่อาจเป็นคำตอบที่มีประสิทธิภาพมากกว่า Tom's (ด้านบน) ถ้ามันมีความยาวมากขึ้นเพื่อแสดงให้เห็นว่าโครงสร้างแบบวงเล็บปีกกาน้อยกว่านั้นสามารถทำงานได้ แต่ในลักษณะที่สับสน เพื่อบอกเป็นนัยว่ามันไม่สามารถทำงานได้ซึ่งฉันเชื่อว่าไม่ถูกต้อง)
Shog9

@JerryCoffin ขอบคุณที่ขยายคำตอบของคุณ: มันชัดเจนขึ้นสำหรับฉันตอนนี้

try return g(); catch(xxii) error("G() goofed: xxii");สิ่งที่เรียบร้อยยังคง IMO
alfC

19

ในคำตอบเกี่ยวกับสาเหตุที่ต้องใช้วงเล็บสำหรับการสร้างคำสั่งเดี่ยว แต่ไม่ใช่คำสั่งอื่น Eric Lippert เขียน:

มีหลายสถานที่ที่ C # ต้องการบล็อกที่มีคำสั่งแบบ braced แทนที่จะอนุญาตให้ใช้คำสั่ง "naked" พวกเขาคือ:

  • เนื้อความของเมธอด, ตัวสร้าง, ตัวทำลาย, ตัวเข้าถึงคุณสมบัติ, ตัวเข้าถึงเหตุการณ์หรือตัวสร้างดัชนี
  • บล็อกของพื้นที่ลอง, ตรวจสอบ, สุดท้าย, เลือก, ไม่เลือกหรือไม่ปลอดภัย
  • บล็อกของคำสั่งแลมบ์ดาหรือวิธีการที่ไม่ระบุชื่อ
  • บล็อกของคำสั่ง if หรือ loop หากบล็อกมีการประกาศตัวแปรโลคัลโดยตรง (นั่นคือ "ในขณะที่ (x! = 10) int y = 123;" ผิดกฎหมายคุณต้องปิดบังการประกาศ)

ในแต่ละกรณีเหล่านี้อาจเป็นไปได้ที่จะเกิดไวยากรณ์ที่ไม่คลุมเครือ (หรือฮิวริสติกเพื่อแก้ปัญหาไวยากรณ์ที่คลุมเครือ) สำหรับคุณลักษณะที่คำสั่ง unbraced เดียวถูกกฎหมาย แต่ประเด็นจะเป็นอย่างไร ในแต่ละสถานการณ์คุณคาดหวังว่าจะเห็นหลายข้อความ งบเดียวเป็นกรณีที่หายากและไม่น่าเป็นไปได้ ดูเหมือนว่ามันไม่คุ้มค่าที่จะทำให้ไวยากรณ์ไม่ชัดเจนสำหรับกรณีที่ไม่น่าเป็นไปได้เหล่านี้

กล่าวอีกนัยหนึ่งมันก็มีราคาแพงกว่าสำหรับทีมคอมไพเลอร์ที่จะใช้มันมากกว่าที่เป็นธรรมเพื่อผลประโยชน์ส่วนเพิ่มที่จะให้


13

ฉันคิดว่ามันควรหลีกเลี่ยงปัญหาเรื่องสไตล์อื่น ๆ ต่อไปนี้จะคลุมเครือ ...

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?

มันอาจหมายถึงสิ่งนี้:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

หรือ...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 

24
ความคลุมเครือนี้ใช้กับหากและเพื่อความเท่าเทียมกัน คำถามคือ: ทำไมจึงอนุญาตให้ifและswitch statement แต่ไม่ใช่เพื่อลอง / catch ?
Dipan Mehta

3
@Dipan จุดยุติธรรม ฉันสงสัยว่ามันเป็นเพียงเรื่องของ Java / C # ที่พยายามจะสอดคล้องกับภาษาที่เก่ากว่าเช่น C โดยการอนุญาตให้ใช้ ifs ที่ไม่ใช่การโยง ในขณะที่ try / catch เป็นโครงสร้างที่ใหม่กว่าดังนั้นผู้ออกแบบภาษาจึงคิดว่ามันโอเคที่จะทำลายประเพณี
Tom Jefferys

ฉันพยายามตอบคำถามอย่างน้อยที่สุดสำหรับ C และ C ++
Dipan Mehta

6
@DipanMehta: เนื่องจากมีหลายกรณีที่อาจเป็นไปได้ในifกรณีที่ไม่มี dangling clauses มันง่ายมากที่จะพูดว่า "สิ่งอื่น ๆ ที่ผูกกับสิ่งที่อยู่ข้างในสุดถ้า" และทำได้ด้วย แต่สำหรับการลอง / จับที่ไม่ได้ผล
Billy ONeal

2
@Billy: ฉันคิดว่าคุณกำลังคัดค้านความคลุมเครือที่อาจเกิดขึ้นหาก / ถ้า ... มันง่ายที่จะพูดว่า "มันเป็นเรื่องง่ายที่จะพูด" - แต่นั่นเป็นเพราะมีกฎที่ยากสำหรับการแก้ไขความคลุมเครือ โครงสร้างบางอย่างโดยไม่ต้องใช้วงเล็บ คำตอบของ Jerry นัยนี้ก็เป็นทางเลือกที่ใส่ใจทำเพื่อหลีกเลี่ยงความสับสน แต่แน่นอนมันอาจจะได้ทำงาน - เช่นเดียวกับ "ผลงาน" ถ้า / ถ้าอื่น
Shog9

1

ฉันคิดว่าเหตุผลหลักคือมีน้อยมากที่คุณสามารถทำได้ใน C # ที่จะต้องมีการลอง / จับบล็อกที่มีเพียงหนึ่งบรรทัด (ตอนนี้ฉันไม่สามารถนึกถึงส่วนบนสุดของหัวได้) คุณอาจมีจุดที่ถูกต้องในแง่ของ catch block เช่นคำสั่งหนึ่งบรรทัดในการบันทึกบางสิ่ง แต่ในแง่ของความสามารถในการอ่านมันทำให้มีเหตุผลมากกว่า (อย่างน้อยสำหรับฉัน) ที่ต้องการ {}

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.