ทำไมข้อกำหนดข้อยกเว้นไม่ดี?


50

ย้อนกลับไปที่โรงเรียนเมื่อ 10 ปีก่อนพวกเขาสอนให้คุณใช้เครื่องมือระบุข้อยกเว้น เนื่องจากพื้นหลังของฉันเป็นหนึ่งในนั้นโปรแกรมเมอร์ Torvaldish C ที่หลีกเลี่ยงการดื้อ C ++ ยกเว้นว่าถูกบังคับให้ฉันลงเอยด้วยการใช้ C ++ เป็นระยะ ๆ และเมื่อฉันยังคงใช้ตัวระบุข้อยกเว้นเนื่องจากนั่นคือสิ่งที่ฉันได้รับการสอน

อย่างไรก็ตามโปรแกรมเมอร์ C ++ ส่วนใหญ่ดูเหมือนจะขมวดคิ้วเมื่อตัวระบุข้อยกเว้น ฉันได้อ่านการอภิปรายและข้อโต้แย้งต่าง ๆ จาก C ++ ผู้เชี่ยวชาญด้านการเช่นนี้ เท่าที่ฉันเข้าใจมันเดือดลงถึงสามสิ่ง:

  1. ตัวระบุข้อยกเว้นใช้ระบบชนิดที่ไม่สอดคล้องกับภาษาที่เหลือ ("ระบบชนิดเงา")
  2. หากฟังก์ชั่นของคุณที่มีตัวระบุข้อยกเว้นมีข้อผิดพลาดอื่นใดนอกจากสิ่งที่คุณได้ระบุไว้โปรแกรมจะถูกยกเลิกในรูปแบบที่ไม่ดีและไม่คาดคิด
  3. ตัวระบุข้อยกเว้นจะถูกลบออกในมาตรฐาน C ++ ที่กำลังจะมาถึง

ฉันพลาดอะไรบางอย่างที่นี่หรือนี่คือเหตุผลทั้งหมดเหรอ?

ความคิดเห็นของฉัน:

เกี่ยวกับ 1): แล้วอะไรล่ะ C ++ อาจเป็นภาษาการเขียนโปรแกรมที่ไม่สอดคล้องกันมากที่สุดที่เคยมีมา เรามีมาโคร, goto / label, horde (hoard?) ของพฤติกรรมที่ไม่ได้กำหนด - / ไม่ระบุ / / การนำไปใช้งาน, ประเภทจำนวนเต็มที่กำหนดไม่ดี, กฎการส่งเสริมประเภทโดยนัยทั้งหมด, คำหลักกรณีพิเศษเช่นเพื่อน, อัตโนมัติ ลงทะเบียนชัดเจน ... และอื่น ๆ บางคนอาจเขียนหนังสือหนา ๆ หลายเล่มเกี่ยวกับความแปลกประหลาดทั้งหมดใน C / C ++ เหตุใดผู้คนจึงตอบโต้กับความไม่สอดคล้องนี้โดยเฉพาะซึ่งเป็นข้อบกพร่องเล็ก ๆ น้อย ๆ เมื่อเปรียบเทียบกับคุณสมบัติที่เป็นอันตรายอื่น ๆ อีกมากมายของภาษา

เกี่ยวกับ 2): นั่นไม่ใช่ความรับผิดชอบของฉันเองเหรอ? มีวิธีอื่นอีกมากมายที่ฉันสามารถเขียนบั๊กที่ร้ายแรงใน C ++ เหตุใดกรณีนี้จึงเลวร้ายกว่านี้? แทนที่จะเขียนthrow(int)แล้วขว้าง Crash_t ฉันก็อาจอ้างได้ว่าฟังก์ชั่นของฉันคืนตัวชี้ไปที่ int จากนั้นสร้างตัวอักษรที่ชัดเจนชัดเจนและส่งกลับตัวชี้ไปที่ Crash_t จิตวิญญาณของ C / C ++ ได้เสมอที่จะทิ้งความรับผิดชอบส่วนใหญ่ให้กับโปรแกรมเมอร์

