การจับ Throwable เป็นการปฏิบัติที่ไม่ดีหรือไม่?


107

ฝึกจับผิดThrowableหรือเปล่า

ตัวอย่างเช่นสิ่งนี้:

try {
    // Some code
} catch(Throwable e) {
    // handle the exception
}

นี่เป็นการปฏิบัติที่ไม่ดีหรือเราควรเจาะจงให้มากที่สุด?


5
ที่เกี่ยวข้อง: stackoverflow.com/questions/581878/…และstackoverflow.com/questions/2146108/…
finnw

คำตอบ:


104

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

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


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

7
คุณรู้ได้อย่างไรว่ามีการจัดสรรอะไรบ้างและอะไรที่ไม่เคยมีมาก่อน OOME การเดิมพันทั้งหมดจะปิดลงทันทีที่คุณได้รับสิ่งนั้นแม้จะอยู่ในคอนเทนเนอร์ J2EE เช่น Tomcat หรือ JBoss
bmauter

10
เรามี NoSuchMethodError และโชคดีที่ไม่ได้กำจัดลูกค้าของเราทั้งหมดโดยการปิดเซิร์ฟเวอร์เนื่องจากเกิดขึ้นสองสัปดาห์หลังจากการปรับใช้ กล่าวคือ. เรามักจะจับโยนได้และพยายามอย่างเต็มที่ในการจัดการและส่งข้อผิดพลาดไปยังลูกค้า ท้ายที่สุดมีข้อผิดพลาดหลายประเภทที่สามารถกู้คืนได้เนื่องจากอาจมีผลต่อลูกค้า 1 ใน 1,000 รายเท่านั้น
Dean Hiller

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

3
@assylias แอปพลิเคชันแบบสแตนด์อโลนจะส่งข้อผิดพลาดร้ายแรงไปยัง stderr
Philip Whitehouse

37

นี่เป็นความคิดที่ไม่ดี ในความเป็นจริงแม้แต่การจับExceptionก็เป็นความคิดที่ไม่ดี ลองพิจารณาตัวอย่าง:

try {
    inputNumber = NumberFormat.getInstance().formatNumber( getUserInput() );
} catch(Throwable e) {
    inputNumber = 10; //Default, user did not enter valid number
}

ตอนนี้สมมติว่า getUserInput () บล็อกชั่วขณะและเธรดอื่นหยุดเธรดของคุณด้วยวิธีที่เลวร้ายที่สุด (เรียกว่า thread.stop ()) บล็อกการจับของคุณจะเกิดThreadDeathข้อผิดพลาด นี่แย่สุด ๆ พฤติกรรมของโค้ดของคุณหลังจากจับได้ว่า Exception ส่วนใหญ่ไม่ได้กำหนดไว้

ปัญหาที่คล้ายกันเกิดขึ้นกับการตรวจจับข้อยกเว้น อาจgetUserInput()ล้มเหลวเนื่องจาก InterruptException หรือการอนุญาตปฏิเสธข้อยกเว้นขณะพยายามบันทึกผลลัพธ์หรือความล้มเหลวอื่น ๆ ทุกประเภท คุณไม่รู้ว่าเกิดอะไรขึ้นเพราะเหตุนี้คุณจึงไม่รู้วิธีแก้ไขปัญหา

คุณมีสามทางเลือกที่ดีกว่า:

1 - จับข้อยกเว้นที่คุณรู้วิธีจัดการ:

try {
    inputNumber = NumberFormat.getInstance().formatNumber( getUserInput() );
} catch(ParseException e) {
    inputNumber = 10; //Default, user did not enter valid number
}

2 - ยกเลิกข้อยกเว้นที่คุณพบและไม่รู้วิธีจัดการ:

try {
    doSomethingMysterious();
} catch(Exception e) {
    log.error("Oh man, something bad and mysterious happened",e);
    throw e;
}

3 - ใช้การบล็อกในที่สุดเพื่อที่คุณจะได้ไม่ต้องจำไว้ว่าต้องสร้างใหม่:

 Resources r = null;
 try {
      r = allocateSomeResources();
      doSomething(r);
 } finally {
     if(r!=null) cleanUpResources(r);
 }

4
+1 สำหรับการระบุว่าแม้แต่การจับ Exception ก็ไม่ดี อย่างน้อยก็มี ThreadInterruptedException ซึ่งต้องการการดูแลเป็นพิเศษ (ในระยะสั้น - หลังจากจับได้คุณต้องตั้งค่าสถานะการขัดจังหวะของเธรดกลับเป็น 'จริง')
Kirill Gamazkov

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

