`catch (…) {throw; } `การปฏิบัติที่ไม่ดี?


74

ในขณะที่ฉันเห็นด้วยว่าการจับ... โดยไม่ต้องทำใหม่เป็นสิ่งที่ผิด แต่ฉันเชื่อว่าการใช้โครงสร้างเช่นนี้

try
{
  // Stuff
}
catch (...)
{
  // Some cleanup
  throw;
}

เป็นที่ยอมรับในกรณีที่ RAII ไม่ได้บังคับ (โปรดอย่าถาม ... ทุกคนใน บริษัท ของฉันชอบการเขียนโปรแกรมเชิงวัตถุและ RAII มักถูกมองว่าเป็น "สิ่งที่ไร้ประโยชน์ต่อโรงเรียน" ... )

เพื่อนร่วมงานของฉันบอกว่าคุณควรรู้อยู่เสมอว่าจะมีข้อยกเว้นอะไรบ้างและคุณสามารถใช้โครงสร้างเช่น:

try
{
  // Stuff
}
catch (exception_type1&)
{
  // Some cleanup
  throw;
}
catch (exception_type2&)
{
  // Some cleanup
  throw;
}
catch (exception_type3&)
{
  // Some cleanup
  throw;
}

มีวิธีปฏิบัติที่ดีที่ยอมรับอย่างดีเกี่ยวกับสถานการณ์เหล่านี้หรือไม่?


3
@Pubby: ไม่แน่ใจว่านี่เป็นคำถามเดียวกันแน่นอน คำถามที่เชื่อมโยงนั้นเป็นเรื่องเกี่ยวกับ "ฉันควรจับได้หรือไม่..." ในขณะที่คำถามของฉันมุ่งเน้นไปที่ "ฉันควรจะจับดีขึ้น...หรือ<specific exception>ก่อนที่จะกลับมาใหม่"
ereOn

53
ขออภัยที่จะพูดเช่นนั้น แต่ C ++ ที่ไม่มี RAII ไม่ใช่ C ++
fredoverflow

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

11
"การจับ ... โดยไม่ต้องเปลี่ยนใหม่เป็นสิ่งผิด" - คุณเข้าใจผิด ในmain, catch(...) { return EXIT_FAILURE; }ดีอาจจะมีสิทธิ์ในรหัสที่ไม่ได้ทำงานภายใต้การดีบักเกอร์ หากคุณไม่จับแล้วสแต็กอาจไม่คลาย ก็ต่อเมื่อดีบักตรวจพบข้อยกเว้น uncaught mainที่คุณต้องการให้พวกเขาออก
Steve Jessop

3
... ดังนั้นแม้ว่าจะเป็น "ข้อผิดพลาดการเขียนโปรแกรม" ก็ไม่จำเป็นต้องทำตามที่คุณไม่ต้องการรู้เกี่ยวกับมัน อย่างไรก็ตามเพื่อนร่วมงานของคุณไม่ใช่ผู้เชี่ยวชาญด้านซอฟต์แวร์ที่ดีดังนั้น sbi กล่าวว่าเป็นเรื่องยากมากที่จะพูดคุยเกี่ยวกับวิธีที่ดีที่สุดในการจัดการกับสถานการณ์ที่เริ่มอ่อนแรงเรื้อรัง
Steve Jessop

คำตอบ:


196

เพื่อนร่วมงานของฉันบอกว่าคุณควรรู้อยู่เสมอว่าจะมีข้อยกเว้นอะไร [... ]

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

ว่าในโลกระดับเช่นสามารถstd::vectorแม้กระทั่งแกล้งจะรู้ว่าสิ่งก่อสร้างสำเนาจะโยนขณะที่ยังคงรับประกันความปลอดภัยยกเว้น?

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