แล้วข้อดีล่ะ สิ่งที่ชัดเจนที่สุดคือถ้าฟังก์ชั่นของคุณพยายามที่จะโยนประเภทอื่นใดนอกเหนือจากที่คุณระบุไว้อย่างชัดเจนคอมไพเลอร์จะให้ข้อผิดพลาดแก่คุณ ฉันเชื่อว่ามาตรฐานมีความชัดเจนเกี่ยวกับเรื่องนี้ (?) บั๊กจะเกิดขึ้นก็ต่อเมื่อฟังก์ชั่นของคุณเรียกใช้ฟังก์ชั่นอื่น ๆ ที่ผิดประเภท

มาจากโลกของโปรแกรม C แบบฝังตัวที่กำหนดแน่นอนที่สุดแน่นอนฉันอยากรู้ว่าฟังก์ชั่นอะไรจะทำให้ฉัน หากมีบางอย่างในภาษาที่สนับสนุนว่าทำไมไม่ใช้มัน? ทางเลือกที่ดูเหมือนจะเป็น:

void func() throw(Egg_t);

และ

void func(); // This function throws an Egg_t

ฉันคิดว่ามีโอกาสสูงที่ผู้เรียกจะไม่สนใจ / ลืมใช้การลองจับในกรณีที่สองน้อยกว่าในกรณีแรก

ตามที่ฉันเข้าใจแล้วหากหนึ่งในสองรูปแบบนี้ตัดสินใจที่จะโยนข้อยกเว้นชนิดอื่นทันทีโปรแกรมจะหยุดทำงาน ในกรณีแรกเพราะมันไม่ได้รับอนุญาตให้โยนข้อยกเว้นอื่นในกรณีที่สองเพราะไม่มีใครคาดว่าจะโยน SpanishInquisition_t

ในกรณีหลังให้มีการจับสุดท้าย (... ) ที่ระดับสูงสุดของโปรแกรมดูเหมือนจะไม่ดีไปกว่าความผิดพลาดของโปรแกรม: "เฮ้บางแห่งในโปรแกรมของคุณมีบางสิ่งที่แปลกประหลาดและไม่มีข้อยกเว้น . " คุณไม่สามารถกู้คืนโปรแกรมได้เมื่อคุณอยู่ไกลจากที่มีข้อยกเว้นเกิดขึ้นสิ่งเดียวที่คุณทำได้คือออกจากโปรแกรม

และจากมุมมองของผู้ใช้พวกเขาไม่สามารถดูแลน้อยลงหากพวกเขาได้รับกล่องข้อความที่ชั่วร้ายจากระบบปฏิบัติการว่า "โปรแกรมสิ้นสุดลง Blablabla ตามที่อยู่ 0x12345" หรือกล่องข้อความชั่วร้ายจากโปรแกรมของคุณที่พูดว่า "ข้อยกเว้นจัดการ: myclass func.something" ข้อผิดพลาดยังคงอยู่ที่นั่น


ด้วยมาตรฐาน C ++ ที่กำลังจะมีขึ้นฉันจะไม่มีตัวเลือกอื่นนอกจากให้ยกเลิกตัวระบุข้อยกเว้น แต่ฉันอยากได้ยินบางเหตุผลที่ดีว่าทำไมพวกเขาถึงไม่ดีมากกว่า "ความศักดิ์สิทธิ์ของเขาได้กล่าวและมันก็เป็นเช่นนั้น" อาจมีข้อโต้แย้งกับพวกเขามากกว่าที่ฉันแสดงไว้หรืออาจจะมีพวกเขามากกว่าที่ฉันรู้