ฉันจะรู้ได้อย่างไรว่ามีข้อผิดพลาด / โยนได้ถ้าฉันจับไม่ได้? ฉันไม่เห็นอะไรเลยในท่อนไม้ แอป Java EE ใช้เวลาหลายวันติดอยู่โดยไม่รู้ว่าปัญหาคืออะไรจนกว่าฉันจะเพิ่มการจับนี้
Philip Rego

2
ฉันถือว่าตัวเลือก # 2 เป็นการปฏิบัติที่ไม่ดี ลองนึกภาพการโทรที่ถูกล่ามโซ่ 10 ครั้งพร้อมบันทึกและการโทรใหม่ หากคุณดูไฟล์บันทึกคุณจะไม่มีความสุข มีการบันทึกข้อยกเว้น 10 ครั้งทำให้อ่านบันทึกได้ยากมาก IMHO throw new Exception("Some additional info, eg. userId " + userId, e);ที่ดีมากคือการทำ สิ่งนี้จะถูกบันทึกไว้ในข้อยกเว้นที่ดีที่มีสาเหตุ 10 ประการ
Petr Újezdský

21

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

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

try {
   ...
} catch (RuntimeException exception) {
  //do something
} catch (Error error) {
  //do something
}

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


14

ตรงจาก javadoc ของคลาส Error (ซึ่งไม่แนะนำให้จับสิ่งเหล่านี้):

 * An <code>Error</code> is a subclass of <code>Throwable</code> 
 * that indicates serious problems that a reasonable application 
 * should not try to catch. Most such errors are abnormal conditions. 
 * The <code>ThreadDeath</code> error, though a "normal" condition,
 * is also a subclass of <code>Error</code> because most applications
 * should not try to catch it. 

 * A method is not required to declare in its <code>throws</code> 
 * clause any subclasses of <code>Error</code> that might be thrown 
 * during the execution of the method but not caught, since these 
 * errors are abnormal conditions that should never occur. 
 *
 * @author  Frank Yellin
 * @version %I%, %G%
 * @see     java.lang.ThreadDeath
 * @since   JDK1.0

13

ไม่ใช่แนวทางปฏิบัติที่ไม่ดีหากคุณไม่สามารถมีฟองข้อยกเว้นออกจากวิธีการ

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


10
เห็นด้วยอย่างยิ่ง - มีกรณีที่ถูกต้องตามกฎหมายสำหรับการจัดการThrowableอินสแตนซ์ทั้งหมดเช่นสำหรับการบันทึกข้อยกเว้นแบบกำหนดเอง
Yuriy Nakonechnyy

9

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

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


12
หรือใช้ไลบรารีเขียนดีกว่า?
Raedwald

6
แน่นอนถ้าคุณมีทางเลือก ;-)
DNA

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

6

Throwable เป็นคลาสพื้นฐานสำหรับทุกคลาสที่สามารถโยนได้ (ไม่ใช่เฉพาะข้อยกเว้น) มีเพียงเล็กน้อยที่คุณสามารถทำได้หากคุณจับ OutOfMemoryError หรือ KernelError (ดูเมื่อจับ java.lang.Error? )

การจับข้อยกเว้นควรจะเพียงพอ


5

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

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

โดยปกติกรณีแรกจะถือและคุณจะจับโยนไม่ได้ แต่ยังมีอีกหลายกรณีที่การจับได้ผลดี


4

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

ในเว็บแอปพลิเคชันที่คุณต้องแสดงหน้าแสดงข้อผิดพลาดที่มีความหมายต่อผู้ใช้ รหัสนี้ตรวจสอบให้แน่ใจว่าสิ่งนี้เกิดขึ้นเนื่องจากเป็นเรื่องใหญ่สำหรับtry/catchตัวจัดการคำขอของคุณทั้งหมด (servlets, struts actions หรือคอนโทรลเลอร์ใด ๆ .... )

try{
     //run the code which handles user request.
   }catch(Throwable ex){
   LOG.error("Exception was thrown: {}", ex);
     //redirect request to a error page. 
 }

}

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

String FoundtransferService.doTransfer( fundtransferVO);

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

for(FundTransferVO fundTransferVO : fundTransferVOList){
   FoundtransferService.doTransfer( foundtransferVO);
}

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

for(FundTransferVO fundTransferVO : fundTransferVOList){
    FoundtransferService.doTransfer( foundtransferVO);
 }catch(Throwable ex){
    LOG.error("The transfer for {} failed due the error {}", foundtransferVO, ex);
  }
}

คุณสามารถเรียกดูโครงการโอเพนซอร์สจำนวนมากเพื่อดูว่าthrowableมีการแคชและจัดการจริงๆ ยกตัวอย่างเช่นที่นี่คือการค้นหาของtomcat, struts2และprimefaces:

https://github.com/apache/tomcat/search?utf8=%E2%9C%93&q=catch%28Throwable https://github.com/apache/struts/search?utf8=%E2%9C%93&q=catch % 28Throwable https://github.com/primefaces/primefaces/search?utf8=%E2%9C%93&q=catch%28Throwable


