วิธีการที่ทันสมัยในการจัดการข้อผิดพลาด ...


118

ฉันไตร่ตรองปัญหานี้มาระยะหนึ่งแล้วและพบว่าตัวเองกำลังค้นหาคำเตือนและความขัดแย้งอยู่เรื่อย ๆ ดังนั้นฉันจึงหวังว่าจะมีคนสรุปผลได้ดังต่อไปนี้:

ชอบข้อยกเว้นเกี่ยวกับรหัสข้อผิดพลาด

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

แต่สำหรับฉันนี่ดูเหมือนจะขัดแย้ง ...

การเข้ารหัสไปยังอินเตอร์เฟสไม่ใช่การนำไปใช้งาน

เราใช้รหัสในส่วนต่อประสานหรือบทคัดย่อเพื่อลดการแต่งงาน เราไม่ทราบหรือต้องการทราบประเภทและการนำไปใช้งานของอินเทอร์เฟซที่เฉพาะเจาะจง แล้วเราจะรู้ได้อย่างไรว่ามีข้อยกเว้นอะไรบ้างที่เราควรจับตา? การใช้งานอาจมีข้อยกเว้นแตกต่างกัน 10 ข้อหรือไม่สามารถทิ้งได้ เมื่อเราได้รับการยกเว้นแน่นอนว่าเรากำลังตั้งสมมติฐานเกี่ยวกับการนำไปปฏิบัติ

เว้นแต่ - ส่วนต่อประสานมี ...

ข้อกำหนดข้อยกเว้น

บางภาษาอนุญาตให้นักพัฒนาระบุว่าวิธีการบางอย่างมีข้อยกเว้นบางอย่าง (เช่น Java ใช้throwsคำหลัก) จากมุมมองของรหัสการโทรสิ่งนี้ดูดี - เรารู้อย่างชัดเจนว่าข้อยกเว้นใดที่เราอาจต้องตรวจจับ

แต่ - ดูเหมือนว่าจะแนะนำ ...

นามธรรมรั่วไหล

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

ดังนั้น...

สรุป

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


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

@deceze อินเตอร์เฟสอาจบอกได้อย่างไรว่าการใช้งานอาจเกิดขึ้นได้อย่างไร? มันอาจส่งข้อยกเว้นทั้งหมดในระบบพิมพ์! และมีหลายภาษาที่ไม่รองรับข้อกำหนดข้อยกเว้นแนะนำว่าพวกเขาค่อนข้างหลบหลีก
RichK

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

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

คำตอบ:


31

ก่อนอื่นฉันไม่เห็นด้วยกับข้อความนี้:

ชอบข้อยกเว้นเกี่ยวกับรหัสข้อผิดพลาด

นี่ไม่ใช่กรณีเสมอไปตัวอย่างเช่นดูที่ Objective-C (พร้อมกับเฟรมเวิร์กของมูลนิธิ) มี NSError เป็นวิธีที่ต้องการในการจัดการข้อผิดพลาดแม้จะมีสิ่งที่นักพัฒนา Java จะเรียกข้อยกเว้นที่แท้จริง: @try, @catch, @throw, NSException class ฯลฯ

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

จัดการกับข้อผิดพลาด / ข้อยกเว้นในระดับระยะเวลาที่ต่ำที่สุด

ฉันคิดว่าถ้าใครยึดติดกับกฎง่ายๆขนาดของจำนวน "การรั่ว" จาก abstractions อาจมี จำกัด และมีอยู่มาก

ไม่ว่าจะมีข้อยกเว้นโยนโดยวิธีการควรเป็นส่วนหนึ่งของการประกาศของมันฉันเชื่อว่าพวกเขาควร: พวกเขาเป็นส่วนหนึ่งของสัญญาที่กำหนดโดยอินเตอร์เฟซนี้: วิธีนี้จะ A หรือล้มเหลวด้วย B หรือ C

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

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

นอกจากนี้ฉันคิดว่าผู้สร้าง Java มีเหตุผลด้านความปลอดภัยที่แข็งแกร่งมากในการรวมข้อยกเว้นในการประกาศ / คำนิยามวิธีการ

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


12
+1 สำหรับการปฏิเสธ "ความโปรดปรานยกเว้นรหัสข้อผิดพลาด" ฉันได้รับการสอนแล้วว่าข้อยกเว้นนั้นดีและดีตราบใดที่จริงแล้วข้อยกเว้นไม่ใช่กฎ การเรียกใช้เมธอดที่ส่งข้อยกเว้นเพื่อพิจารณาว่าเงื่อนไขเป็นจริงหรือไม่นั้นเป็นวิธีปฏิบัติที่ไม่ดีอย่างยิ่ง
Neil

