เป็นการดีที่จะตรวจสอบข้อยกเว้นที่ตรวจสอบแล้วโยน RuntimeException หรือไม่?


70

ฉันอ่านรหัสของเพื่อนร่วมงานและพบว่าเขามักจะจับข้อยกเว้นต่าง ๆ และจากนั้นก็โยน 'RuntimeException' แทน ฉันมักจะคิดว่านี่เป็นวิธีปฏิบัติที่ไม่ดีมาก ฉันผิดหรือเปล่า?


17
"ราคาของข้อยกเว้นที่ตรวจสอบนั้นเป็นการละเมิดหลักการเปิด / ปิดหากคุณโยนข้อยกเว้นที่ตรวจสอบจากวิธีการในรหัสของคุณและการดักจับเป็นสามระดับด้านบนคุณต้องประกาศข้อยกเว้นนั้นในลายเซ็นของแต่ละวิธีระหว่างคุณและจับ ซึ่งหมายความว่าการเปลี่ยนแปลงในระดับต่ำของซอฟต์แวร์สามารถบังคับให้มีการเปลี่ยนแปลงลายเซ็นในระดับที่สูงขึ้นมากมาย " —Robert C. Martin, « Clean Code », หน้า 107
Songo

6
เป็นที่น่าสนใจที่จะทราบว่า Jim Waldo คุยโวกับข้อยกเว้นที่ไม่ได้ตรวจสอบใน "Java: The Good Parts" shop.oreilly.com/product/9780596803742.doบอกว่าโปรแกรมเมอร์ผู้ใหญ่ควรโยนข้อยกเว้นที่ตรวจสอบแล้วเท่านั้น เราอ่านมันในเหยือกของเราเมื่อ 6 ปีก่อนเมื่อมันออกมาและดูเหมือนคำแนะนำที่ดี! ขณะนี้ด้วยการตั้งโปรแกรมการทำงานข้อยกเว้นที่ตรวจสอบแล้วนั้นไม่ได้ใช้อย่างสมบูรณ์ ภาษาอย่าง Scala และ Kotlin ไม่มีแม้แต่ภาษาเหล่านั้น ฉันได้เริ่มการตัดคำด้วยการตรวจสอบในข้อยกเว้นที่ไม่ได้ตรวจสอบแล้วเช่นกัน
GlenPeterson

@GlenPeterson คุณมีคำแนะนำใน FP เพื่อหลีกเลี่ยง execetions ทั้งหมดและใช้ผลรวมประเภทแทน
jk

นอกจากนี้ยังมีกรณีที่เห็นได้ชัดของการเชื่อมต่อการทำงาน: การเชื่อมต่อการทำงานในตัว (เช่นFunction, Predicateฯลฯ ) ไม่ได้มี parametrized พ่นคำสั่ง ซึ่งหมายความว่าคุณจำเป็นต้องจับห่อและสร้างข้อยกเว้นที่เลือกใหม่ในลูปด้านในของวิธีการสตรีม () ในและของตัวเองเคล็ดลับสมดุลสำหรับฉันว่าการตรวจสอบข้อยกเว้นเป็นความคิดที่ดี
Joel Cornett

ไม่มีอะไรผิดปกติกับการสร้างคลาสย่อยที่กำหนดเองของ RuntimeException เพื่อสื่อสารความหมายผ่านข้อยกเว้นของคุณ
Joel Cornett

คำตอบ:


55

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

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

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

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


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

6
นั่นเป็นความจริง @MichaelBorgwardt แต่สถานที่สำหรับการจัดการประเภทนั้นมักจะอยู่ในระดับสูงสุดของแอปพลิเคชันดังนั้นเมื่อใดก็ตามที่ฉันเห็นข้อยกเว้น "การจัดการ" ของนักพัฒนาในระดับต่ำกว่าปกติแล้วมันง่ายที่จะลบการจัดการ สูงกว่า ตัวอย่างเช่นเว็บเฟรมเวิร์กเช่น JSF จะจับข้อยกเว้นในระดับสูงสุดพิมพ์ข้อความบันทึกและดำเนินการตามคำขออื่น ๆ ต่อไป (ไม่ได้บอกว่าการจัดการเริ่มต้นเหมาะสมสำหรับตัวอย่างเท่านั้น)
DavidS

40

มันสามารถที่ดี กรุณาอ่าน:

http://onjava.com/pub/a/onjava/2003/11/19/exceptions.html

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