1
เห็นรหัสในลิงก์เหล่านี้ โยนได้ไม่ใช่แค่คนเดียวที่จับได้! มีข้อยกเว้นอื่น ๆ ที่จับได้ก่อนที่จะโยนได้
พัฒนา

@ developer101 แน่นอน แต่พวกเขาจับthrowableได้ว่าคำถามนี้เกี่ยวกับอะไร
Alireza Fattahi

4

คำถามค่อนข้างคลุมเครือ คุณถามว่า " ThrowableจับThrowableได้หรือเปล่า" หรือ " จับได้แล้วไม่ทำอะไร" หลายคนที่นี่ตอบอย่างหลัง แต่นั่นเป็นปัญหาข้างเคียง 99% ของเวลาที่คุณไม่ควร "กิน" หรือยกเลิกข้อยกเว้นไม่ว่าคุณจะจับThrowableหรือIOExceptionหรืออะไรก็ตาม

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

ตัวอย่างที่ดีว่าเหตุใดคุณจึงต้องการตรวจจับThrowableคือการจัดเตรียมการล้างข้อมูลบางประเภทหากมีข้อผิดพลาดใด ๆ ตัวอย่างเช่นใน JDBC หากข้อผิดพลาดเกิดขึ้นระหว่างธุรกรรมคุณต้องการย้อนกลับธุรกรรม:

try {
  
} catch(final Throwable throwable) {
  connection.rollback();
  throw throwable;
}

โปรดทราบว่าข้อยกเว้นจะไม่ถูกละทิ้ง แต่ถูกเผยแพร่

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


1

โดยทั่วไปแล้วคุณต้องการหลีกเลี่ยงการจับErrors แต่ฉันคิดได้ (อย่างน้อย) สองกรณีเฉพาะที่เหมาะสมที่จะทำเช่นนั้น:

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

0

หากเราใช้Throwableมันก็จะครอบคลุมErrorด้วยเช่นกัน

ตัวอย่าง.

    public class ExceptionTest {
/**
 * @param args
 */
public static void m1() {
    int i = 10;
    int j = 0;
    try {
        int k = i / j;
        System.out.println(k);
    } catch (Throwable th) {
        th.printStackTrace();
    }
}

public static void main(String[] args) {
    m1();
}

}

เอาท์พุต:

java.lang.ArithmeticException: / by zero
at com.infy.test.ExceptionTest.m1(ExceptionTest.java:12)
at com.infy.test.ExceptionTest.main(ExceptionTest.java:25)

0

Throwable คือ superclass ของข้อผิดพลาดและ excetions ทั้งหมด หากคุณใช้ Throwable ในประโยค catch จะไม่เพียงจับข้อยกเว้นทั้งหมด แต่ยังตรวจจับข้อผิดพลาดทั้งหมดด้วย ข้อผิดพลาดถูกส่งโดย JVM เพื่อบ่งชี้ปัญหาร้ายแรงที่ไม่ได้ตั้งใจให้แอปพลิเคชันจัดการ ตัวอย่างทั่วไปสำหรับสิ่งนั้นคือ OutOfMemoryError หรือ StackOverflowError ทั้งสองอย่างเกิดจากสถานการณ์ที่อยู่นอกเหนือการควบคุมของแอปพลิเคชันและไม่สามารถจัดการได้ ดังนั้นคุณไม่ควรจับ Throwables เว้นแต่ว่าคุณค่อนข้างมั่นใจว่าจะเป็นข้อยกเว้นที่อยู่ใน Throwable เท่านั้น


-1

แม้ว่าโดยทั่วไปแล้วจะเป็นวิธีปฏิบัติที่ไม่ดีในการจับ Throwable (ตามที่อธิบายได้จากคำตอบมากมายในคำถามนี้) สถานการณ์ที่การจับThrowableเป็นประโยชน์นั้นค่อนข้างบ่อย ให้ฉันอธิบายกรณีดังกล่าวที่ฉันใช้ในงานของฉันด้วยตัวอย่างที่เรียบง่าย

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

public Integer addNumbers(Integer a, Integer b) {
    Integer c = a + b;          //This will throw a NullPointerException if either 
                                //a or b are set to a null value by the
                                //calling method
    successfulAdditionAlert(c);
    return c;
}

private void successfulAdditionAlert(Integer c) {
    try {
        //Code here to read configurations and send email alerts.
    } catch (Throwable e) {
        //Code to log any exception that occurs during email dispatch
    }
}

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


ฉันจะพยายามหลีกเลี่ยงปัญหานี้โดยมีเธรดเฉพาะสำหรับงาน (พร้อมคิว) ในการส่งอีเมล
boumbh

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