4
@JoshuaDrake: เขาผิดแน่นอน ข้อยกเว้นและgotoแตกต่างกันมาก ตัวอย่างเช่นข้อยกเว้นจะไปในทิศทางเดียวกันทุกครั้งที่สแต็คการโทร และประการที่สองการปกป้องตัวคุณเองจากข้อยกเว้นประหลาดใจนั้นเป็นแบบเดียวกันกับ DRY- ตัวอย่างเช่นใน C ++ ถ้าคุณใช้ RAII เพื่อให้แน่ใจว่ามีการล้างข้อมูลแล้วมันจะช่วยให้แน่ใจว่าการล้างข้อมูลในทุกกรณีไม่ใช่แค่ข้อยกเว้น นี่คือความน่าเชื่อถือมากขึ้นอย่างไม่มีที่สิ้นสุด try/finallyทำบางสิ่งบางอย่างที่ค่อนข้างคล้ายกัน เมื่อคุณรับประกันการล้างข้อมูลอย่างถูกต้องคุณไม่จำเป็นต้องพิจารณาข้อยกเว้นเป็นกรณีพิเศษ
DeadMG

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

25
-1 เพราะฉันไม่เห็นด้วยกับ "จัดการกับข้อผิดพลาด / ข้อยกเว้นในระดับที่ต่ำที่สุด" - ผิดระยะเวลา จัดการกับข้อผิดพลาด / ข้อยกเว้นในระดับที่เหมาะสม บ่อยครั้งที่อยู่ในระดับที่สูงขึ้นมากและในกรณีนั้นรหัสข้อผิดพลาดเป็นความเจ็บปวดอย่างมาก เหตุผลในการสนับสนุนข้อยกเว้นรหัสข้อผิดพลาดก็คือพวกเขาช่วยให้คุณสามารถเลือกได้อย่างอิสระในระดับที่จะจัดการกับพวกเขาโดยไม่ส่งผลกระทบต่อระดับในระหว่าง
Michael Borgwardt

2
@Giorgio: ดูartima.com/intv/handcuffs.html - โดยเฉพาะอย่างยิ่งหน้า 2 และ 3
Michael Borgwardt

27

ฉันต้องการทราบว่าข้อยกเว้นและรหัสข้อผิดพลาดไม่ใช่วิธีเดียวที่จะจัดการกับข้อผิดพลาดและเส้นทางรหัสอื่น

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

func x = do
    a <- operationThatMightFail 10
    b <- operationThatMightFail 20
    c <- operationThatMightFail 30
    return (a + b + c)

operationThatMightfail เป็นฟังก์ชันที่ส่งคืนค่าที่ห่อในอาจ มันทำงานเหมือนตัวชี้ nullable แต่เครื่องหมายที่รับประกันว่าสิ่งทั้งหมดประเมินเป็นโมฆะถ้า a, b หรือ c ล้มเหลว (และคอมไพเลอร์ปกป้องคุณจากการสร้าง NullPointerException โดยไม่ได้ตั้งใจ)

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

LISP ทั่วไปทำสิ่งนี้และทำให้มันเป็นไปได้โดยการสนับสนุน syntatic (ข้อโต้แย้งโดยนัย) และการมีฟังก์ชั่นในตัวเป็นไปตามโปรโตคอลนี้


1
นั่นมันเรียบร้อยจริงๆ ขอบคุณที่ตอบคำถามเกี่ยวกับทางเลือก :) :)
RichK

1
BTW ข้อยกเว้นเป็นหนึ่งในส่วนที่ใช้งานได้ดีที่สุดของภาษาคำสั่งที่ทันสมัย ข้อยกเว้นในรูปแบบ monad ข้อยกเว้นใช้การจับคู่แบบมีลาย ข้อยกเว้นช่วยเลียนแบบสไตล์การสมัครโดยไม่ได้เรียนรู้สิ่งที่Maybeเป็นจริง
9000

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

8

ใช่ข้อยกเว้นอาจทำให้เกิด abstractions ที่รั่ว แต่รหัสข้อผิดพลาดไม่ได้เลวร้ายยิ่งกว่าในเรื่องนี้?

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

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

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


1
ฉันไม่เข้าใจว่าทำไมรหัสข้อผิดพลาดอาจทำให้เกิดสิ่งที่เป็นนามธรรมรั่วไหล หากมีการส่งคืนรหัสข้อผิดพลาดแทนค่าส่งคืนปกติและลักษณะการทำงานนี้อธิบายไว้ในข้อมูลจำเพาะของฟังก์ชัน / วิธีแสดงว่า IMO ไม่มีการรั่วไหล หรือฉันจะมองอะไร
Giorgio

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

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