public void dataAccessCode(){
  try{
      ..some code that throws SQLException
  }catch(SQLException ex){
      ex.printStacktrace();
  }
} 

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

public void dataAccessCode(){
   try{
       ..some code that throws SQLException
   }catch(SQLException ex){
       throw new RuntimeException(ex);
   }
} 

สิ่งนี้จะแปลง SQLException เป็น RuntimeException ถ้า SQLException เกิดขึ้น catch clause จะเรียกใช้ RuntimeException ใหม่ เธรดการดำเนินการถูกระงับและข้อยกเว้นจะได้รับการรายงาน อย่างไรก็ตามฉันไม่ได้ทำลายเลเยอร์วัตถุธุรกิจของฉันด้วยการจัดการข้อยกเว้นที่ไม่จำเป็นโดยเฉพาะอย่างยิ่งเนื่องจากมันไม่สามารถทำอะไรเกี่ยวกับ SQLException ได้ ถ้า catch ของฉันต้องการสาเหตุข้อยกเว้น root ฉันสามารถใช้เมธอด getCause () ในคลาสยกเว้นทั้งหมดตั้งแต่ JDK1.4

การโยนข้อยกเว้นที่เลือกและไม่สามารถกู้คืนได้จะไม่ช่วย

บางคนคิดว่าไม่ควรใช้ข้อยกเว้นที่ตรวจสอบแล้ว ดู http://www.ibm.com/developerworks/java/library/j-jtp05254/index.html

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

จากลิงค์เดียวกัน:

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


13

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


1
ขอบคุณสำหรับคำใบ้ และถ้าเขาโยนข้อยกเว้นแบบกำหนดเองที่เขาใช้ไปซึ่งสืบทอดโดยตรงจาก RuntimeException
RoflcoptrException

27
@Gary Buyn: หลายคนคิดว่าการตรวจสอบข้อยกเว้นมีการทดสอบการออกแบบภาษาล้มเหลวและพวกเขาจะเป็นคนที่ควรจะใช้เท่าที่จำเป็นไม่ได้เป็นเรื่องของนิสัย
Michael Borgwardt

7
@Gary Buyn: นี่เป็นบทความที่สรุปการอภิปรายค่อนข้างดี: ibm.com/developerworks/java/library/j-jtp05254/index.htmlโปรดทราบว่ากว่า 15 ปีหลังจาก Java ได้เปิดตัวคุณลักษณะนี้ไม่มีภาษาอื่นที่นำมาใช้ และ C ++ เลิกใช้ฟีเจอร์ที่คล้ายกัน
Michael Borgwardt

7
@c_maker: ที่จริงแล้ว Bloch ส่วนใหญ่ส่งเสริมมุมมองดั้งเดิมและความคิดเห็นของคุณดูเหมือนจะเกี่ยวกับการใช้ข้อยกเว้นเพิ่มเติมระยะเวลา มุมมองของฉันคือเหตุผลที่ถูกต้องเท่านั้นที่จะใช้ข้อยกเว้นที่ตรวจสอบได้คือเงื่อนไขที่คุณคาดว่าผู้โทรทั้งหมดจะจัดการทันที
Michael Borgwardt

14
การโยนทิ้งโดยไม่ได้ตรวจสอบที่ไม่จำเป็นทำให้ละเมิดการห่อหุ้ม คุณจะทำอย่างไรถ้าวิธีง่ายๆเช่น 'getAccounts ()' ส่ง 'SQLException', 'NullPointerException' หรือ 'FileNotFoundException' ให้คุณ? คุณรับมือมันได้ไหม? คุณอาจจะแค่ 'จับ (ข้อยกเว้น e) {}' มัน นอกจากนี้ข้อยกเว้นเหล่านั้น - การใช้งานที่เฉพาะเจาะจง! มันไม่ควรเป็นส่วนหนึ่งของสัญญา! ทั้งหมดที่คุณต้องทราบว่าเป็นที่มีข้อผิดพลาด และจะเกิดอะไรขึ้นถ้าการดำเนินการเปลี่ยนแปลง? ทันใดนั้นทุกอย่างก็เปลี่ยนไปทำให้วิธีไม่เปลี่ยนเป็น 'SQLException' อีกต่อไป แต่เป็น 'ParseXMLException' แทน!
KL

8

มันขึ้นอยู่กับ.

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