32
จริงๆแล้วแม้ว่าพวกเขารู้ว่าจะมีการโยนข้อยกเว้น วัตถุประสงค์ของการทำสำเนารหัสนี้คืออะไร? ฉันเห็นว่าไม่มีข้อยกเว้นในการแจกแจงข้อยกเว้นเพื่ออวดความรู้ของคุณ
Michael Krelin - แฮกเกอร์

3
@ MichaelKrelin-hacker: ก็เช่นกัน นอกจากนี้ยังเพิ่มข้อเท็จจริงที่ว่าพวกเขาเลิกใช้ข้อกำหนดข้อยกเว้นเนื่องจากรายการข้อยกเว้นที่เป็นไปได้ทั้งหมดในรหัสมีแนวโน้มที่จะทำให้เกิดข้อบกพร่องในภายหลัง ... มันเป็นความคิดที่เลวร้ายที่สุดที่เคยมีมา
Mehrdad

4
และสิ่งที่รบกวนจิตใจฉันคือสิ่งที่อาจเป็นจุดกำเนิดของทัศนคติเช่นนี้เมื่อผนวกกับการเห็นเทคนิคที่มีประโยชน์และสะดวกสบายว่าเป็น "สิ่งที่ไร้ประโยชน์ในโรงเรียน" แต่ ...
Michael Krelin - แฮ็กเกอร์

1
+1 แจงนับของทุกตัวเลือกที่เป็นไปได้เป็นสูตรที่ดีสำหรับความล้มเหลวในอนาคตทำไมในโลกคนที่จะเลือกทำว่ากว่า...?
littleadv

2
คำตอบที่ดี อาจเป็นประโยชน์จากการกล่าวถึงว่าหากคอมไพเลอร์ที่ต้องให้การสนับสนุนมีข้อบกพร่องในพื้นที่ X จากนั้นการใช้ฟังก์ชันการทำงานจากพื้นที่ X จะไม่ฉลาดอย่างน้อยที่สุดก็ไม่ควรใช้โดยตรง ตัวอย่างเช่นเมื่อได้รับข้อมูลเกี่ยวกับ บริษัท ฉันจะไม่แปลกใจถ้าพวกเขาใช้ Visual C ++ 6.0 ซึ่งมี sillybugs บางส่วนในพื้นที่นี้ (เช่นตัวทำลายวัตถุที่เรียกว่าสองครั้ง) - ลูกหลานที่เล็กกว่าของแมลงตัวแรก ๆ วันนี้ แต่ต้องมีการเตรียมการอย่างระมัดระวังเพื่อให้ประจักษ์
Alf P. Steinbach

44

สิ่งที่คุณดูเหมือนจะติดอยู่คือนรกที่เฉพาะเจาะจงของคนที่พยายามมีเค้กและกินมันด้วย

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

catchคำสั่งสามารถทำหนึ่งในสองสิ่ง: การจัดการข้อผิดพลาดหรือกรณีพิเศษหรือทำผลงานทำความสะอาด บางครั้งมันทำทั้งสองอย่าง แต่ทุก ๆcatchประโยคมีอยู่ให้ทำอย่างน้อยหนึ่งอย่าง

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

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

ดังนั้นผมจะยอมรับว่าcatch(...)เป็นความชั่วร้ายส่วนใหญ่ ... ชั่วคราว

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

คุณไม่สามารถมีหนึ่งโดยไม่มีอื่น ๆ คุณไม่สามารถพูดได้ว่าทั้ง RAII และ catch(...)ไม่ดี คุณต้องการอย่างน้อยหนึ่งอย่าง มิฉะนั้นคุณจะไม่ได้รับการยกเว้นอย่างปลอดภัย

แน่นอนว่ามีการใช้งานที่ถูกต้อง แต่หายากcatch(...)ที่ไม่ใช่แม้แต่ RAII ที่สามารถขับไล่ได้: รับการexception_ptrส่งต่อไปยังผู้อื่น โดยทั่วไปผ่านpromise/futureอินเทอร์เฟซหรือคล้ายกัน

