การเผยแพร่ข้อยกเว้น: เมื่อใดที่ฉันควรตรวจจับข้อยกเว้น


44

เมธอด A เรียกเมธอด B ซึ่งจะเรียกเมธอด C

ไม่มีการจัดการข้อยกเว้นใน MethodB หรือ MethodC แต่มีข้อยกเว้นการจัดการใน MethodA

ใน MethodC มีข้อยกเว้นเกิดขึ้น

ตอนนี้ข้อยกเว้นนั้นทำให้เดือดร้อนขึ้นไปที่ MethodA ซึ่งจัดการมันอย่างเหมาะสม

เกิดอะไรขึ้นกับสิ่งนี้?

ในใจของฉันในบางจุดผู้เรียกจะเรียกใช้เมธอด B หรือ MethodC และเมื่อเกิดข้อยกเว้นขึ้นในวิธีการเหล่านั้นสิ่งที่จะได้รับจากการจัดการข้อยกเว้นภายในวิธีการเหล่านั้นซึ่งโดยพื้นฐานแล้วเป็นเพียงลอง / จับ / ในที่สุดบล็อก พวกเขาฟองขึ้นไปที่ callee?

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

ฉันเข้าใจเมื่อคุณต้องการเพิ่มทรัพยากร นั่นเป็นเรื่องที่แตกต่างอย่างสิ้นเชิง


46
ทำไมคุณคิดว่าฉันทามติที่จะมีห่วงโซ่ของการผ่านการจับ?
Caleth

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

14
หากวิธีการไม่สามารถจัดการกับข้อยกเว้นและเป็นเพียง rethrowing ฉันจะบอกว่าเป็นกลิ่นรหัส หากวิธีการไม่สามารถจัดการกับข้อยกเว้นและไม่จำเป็นต้องทำอะไรอย่างอื่นเมื่อโยนข้อยกเว้นแล้วก็ไม่จำเป็นต้องมีtry-catchบล็อกเลย
Greg Burghardt

7
"มีอะไรผิดปกติกับเรื่องนี้?" : ไม่มีอะไร
Ewan

5
Pass-through catch (ที่ไม่ได้มีข้อยกเว้นในประเภทที่แตกต่างกันหรืออะไรทำนองนั้น) เอาชนะจุดประสงค์ทั้งหมดของข้อยกเว้น การขว้างข้อยกเว้นเป็นกลไกที่ซับซ้อนและมันถูกสร้างขึ้นโดยเจตนา หากการจับ pass-through เป็นกรณีการใช้งานที่ตั้งใจไว้สิ่งที่คุณต้องมีก็คือการใช้Result<T>ชนิด (ชนิดที่เก็บผลลัพธ์ของการคำนวณหรือข้อผิดพลาด) และส่งคืนจากฟังก์ชันการขว้างของคุณ การเผยแพร่ข้อผิดพลาดขึ้นสแต็กจะเกี่ยวข้องกับการอ่านทุกค่าส่งคืนตรวจสอบว่ามีข้อผิดพลาดและส่งกลับข้อผิดพลาดถ้าเป็นเช่นนั้น
Alexander

คำตอบ:


139

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

เหตุผลเพียงอย่างเดียวที่วิธีการนั้นควรมีกลไกการตรวจจับและรื้อถอนคือ:

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

มิฉะนั้นการจับข้อยกเว้นในระดับที่ไม่ถูกต้องมีแนวโน้มที่จะส่งผลให้เกิดรหัสที่ล้มเหลวอย่างเงียบ ๆ โดยไม่ต้องให้ข้อเสนอแนะที่เป็นประโยชน์ใด ๆ กับรหัสโทร (และท้ายที่สุดผู้ใช้ซอฟต์แวร์) ทางเลือกของการจับข้อยกเว้นและจากนั้น rethrowing ทันทีนั้นไม่มีจุดหมาย


28
@GregBurghardt หากภาษาของคุณมีบางอย่างที่คล้ายtry ... finally ...กันจากนั้นใช้สิ่งนั้นไม่จับ & rethrow
Caleth

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

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

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

14
@Voo คุณดูเหมือนจะพลาด "คุณต้องการแปลงข้อยกเว้นหนึ่งเป็นอีกข้อหนึ่งที่มีความหมายกับผู้โทรมาด้านบนมากกว่า" เหตุผลสำหรับสถานการณ์จับ / เปิดใหม่
jpmc26

21

เกิดอะไรขึ้นกับสิ่งนี้?

ไม่มีอะไรจริงๆ.

ตอนนี้ข้อยกเว้นนั้นทำให้เดือดร้อนขึ้นไปที่ MethodA ซึ่งจัดการมันอย่างเหมาะสม

"จัดการกับมันอย่างเหมาะสม" เป็นส่วนสำคัญ นั่นคือปมของการจัดการข้อยกเว้นเชิงโครงสร้าง

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

. . . ทำไมไม่ลองจับข้อยกเว้นต่อไปให้โซ่แทนที่จะลอง / จับบล็อกจนสุด

นั่นคือสิ่งที่คุณควรทำ หากคุณกำลังอ่านโค้ดที่มี handler / rethrowers "ลงมา" คุณก็อาจจะอ่านโค้ดที่แย่

น่าเศร้าที่นักพัฒนาบางคนเห็นว่าบล็อก catch เป็นรหัส "boiler-plate" ที่พวกเขาโยน (ไม่มีการเล่นสำนวน) สำหรับทุกวิธีที่พวกเขาเขียนบ่อยครั้งเพราะพวกเขาไม่ได้ "รับ" การจัดการข้อยกเว้นและคิดว่าพวกเขาต้องเพิ่มอะไรลงไป ข้อยกเว้นไม่ได้ "หลบหนี" และฆ่าโปรแกรมของพวกเขา

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