ในทางกลับกันหากข้อยกเว้นไม่ใช่ runtime (ทำเครื่องหมายไว้) ผู้พัฒนา API ระบุว่าข้อยกเว้นนี้สามารถแก้ไขได้และควรได้รับการซ่อมแซม ถ้าเป็นไปได้มากกว่าที่คุณควรทำแน่นอน

วิธีแก้ปัญหาอื่นอาจจะ rethrow ข้อยกเว้นที่ตรวจสอบนี้ในเลเยอร์การโทร แต่ถ้าคุณไม่สามารถแก้ไขได้ที่ข้อยกเว้นเกิดขึ้นคุณจะไม่สามารถแก้ไขได้ที่นี่ ...


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

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

2
ขอบคุณสำหรับการกล่าวถึงexception handling layer- เช่นใน webapp ตัวกรอง
Jake โตรอนโต

5

ฉันต้องการได้รับความคิดเห็นเกี่ยวกับเรื่องนี้ แต่ฉันพบว่ามีบางครั้งที่นี่ไม่ได้เป็นวิธีปฏิบัติที่ไม่ดี (หรือแย่มาก) แต่บางทีฉันผิด

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

สมมติว่า RuntimeException ไม่ถูกตรวจจับและเพิกเฉยในภายหลังไม่มีที่ไหนใกล้กับ OnErrorResumeNext

OnErrorResumeNext จะเกิดขึ้นเมื่อมีคนจับข้อยกเว้นและเพียงละเว้นหรือพิมพ์ออกมา นี่เป็นวิธีปฏิบัติที่แย่มากในเกือบทุกกรณี


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

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

4

TL; DR

หลักฐาน

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

ข้อสรุป

เราอาจ rethrow ข้อยกเว้นที่ตรวจสอบเป็นข้อยกเว้นรันไทม์หากรหัส propagating หรืออินเตอร์เฟซที่ถือว่าการใช้งานพื้นฐานขึ้นอยู่กับสถานะภายนอกเมื่อมันไม่ชัดเจน


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

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

ตัวอย่างเช่นมีความเหมาะสมที่จะโยนข้อยกเว้นรันไทม์สำหรับสิ่งต่อไปนี้:

float nan = 1/0;

สิ่งนี้จะทำให้เกิดการหารด้วยข้อยกเว้นรันไทม์ของศูนย์ สิ่งนี้เหมาะสมเนื่องจากรหัสมีข้อบกพร่อง

หรือตัวอย่างเช่นนี่เป็นส่วนหนึ่งของคอนHashMapสตรัคเตอร์:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                loadFactor);
    // more irrelevant code...
}

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

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

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

ลองมาตัวอย่างที่กล่าวถึงในคำตอบ mrmuggles:

public void dataAccessCode(){
   try{
       ..some code that throws SQLException
   }catch(SQLException ex){
       throw new RuntimeException(ex);
   }
}

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

public Data dataAccessCode() throws SQLException {
    // some code that communicates with the database
}

ซึ่งช่วยให้เป็นไปได้ของการกู้คืนโดยโทร:

public void loadDataAndShowUi() {
    try {
        Data data = dataAccessCode();
        showUiForData(data);
    } catch(SQLException e) {
        // Recover by showing an error alert dialog
        showCantLoadDataErrorDialog();
    }
}

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

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

ตอนนี้เรามีความแตกต่างที่ชัดเจนนี้เราอาจดำเนินการสรุปเมื่อมันตกลงที่จะ rethrow ข้อยกเว้นที่ตรวจสอบเป็นข้อยกเว้นรันไทม์


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

พิจารณาสิ่งต่อไปนี้:

StringReader sr = new StringReader("{\"test\":\"test\"}");
try {
    doesSomethingWithReader(sr); // calls #read, so propagates IOException
} catch (IOException e) {
    throw new IllegalStateException(e);
}

ในตัวอย่างนี้รหัสกำลังเผยแพร่IOExceptionเนื่องจาก API ของReaderถูกออกแบบมาเพื่อเข้าถึงสถานะภายนอกอย่างไรก็ตามเรารู้ว่าStringReaderการใช้งานไม่สามารถเข้าถึงสถานะภายนอก ในขอบเขตนี้ซึ่งเราสามารถยืนยันได้อย่างแน่นอนว่าชิ้นส่วนที่เกี่ยวข้องในการโทรไม่สามารถเข้าถึง IO หรือสถานะภายนอกอื่น ๆ เราสามารถสร้างข้อยกเว้นใหม่ได้อย่างปลอดภัยเป็นข้อยกเว้นแบบรันไทม์โดยไม่มีเพื่อนร่วมงานที่น่าทึ่งที่ไม่รู้ สมมติว่ารหัสการเข้าถึง IO จะส่งผลให้IOException)


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