เพื่อนร่วมงานของฉันบอกว่าคุณควรรู้อยู่เสมอว่าจะมีข้อยกเว้นอะไรบ้างและคุณสามารถใช้โครงสร้างเช่น:

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

กล่าวโดยย่อ: นี่เป็นปัญหาที่ RAII ถูกสร้างขึ้นเพื่อแก้ไข (ไม่ใช่ว่ามันจะไม่แก้ปัญหาอื่น ๆ )

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

ยิ่งกว่านั้นเขากับ RAII เพราะซ่อนรายละเอียด การเรียก Destructor ไม่สามารถมองเห็นได้ทันทีในตัวแปรอัตโนมัติ ดังนั้นคุณจะได้รับรหัสที่ถูกเรียกโดยปริยาย โปรแกรมเมอร์บางคนเกลียดที่จริง เห็นได้ชัดว่าถึงจุดที่พวกเขาคิดว่ามี 3 catchคำสั่งทั้งหมดที่ทำในสิ่งเดียวกันกับรหัสคัดลอกและวางเป็นความคิดที่ดีกว่า


2
ดูเหมือนว่าคุณไม่ได้เขียนโค้ดที่ให้แข็งแกร่งรับประกันความปลอดภัยยกเว้น RAII ช่วยในการรับประกันขั้นพื้นฐาน แต่เพื่อให้การรับประกันที่ดีคุณต้องยกเลิกการดำเนินการบางอย่างเพื่อเปลี่ยนระบบกลับสู่สถานะที่เคยมีก่อนเรียกใช้ฟังก์ชัน รับประกันพื้นฐานคือการล้างข้อมูลการรับประกันที่แข็งแกร่งคือการย้อนกลับ การย้อนกลับเป็นฟังก์ชันเฉพาะ ดังนั้นคุณไม่สามารถใส่ลงใน "RAII" และนั่นคือเมื่อบล็อกcatch-allกลายเป็นประโยชน์ หากคุณเขียนรหัสที่มีการรับประกันสูงคุณจะใช้catch-allมาก
anton_rh

@anton_rh: บางที แต่แม้ในกรณีที่จับทุกงบเครื่องมือของสุดท้าย เครื่องมือที่ต้องการคือทำทุกอย่างที่เกิดขึ้นก่อนที่คุณจะเปลี่ยนสถานะใด ๆ ที่คุณจะต้องเปลี่ยนกลับยกเว้น เห็นได้ชัดว่าคุณไม่สามารถใช้งานทุกอย่างในทุกกรณี แต่เป็นวิธีที่ดีที่สุดในการรับประกันข้อยกเว้นที่แข็งแกร่ง
Nicol Bolas

14

สองความเห็นจริง ๆ อย่างแรกคือขณะที่อยู่ในโลกในอุดมคติคุณควรรู้เสมอว่ามีข้อยกเว้นอะไรบ้างในทางปฏิบัติหากคุณกำลังจัดการกับห้องสมุดบุคคลที่สามหรือคอมไพล์ด้วยคอมไพเลอร์ของ Microsoft คุณก็ไม่ควรทำ มากขึ้นถึงจุดอย่างไรก็ตาม; แม้ว่าคุณจะทราบข้อยกเว้นที่เป็นไปได้ทั้งหมดนั้นมีความเกี่ยวข้องหรือไม่ catch (...)เป็นการแสดงออกถึงความตั้งใจที่ดีกว่าcatch ( std::exception const& )แม้สมมติว่าข้อยกเว้นที่เป็นไปได้ทั้งหมดมาจาก std::exception(ซึ่งจะเป็นกรณีในโลกอุดมคติ) สำหรับการใช้ catch catch หลายบล็อกหากไม่มีฐานร่วมกันสำหรับข้อยกเว้นทั้งหมดนั่นคือการทำให้งงงวยอย่างจริงจังและฝันร้ายการบำรุงรักษา คุณรู้ได้อย่างไรว่าพฤติกรรมทั้งหมดเหมือนกัน? และนั่นคือเจตนา? และจะเกิดอะไรขึ้นถ้าคุณต้องเปลี่ยนพฤติกรรม (เช่นแก้ไขข้อผิดพลาด)? มันง่ายเกินไปที่จะพลาดไป