30
ฉันถูกล่อลวงให้ลงคะแนนว่า "พูดจาโผงผางปลอมตัวเป็นคำถาม" คุณถามประเด็นที่ถูกต้องสามข้อเกี่ยวกับ E.specs แต่ทำไมคุณถึงรบกวนเราด้วยภาษาซีพลัสพลัส - ramblings ที่ดีกว่าและน่ารำคาญ
Martin Ba

10
@ มาร์ติน: เพราะฉันต้องการชี้ให้เห็นว่าฉันมีความลำเอียงและไม่ทันสมัยพร้อมรายละเอียดทั้งหมด แต่ฉันก็ยังมองว่าภาษานั้นไร้เดียงสาและ / หรือยังไม่ได้ทำลายดวงตา แต่ก็เช่นกันว่า C และ C ++ เป็นภาษาที่มีข้อบกพร่องอย่างไม่น่าเชื่อดังนั้นข้อบกพร่องหนึ่งข้อที่มากหรือน้อยก็ไม่สำคัญ จริงๆแล้วโพสต์นั้นแย่กว่าเดิมมากก่อนที่ฉันจะแก้ไขมัน :) ข้อโต้แย้งกับตัวระบุข้อยกเว้นนั้นค่อนข้างเป็นอัตนัยและดังนั้นจึงเป็นการยากที่จะพูดคุยกับพวกเขาโดยไม่ทำให้ตัวเองสับสน

SpanishInquisition_t! เฮฮา! ฉันเองประทับใจกับการใช้ตัวชี้เฟรมกองซ้อนเพื่อโยนข้อยกเว้นและดูเหมือนว่ามันจะทำให้โค้ดสะอาดยิ่งขึ้น อย่างไรก็ตามฉันไม่เคยเขียนโค้ดโดยมีข้อยกเว้น โทรหาฉันแบบเก่า ๆ แต่ค่าส่งคืนใช้ได้ดีสำหรับฉัน
Shahbaz

@Shahbaz อย่างที่ใคร ๆ ก็สามารถอ่านได้ระหว่างบรรทัดฉันค่อนข้างล้าสมัยเหมือนกัน แต่ฉันก็ยังไม่เคยถามจริง ๆ ว่าข้อยกเว้นของตัวเองดีหรือไม่ดี

2
อดีตกาลของจากเส้นข้างจะโยนไม่เหวี่ยง มันเป็นคำกริยาที่แข็งแกร่ง
TRiG

คำตอบ:


48

รายละเอียดข้อยกเว้นไม่ดีเนื่องจากมีการบังคับใช้อย่างอ่อนจึงไม่ประสบความสำเร็จมากนักและยังไม่ดีเพราะบังคับให้ใช้เวลาเพื่อตรวจสอบข้อยกเว้นที่ไม่คาดคิดเพื่อให้สามารถยุติ () แทนที่จะเรียกใช้ UB สิ่งนี้อาจทำให้เสียประสิทธิภาพได้อย่างมาก

ดังนั้นโดยสรุปรายละเอียดข้อยกเว้นไม่ได้มีการบังคับใช้อย่างมากพอในภาษาที่จะทำให้รหัสปลอดภัยยิ่งขึ้นและการใช้งานตามที่ระบุไว้เป็นตัวระบายประสิทธิภาพที่ยิ่งใหญ่


2
แต่การตรวจสอบรันไทม์ไม่ได้เป็นความผิดของสเป็กยกเว้นยกเว้น แต่เป็นส่วนหนึ่งส่วนใดของมาตรฐานที่ระบุว่าควรมีการยกเลิกหากมีการโยนประเภทที่ไม่ถูกต้อง มันจะไม่ฉลาดกว่าที่จะลบความต้องการที่นำไปสู่การตรวจสอบแบบไดนามิกนี้และปล่อยให้ทุกอย่างได้รับการประเมินแบบคงที่โดยคอมไพเลอร์? ดูเหมือนว่า RTTI จะเป็นผู้ร้ายตัวจริงที่นี่หรือ ...