2

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


2

นี่คือการปฏิบัติทั่วไปในหลายกรอบ เช่นHibernateทำสิ่งนี้ แนวคิดคือ APIs ไม่ควรล่วงล้ำสำหรับฝั่งไคลเอ็นต์และExceptionรบกวนเนื่องจากคุณต้องเขียนรหัสอย่างชัดเจนเพื่อจัดการพวกเขาในสถานที่ที่คุณเรียก API แต่สถานที่นั้นอาจไม่ใช่สถานที่ที่เหมาะสมในการจัดการข้อยกเว้นในสถานที่แรก
ความจริงแล้วเป็นเรื่องจริงนี่คือหัวข้อ "ร้อนแรง" และข้อพิพาทมากมายดังนั้นฉันจะไม่เข้าข้าง แต่ฉันจะบอกว่าสิ่งที่เพื่อนของคุณทำ / เสนอคือไม่ธรรมดาหรือไม่ธรรมดา


1

สิ่งที่ "ตรวจสอบข้อยกเว้น" ทั้งหมดเป็นความคิดที่ไม่ดี

การเขียนโปรแกรมแบบมีโครงสร้างอนุญาตให้ส่งผ่านข้อมูลระหว่างฟังก์ชั่น (หรือใน Java parlance เมธอดเท่านั้น) เมื่อมีการ "ใกล้เคียง" แม่นยำยิ่งขึ้นข้อมูลสามารถย้ายข้ามฟังก์ชันได้สองวิธี:

  1. จากผู้โทรถึงผู้รับสายผ่านการส่งอาร์กิวเมนต์

  2. จากผู้โทรไปยังผู้โทรเป็นค่าส่งคืน

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

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

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


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

0

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

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

ตัวอย่างเช่น Integer.parseInt (String) รับสตริงและส่งคืนจำนวนเต็มเทียบเท่ากับมันแล้วส่ง NumberFormatException ในกรณีที่สตริงไม่ใช่ตัวเลข ตอนนี้ลองนึกภาพการส่งแบบฟอร์มที่มีเขตข้อมูลageจะถูกแปลงผ่านวิธีนี้ แต่ลูกค้าจะได้รับการยืนยันการตรวจสอบแล้วในส่วนของมันจึงไม่มีจุดบังคับให้ตรวจสอบข้อยกเว้น


0

มีคำถามสองสามข้อที่นี่จริงๆ

  1. คุณควรเปลี่ยนข้อยกเว้นที่เลือกให้เป็นข้อ จำกัด ที่ไม่ได้ตรวจสอบหรือไม่?

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

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

  1. หากคุณจะเปลี่ยนข้อยกเว้นที่ไม่ได้ตรวจสอบให้เป็นข้อยกเว้นที่ตรวจสอบแล้วคุณควรทำอย่างไร

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

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


0

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

ในช่วงหลายปีที่ผ่านมาฉันพบว่ามีใครบางคนสับสนเกี่ยวกับปัญหาเล็ก ๆ น้อย ๆ ที่พวกเขาขาดความเข้าใจพื้นฐานบางอย่าง

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

สมมติว่าแอปของคุณมีเลเยอร์ต่อไปนี้จากล่างขึ้นบน NET, TCP, HTTP, REST, DATA MODEL, BUSINESS

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

มีอะไรเพิ่มเติมมันไม่ดีถ้าฉันรู้รายละเอียดเพราะถ้าพรุ่งนี้ฉันต้องการเปลี่ยนโปรโตคอลการขนส่งที่ขีดเส้นใต้ที่จัดการกับรายละเอียดเฉพาะโปรโตคอล TCP นั่นหมายความว่า REST นามธรรมของฉันไม่ได้ทำงานที่ดี การใช้งานเฉพาะ

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

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


-2

ในความเห็นของฉัน,

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

ในระดับแอปพลิเคชันเรามักจะจับข้อยกเว้นรันไทม์และฉันคิดว่าวิธีนี้ไม่ดี


1
และคุณจะทำอะไรกับข้อยกเว้นเหล่านั้นในระดับกรอบ
Matthieu

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