7
มีอะไรที่แย่กว่านั้นคือเมื่อแอปพลิเคชันจับข้อยกเว้นจากนั้นให้บันทึก (ซึ่งหวังว่ามันจะไม่อยู่ที่นั่นตลอดไป) และพยายามที่จะดำเนินการต่อตามปกติแม้ว่ามันจะทำไม่ได้ก็ตาม
โซโลมอน Ucko

1
@SolomonUcko: ขึ้นอยู่กับมัน หากตัวอย่างเช่นคุณกำลังเขียนเซิร์ฟเวอร์ RPC อย่างง่ายและข้อยกเว้นที่ไม่สามารถจัดการได้จนถึงจุดวนรอบเหตุการณ์หลักตัวเลือกที่เหมาะสมเท่านั้นของคุณคือบันทึกล็อกส่งข้อผิดพลาด RPC ไปยังเพียร์ระยะไกลและดำเนินการกิจกรรมการประมวลผลต่อ อีกทางเลือกหนึ่งคือการฆ่าโปรแกรมทั้งหมดซึ่งจะผลักดัน SREs ของคุณเมื่อเซิร์ฟเวอร์ตายในการผลิต
เควิน

@Kevin ในกรณีนั้นควรมีcatchระดับสูงสุดที่เป็นไปได้ที่จะบันทึกข้อผิดพลาดและส่งกลับการตอบสนองข้อผิดพลาด ไม่catchบล็อกโรยทุกที่ หากคุณไม่ต้องการแสดงรายการทุกข้อยกเว้นที่ตรวจสอบได้ (ในภาษาเช่น Java) เพียงแค่ห่อมันไว้ในRuntimeExceptionแทนที่จะล็อกไว้ที่นั่นพยายามดำเนินการต่อและพบข้อผิดพลาดเพิ่มเติมหรือแม้แต่ช่องโหว่
โซโลมอน Ucko

8

คุณต้องสร้างความแตกต่างระหว่าง Library และ Applications

ห้องสมุดสามารถโยนข้อยกเว้นที่ไม่ได้ตรวจสอบได้อย่างอิสระ

เมื่อคุณออกแบบห้องสมุดในบางครั้งคุณต้องคิดเกี่ยวกับสิ่งที่ผิดพลาด พารามิเตอร์อาจอยู่ในช่วงที่ไม่ถูกต้องหรือnullทรัพยากรภายนอกอาจไม่พร้อมใช้งาน ฯลฯ

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

แอปพลิเคชันควรเสมอในบางจุดจับข้อยกเว้น

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

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

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


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

1
@VLAZ สิ่งที่เป็นอันตรายถึงชีวิตและไม่ร้ายแรงเป็นสิ่งที่แอปพลิเคชั่นต้องตัดสินใจ ห้องสมุดควรบอกสิ่งที่เกิดขึ้น แอปพลิเคชันต้องตัดสินใจว่าจะตอบโต้อย่างไร
MechMK1

0

มีอะไรผิดปกติกับรูปแบบที่คุณอธิบายคือวิธีการ A จะไม่มีวิธีแยกแยะระหว่างสถานการณ์จำลองทั้งสาม:

  1. วิธีการ B ล้มเหลวในแบบที่คาดการณ์ไว้

  2. วิธีการ C ล้มเหลวในแบบที่ไม่ได้คาดการณ์ไว้โดยวิธี B แต่ในขณะที่วิธี B กำลังทำการผ่าตัดที่สามารถละทิ้งได้อย่างปลอดภัย

  3. เมธอด C ล้มเหลวในแบบที่ไม่ได้คาดการณ์ไว้โดยเมธอด B แต่ขณะที่เมธอด B กำลังดำเนินการที่วางสิ่งต่าง ๆ ไว้ในสถานะที่ไม่ต่อเนื่องกันชั่วคราวซึ่ง B ไม่สามารถล้างข้อมูลได้เนื่องจากความล้มเหลวของ C

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


2
สถานการณ์ที่ 2 และ 3 เป็นข้อบกพร่องในวิธี B วิธี A ไม่ควรพยายามแก้ไขสิ่งเหล่านั้น
Sjoerd

@Sjoerd: วิธี B ควรจะคาดการณ์ทุกวิธีที่วิธี C อาจล้มเหลวได้อย่างไร
supercat

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

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

@Sjoerd วิธี B กลายเป็นเรื่องง่ายขึ้นมากในการให้เหตุผลว่าข้อยกเว้นถูกห้ามโดยภาษา เพราะในกรณีนั้นคุณจะเห็นเส้นทางการควบคุมทั้งหมดผ่านทาง B และคุณไม่จำเป็นต้องคาดเดาว่าตัวดำเนินการใดที่จะโอเวอร์โหลดในการขว้างปา (C ++) เป็นครั้งที่สองเพื่อหลีกเลี่ยงสถานการณ์ที่ 3 เรากำลังจ่ายเงินเป็นจำนวนมาก ความชัดเจนและความปลอดภัยเพื่อให้ขี้เกียจเกี่ยวกับการส่งคืนข้อผิดพลาดโดย "เพียงแค่" โยนข้อยกเว้น เพราะในที่สุดการจัดการข้อผิดพลาดเป็นส่วนสำคัญของรหัส
cmaster
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.