3
ที่จริงแล้วผู้ร่วมงานของฉันออกแบบคลาสการยกเว้นของเขาเองซึ่งไม่ได้มาจากstd::exceptionและพยายามทุกวันเพื่อบังคับใช้ในหมู่ codebase ของเรา ฉันเดาว่าเขาพยายามลงโทษฉันที่ใช้รหัสและไลบรารี่ภายนอกที่เขาไม่ได้เขียนเอง
ereOn

17
@ereOn ฟังดูเหมือนว่าเพื่อนร่วมงานของคุณกำลังต้องการการฝึกฝนอย่างหนัก ไม่ว่าในกรณีใดฉันอาจหลีกเลี่ยงการใช้ห้องสมุดที่เขาเขียน

2
เทมเพลตและรู้ว่าข้อยกเว้นอะไรที่จะถูกโยนไปด้วยกันเช่นเนยถั่วและตุ๊กแกที่ตายแล้ว บางสิ่งที่เรียบง่ายที่สุดเท่าที่std::vector<>จะทำได้สามารถยกเว้นได้ทุกเหตุผล
David Thornley

3
โปรดบอกเราว่าคุณทราบได้อย่างไรว่าข้อผิดพลาดใดที่จะถูกแก้ไขโดยการแก้ไขข้อบกพร่องในวันพรุ่งนี้ลงไปที่แผนผังการโทร?
mattnz

11

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

หมายความว่า:

try
{
  // Stuff
}
catch (...)
{
  // General stuff
}

ไม่ดีเพราะมันจะซ่อนความผิดพลาดอย่างเงียบ

อย่างไรก็ตาม:

try
{
  // Stuff
}
catch (exception_type_we_can_handle&)
{
  // Deal with the known exception
}

ไม่เป็นไร - เรารู้ว่าเรากำลังติดต่ออะไรและไม่จำเป็นต้องเปิดเผยในรหัสการโทร

ในทำนองเดียวกัน:

try
{
  // Stuff
}
catch (...)
{
  // Rollback transactions, log errors, etc
  throw;
}

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


9

คำตอบใด ๆ เกี่ยวกับใช่หรือไม่ใช่ควรมีเหตุผลว่าทำไมจึงเป็นเช่นนี้

การพูดว่ามันผิดเพราะฉันได้รับการสอนในลักษณะที่เป็นเพียงความคลั่งไคล้ตาบอด

การเขียน//Some cleanup; throwหลาย ๆ ครั้งเหมือนในตัวอย่างของคุณผิดเพราะการทำสำเนารหัสและนั่นคือภาระการบำรุงรักษา การเขียนเพียงครั้งเดียวจะดีกว่า

การเขียนcatch(...)ข้อยกเว้นทั้งหมดเป็นความผิดเพราะคุณควรจัดการข้อยกเว้นที่คุณรู้จักวิธีการจัดการเท่านั้นและด้วยสัญลักษณ์แทนนั้นคุณสามารถกำหนดได้มากกว่าที่คุณคาดหวัง

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

ที่จริงแล้วฉันได้ทำสิ่งนี้เพื่อเข้าสู่ระบบในฟังก์ชั่นที่ละเอียดอ่อนโดยไม่มีปัญหาใด ๆ :

void DoSomethingImportant()
{
    try
    {
        Log("Going to do something important");
        DoIt();
    }
    catch (std::exception &e)
    {
        Log("Error doing something important: %s", e.what());
        throw;
    }
    catch (...)
    {
        Log("Unexpected error doing something important");
        throw;
    }
    Log("Success doing something important");
}

2
หวังว่าLog(...)ไม่สามารถโยนได้
Deduplicator

2

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

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

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

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