7
@Lundin: มันเป็นไปไม่ได้ที่จะกำหนดข้อยกเว้นทั้งหมดซึ่งอาจถูกโยนออกจากฟังก์ชั่นใน C ++ แบบคงที่เนื่องจากภาษาอนุญาตให้มีการเปลี่ยนแปลงตามอำเภอใจและไม่ จำกัด การไหลของการควบคุมที่รันไทม์ (ตัวอย่างเช่นผ่านตัวชี้ฟังก์ชั่น ชอบ). การใช้การตรวจสอบข้อยกเว้นเวลาคอมไพล์แบบคงที่เช่น Java จะต้องมีการเปลี่ยนแปลงพื้นฐานในภาษาและความเข้ากันได้กับ C จำนวนมากจะถูกปิดการใช้งาน

3
@OrbWeaver: ฉันคิดว่าการตรวจสอบคงเป็นไปได้ค่อนข้าง - spec spec จะเป็นส่วนหนึ่งของข้อมูลจำเพาะของฟังก์ชั่น (เช่นค่าตอบแทน) และตัวชี้ฟังก์ชั่นการแทนที่เสมือนจริง ฯลฯ จะต้องมีรายละเอียดที่เข้ากันได้ ข้อโต้แย้งหลักคือว่ามันเป็นคุณสมบัติที่ไม่มีอะไรเลย - ฟังก์ชั่นที่มีรายละเอียดข้อยกเว้นแบบสแตติกไม่สามารถเรียกใช้ฟังก์ชั่นดั้งเดิมได้ (การเรียกฟังก์ชั่น C นั้นใช้ได้เนื่องจากไม่สามารถโยนอะไรได้เลย)
Mike Seymour

2
@Lundin: รายละเอียดข้อยกเว้นไม่ได้ถูกลบออกเพียงแค่เลิกใช้ รหัสดั้งเดิมที่ใช้รหัสจะยังคงใช้ได้
Mike Seymour

1
@Lundin: C ++ รุ่นเก่าที่มีรายละเอียดข้อยกเว้นเข้ากันได้อย่างสมบูรณ์แบบ พวกเขาจะไม่ล้มเหลวในการรวบรวมหรือเรียกใช้โดยไม่คาดคิดหากรหัสนั้นถูกต้องมาก่อน
DeadMG

18

เหตุผลหนึ่งที่ไม่มีใครใช้พวกเขาเป็นเพราะสมมติฐานหลักของคุณผิด:

"ข้อได้เปรียบที่ชัดเจนที่สุดคือถ้าฟังก์ชั่นของคุณพยายามที่จะขว้างรูปแบบอื่นใดนอกเหนือจากที่คุณระบุไว้คอมไพเลอร์จะทำให้เกิดข้อผิดพลาด"

struct foo {};
struct bar {};

struct test
{
    void baz() throw(foo)
    {
        throw bar();
    }
};

int main()
{
    test x;
    try { x.baz(); } catch(bar &b) {}
}

โปรแกรมนี้จะรวบรวมกับข้อผิดพลาดหรือคำเตือนไม่มี

นอกจากนี้แม้ว่าข้อยกเว้นจะได้รับการติดตั้งโปรแกรมยังคงยุติลง