1
@Giorgio: ใช่การรายงานข้อผิดพลาดเฉพาะของการใช้งานนั้นยุ่งยากกับ API ที่ไม่เชื่อเรื่องพระเจ้า ยังคุณสามารถทำได้โดยมีข้อยกเว้นเพราะ (ต่างจากรหัสข้อผิดพลาด) พวกเขาสามารถมีได้หลายช่อง ดังนั้นคุณสามารถใช้ชนิดของข้อยกเว้นเพื่อให้ข้อมูลข้อผิดพลาดทั่วไป (ResourceMissingException) และรวมรหัส / ข้อความ errorn เฉพาะการนำไปใช้เป็นฟิลด์ ดีที่สุดของทั้งสองโลก :-)
sleske

และ BTW นั่นคือสิ่งที่ java.lang.SQLException ทำ มันมีgetSQLState(ทั่วไป) และgetErrorCode(เฉพาะผู้ขาย) ตอนนี้ถ้าเพียง แต่มันมี subclasses ที่เหมาะสม ...
sleske

5

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

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

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

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


4

นามธรรมรั่วไหล

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

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

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


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

@missingno: นั่นเป็นเพราะตามปกติ Java มีการใช้งานที่น่ากลัวของพวกเขาไม่ได้เพราะข้อยกเว้นตรวจสอบจะไม่ดีโดยเนื้อแท้
DeadMG

1
@missingno: ปัญหาประเภทใดที่อาจเกิดจากข้อยกเว้นที่ตรวจสอบแล้ว
จอร์โจ

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

@missingno: ฟังก์ชั่นไม่ระบุชื่อ AFAIK ใน Java เป็นเพียงน้ำตาลประโยคสำหรับคลาสภายในที่ไม่ระบุชื่อด้วยวิธีการหนึ่งเดียวดังนั้นฉันไม่แน่ใจว่าฉันเข้าใจว่าทำไมการยกเว้นการตรวจสอบจะเป็นปัญหา ใช่มันยากที่จะคาดการณ์ว่าจะมีข้อยกเว้นวิธีใดซึ่งเป็นสาเหตุที่ IMO มีประโยชน์ในการตรวจสอบข้อยกเว้นเพื่อที่คุณจะได้ไม่ต้องเดา แน่นอนคุณสามารถเขียนโค้ดที่ไม่ดีพอที่จะจัดการมันได้ แต่สิ่งนี้คุณสามารถทำได้ด้วยข้อยกเว้นที่ไม่ได้ตรวจสอบ อย่างไรก็ตามฉันรู้ว่าการอภิปรายค่อนข้างซับซ้อนและสุจริตฉันเห็นข้อดีข้อเสียทั้งสองด้าน
Giorgio

2

การจัดการข้อยกเว้นที่ดีสามารถมีการใช้อินเตอร์เฟสของตัวเอง ขึ้นอยู่กับประเภทของข้อยกเว้นที่เกิดขึ้นให้ปฏิบัติตามขั้นตอนที่ต้องการ

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

การใช้รหัสข้อผิดพลาดเป็นวิธีการจัดการข้อยกเว้นดั้งเดิม มันเหมือนกับการใช้สตริงกับตัวสร้างสตริง


4
ในคำอื่น ๆ : การใช้งานโยนคลาสย่อยของข้อยกเว้นที่กำหนดไว้ใน API
andrew cooke

2

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