แก้ไข: เพื่อตอบจุดในคำถามของคุณ, catch (... ) (หรือดีกว่า, catch (std :: exeption &) หรือคลาสฐานอื่น ๆแล้ว catch (... ) ยังคงมีประโยชน์แม้ว่าคุณจะไม่ รู้ว่าเกิดอะไรขึ้น

พิจารณาว่าผู้ใช้ของคุณกดปุ่มเมนูเพื่อ "บันทึก" ตัวจัดการสำหรับปุ่มเมนูเชิญแอปพลิเคชันให้บันทึก สิ่งนี้อาจล้มเหลวเนื่องจากสาเหตุมากมาย: ไฟล์อยู่บนทรัพยากรเครือข่ายที่หายไปมีไฟล์แบบอ่านอย่างเดียวและไม่สามารถบันทึกได้เป็นต้น แต่ตัวจัดการไม่สนใจว่าทำไมบางอย่างล้มเหลว มันสนใจเพียงว่าประสบความสำเร็จหรือล้มเหลว หากประสบความสำเร็จทุกอย่างจะดี หากมันล้มเหลวก็สามารถบอกผู้ใช้ ลักษณะที่แท้จริงของข้อยกเว้นไม่เกี่ยวข้อง นอกจากนี้หากคุณเขียนรหัสที่เหมาะสมและปลอดภัยยกเว้นก็หมายความว่าข้อผิดพลาดดังกล่าวสามารถเผยแพร่ผ่านระบบของคุณโดยไม่ทำให้รหัสลงแม้แต่รหัสที่ไม่สนใจข้อผิดพลาด ทำให้ง่ายต่อการขยายระบบ ตัวอย่างเช่นตอนนี้คุณบันทึกผ่านการเชื่อมต่อฐานข้อมูล


4
@Lundin มันคอมไพล์โดยไม่มีคำเตือน และไม่คุณไม่สามารถ ไม่น่าเชื่อถือ คุณสามารถเรียกผ่านพอยน์เตอร์ของฟังก์ชั่นหรือมันอาจจะเป็นฟังก์ชั่นสมาชิกเสมือนและคลาสที่ได้รับจะถูกโยนออกมา
Kaz Dragon

โชคดี Embarcadero / Borland ให้คำเตือนสำหรับสิ่งนี้: "throw exception exception ละเมิดตัวระบุข้อยกเว้น" เห็นได้ชัดว่า GCC ไม่ได้ ไม่มีเหตุผลที่พวกเขาไม่สามารถใช้คำเตือนได้ และในทำนองเดียวกันเครื่องมือวิเคราะห์แบบสแตติกสามารถหาข้อผิดพลาดนี้ได้อย่างง่ายดาย

14
@Lundin: โปรดอย่าเริ่มเถียงและทำซ้ำข้อมูลเท็จ คำถามของคุณเป็นเรื่องคุยโวและการอ้างสิ่งเท็จไม่ได้ช่วยอะไร เครื่องมือวิเคราะห์แบบคงที่ไม่สามารถหาได้หากเกิดเหตุการณ์นี้ การทำเช่นนั้นจะเกี่ยวข้องกับการแก้ปัญหาการหยุดชะงัก ยิ่งไปกว่านั้นมันจะต้องวิเคราะห์โปรแกรมทั้งหมดและบ่อยครั้งที่แหล่งข้อมูลทั้งหมดไม่พร้อมใช้งาน
David Thornley

1
@Lundin เครื่องมือวิเคราะห์แบบคงที่เดียวกันสามารถบอกคุณได้ว่าข้อยกเว้นที่ส่งออกไปจากสแต็กของคุณดังนั้นในกรณีนี้การใช้ข้อกำหนดข้อยกเว้นยังคงไม่ซื้ออะไรเลยนอกจากความผิดพลาดเชิงลบที่อาจเกิดขึ้นซึ่งจะทำให้โปรแกรมของคุณ วิธีที่ดีกว่ามาก (เช่นการประกาศความล้มเหลวและการทำงานต่อไป: ดูครึ่งหลังของคำตอบของฉันสำหรับตัวอย่าง)
Kaz Dragon

1
@Lundin เป็นคำถามที่ดี คำตอบก็คือโดยทั่วไปแล้วคุณไม่รู้ว่าฟังก์ชั่นที่คุณกำลังโทรนั้นจะมีข้อยกเว้นหรือไม่ดังนั้นข้อสันนิษฐานที่ปลอดภัยก็คือ ไหนจะจับเหล่านี้เป็นคำถามที่ผมตอบในstackoverflow.com/questions/4025667/... ตัวจัดการเหตุการณ์ในขอบเขตโมดูลธรรมชาติแบบฟอร์มเฉพาะ การอนุญาตให้ข้อยกเว้นหลบหนีเข้าสู่หน้าต่าง / ระบบเหตุการณ์ทั่วไป (เช่น) จะถูกลงโทษ ตัวจัดการควรจับ 'em ทั้งหมดหากโปรแกรมอยู่รอดที่นั่น
Kaz Dragon

17

การสัมภาษณ์กับ Anders Hejlsbergนี้ค่อนข้างโด่งดัง ในนั้นเขาอธิบายว่าทำไมทีมออกแบบ C # ยกเลิกการตรวจสอบข้อยกเว้นตั้งแต่แรก โดยสรุปมีสองเหตุผลหลักคือความสามารถในการปรับขนาดได้

ฉันรู้ว่า OP กำลังมุ่งเน้นไปที่ C ++ ในขณะที่ Hejlsberg กำลังพูดถึง C # แต่ประเด็นที่ Hejlsberg ทำนั้นใช้ได้กับ C ++ อย่างสมบูรณ์แบบเช่นกัน


5
"ประเด็นที่ Hejlsberg ทำนั้นใช้ได้กับ C ++ อย่างสมบูรณ์แบบ" .. ผิด! การตรวจสอบข้อยกเว้นตามที่นำมาใช้ใน Java บังคับให้ผู้โทรจัดการกับพวกเขา (และ / หรือผู้โทรเข้า ฯลฯ ) รายละเอียดข้อยกเว้นใน C ++ (ในขณะที่ค่อนข้างอ่อนแอและไร้ประโยชน์) จะใช้กับ "ขอบเขตการเรียกใช้ฟังก์ชัน" เดียวเท่านั้นและไม่ "เผยแพร่สแตกการเรียก" เช่นเดียวกับข้อยกเว้นที่เลือก
Martin Ba

3
@ มาร์ติน: ฉันเห็นด้วยกับคุณเกี่ยวกับพฤติกรรมของรายละเอียดข้อยกเว้นใน C ++ แต่วลีของฉันที่คุณพูดถึง "จุดที่ Hejlsberg ทำนั้นเหมาะสมกับ C ++ เหมือนกัน" หมายถึงจุดที่ Hejlsberg ทำนั้นดีกว่า C ++ กล่าวอีกอย่างคือฉันกำลังพูดถึงที่นี่เกี่ยวกับความสามารถในการปรับขนาดได้และความสามารถในการปรับขนาดของโปรแกรมมากกว่าการแพร่กระจายยกเว้น
CesarGon

3
ฉันไม่เห็นด้วยกับ Hejlsberg เร็ว: "ความกังวลที่ฉันมีเกี่ยวกับการตรวจสอบข้อยกเว้นคือ handcuffs ที่พวกเขาใส่ไว้ในโปรแกรมเมอร์คุณจะเห็นโปรแกรมเมอร์เขียน API ใหม่ที่มีข้อผิดพลาดเหล่านี้แล้วคุณจะเห็นว่า ข้อยกเว้นที่เลือกไม่ได้ช่วยอะไรพวกมันนักออกแบบเผด็จการ API เหล่านี้บอกวิธีการจัดการข้อยกเว้นของคุณ " --- ข้อยกเว้นที่ได้รับการตรวจสอบหรือไม่คุณยังต้องจัดการกับข้อยกเว้นที่ API ที่คุณเรียกใช้ ข้อยกเว้นที่ทำเครื่องหมายไว้จะทำให้ชัดเจน
quant_dev

5
@quant_dev: ไม่คุณไม่ต้องจัดการกับข้อยกเว้นที่เกิดจาก API ที่คุณโทรหา ตัวเลือกที่ถูกต้องสมบูรณ์คือการไม่จัดการกับข้อยกเว้นและปล่อยให้พวกเขาเพิ่มสแต็คการโทรเพื่อให้ผู้โทรของคุณจัดการ
CesarGon

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