การอนุญาตให้ผู้เจาะทำลายการควบคุมของคุณอาจไม่ให้ประโยชน์ที่แท้จริงใด ๆ และอาจไม่มีวิธีที่มีความหมายในการจัดการกับข้อยกเว้น สำหรับตัวอย่างโดยตรงหากมีการใช้รูปแบบการสังเกต (ในภาษาเช่น C # ที่คุณมีเหตุการณ์ทุกที่และไม่ชัดเจนthrowsในคำจำกัดความ) ไม่มีเหตุผลจริงที่จะให้ผู้สังเกตการณ์ทำลายการควบคุมของคุณหากมันล้มเหลวและ ไม่มีวิธีที่มีความหมายในการจัดการกับสิ่งต่าง ๆ ของพวกเขา (แน่นอนเพื่อนบ้านที่ดีไม่ควรโยนเมื่อสังเกต แต่ไม่มีใครสมบูรณ์แบบ)

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

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

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

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

จากมุมมองทางทฤษฎีฉันคิดว่าวิธีการจัดการ SoC-kosher นั้นสามารถได้มาโดยตรงจากการสังเกตว่าผู้โทรโดยตรงส่วนใหญ่สนใจเพียงแค่ว่าคุณล้มเหลวไม่ใช่เพราะอะไร ผู้ขว้างปาคนที่อยู่ใกล้ด้านบน (2-3 เฟรม) จับรุ่น upcasted และข้อยกเว้นที่เกิดขึ้นจริงมักจะจมลงไปที่ตัวจัดการข้อผิดพลาดพิเศษ มีแนวโน้มที่จะเป็นแนวนอน


1

ชอบข้อยกเว้นเกี่ยวกับรหัสข้อผิดพลาด

  • ทั้งคู่ควรอยู่ร่วมกัน

  • ส่งคืนรหัสข้อผิดพลาดเมื่อคุณคาดหวังพฤติกรรมบางอย่าง

  • ส่งคืนข้อยกเว้นเมื่อคุณไม่ได้คาดหวังพฤติกรรมบางอย่าง

  • รหัสข้อผิดพลาดมักจะเกี่ยวข้องกับข้อความเดียวเมื่อประเภทยกเว้นยังคงอยู่ แต่ข้อความอาจแตกต่างกันไป

  • ข้อยกเว้นมีการติดตามสแต็กเมื่อรหัสข้อผิดพลาดไม่ ฉันไม่ใช้รหัสข้อผิดพลาดเพื่อแก้ไขข้อบกพร่องของระบบ

การเข้ารหัสไปยังส่วนต่อประสานที่ไม่ได้ใช้งาน

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

เมื่อเราได้รับการยกเว้นแน่นอนว่าเรากำลังตั้งสมมติฐานเกี่ยวกับการนำไปปฏิบัติ

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

จะเกิดอะไรขึ้นถ้าการนำไปปฏิบัติไม่จำเป็นต้องมีข้อยกเว้นหรือต้องการโยนข้อยกเว้นอื่น ๆ

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

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


1

นามธรรมรั่วไหล

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

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

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


1

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

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

สรุป:

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

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

รหัสข้อผิดพลาดเป็นส่วนหนึ่งของสัญญาระหว่างผู้โทร m1 และ callee m2: รหัสข้อผิดพลาดที่เป็นไปได้จะถูกกำหนดไว้ในอินเตอร์เฟสของ m2 เท่านั้น ด้วยข้อยกเว้น (ยกเว้นว่าคุณกำลังใช้ Java และประกาศข้อยกเว้นที่ส่งออกทั้งหมดในลายเซ็นวิธีการ) คุณมีสัญญาโดยนัยระหว่างผู้เรียก m1 และวิธีการทั้งหมดที่สามารถเรียกใช้โดย m2 เรียกซ้ำ ดังนั้นจึงเป็นความผิดพลาดที่จะไม่ตรวจสอบรหัสข้อผิดพลาดที่ส่งคืน แต่เป็นไปได้เสมอที่จะทำ ในทางกลับกันมันเป็นไปไม่ได้ที่จะตรวจสอบข้อยกเว้นทั้งหมดที่ถูกโยนโดยวิธีการเสมอไปเว้นแต่คุณจะรู้วิธีการนำไปใช้
Giorgio

ขั้นแรก: คุณสามารถตรวจสอบข้อยกเว้นทั้งหมด - เพียงแค่ทำ "การจัดการข้อยกเว้นโปเกมอน" (ต้องจับพวกเขาทั้งหมด - เช่นcatch Exceptionหรือแม้กระทั่งThrowableหรือเทียบเท่า)
sleske

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

-1

ลำเอียงถูกต้อง

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

Downside? รหัสที่มีการจัดการข้อผิดพลาดที่เหมาะสมดูน่าขยะแขยง (จริงของกลไกทั้งหมด)


สิ่งนี้ดูเหมือนจะไม่นำเสนออะไรมากมายเกินกว่าที่ทำคะแนนและอธิบายไว้ใน 10 คำตอบก่อนหน้านี้ ทำไมต้องถามคำถามสองปีกับเรื่องแบบนั้น
gnat

ยกเว้นที่นี่ไม่มีใครพูดถึงลำเอียงที่ถูกต้องเช่นกัน hugomg พูดคุยเกี่ยวกับ haskell อย่างใกล้ชิดอย่างไรก็ตามอาจเป็นตัวจัดการข้อผิดพลาดแบบอึเนื่องจากไม่มีคำอธิบายว่าทำไมข้อผิดพลาดเกิดขึ้นหรือวิธีการใด ๆ โดยตรงที่จะกู้คืนและ call-backs เป็นหนึ่งในบาปที่ยิ่งใหญ่ที่สุดในการออกแบบการควบคุมการไหล และคำถามนี้เกิดขึ้นใน google
Keynan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.