ฝึกจับผิดThrowable
หรือเปล่า
ตัวอย่างเช่นสิ่งนี้:
try {
// Some code
} catch(Throwable e) {
// handle the exception
}
นี่เป็นการปฏิบัติที่ไม่ดีหรือเราควรเจาะจงให้มากที่สุด?
ฝึกจับผิดThrowable
หรือเปล่า
ตัวอย่างเช่นสิ่งนี้:
try {
// Some code
} catch(Throwable e) {
// handle the exception
}
นี่เป็นการปฏิบัติที่ไม่ดีหรือเราควรเจาะจงให้มากที่สุด?
คำตอบ:
นี่เป็นความคิดที่ไม่ดี ในความเป็นจริงแม้แต่การจับ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);
}
throw new Exception("Some additional info, eg. userId " + userId, e);
ที่ดีมากคือการทำ สิ่งนี้จะถูกบันทึกไว้ในข้อยกเว้นที่ดีที่มีสาเหตุ 10 ประการ
นอกจากนี้โปรดทราบว่าเมื่อคุณจับThrowable
คุณสามารถจับได้InterruptedException
ซึ่งต้องได้รับการดูแลเป็นพิเศษ ดูการจัดการกับ InterruptedExceptionสำหรับรายละเอียดเพิ่มเติม
หากคุณต้องการจับเฉพาะข้อยกเว้นที่ไม่ได้เลือกคุณอาจพิจารณารูปแบบนี้ด้วย
try {
...
} catch (RuntimeException exception) {
//do something
} catch (Error error) {
//do something
}
ด้วยวิธีนี้เมื่อคุณแก้ไขโค้ดของคุณและเพิ่มการเรียกใช้เมธอดที่สามารถทำให้เกิดข้อยกเว้นที่ตรวจสอบแล้วคอมไพลเลอร์จะเตือนคุณถึงสิ่งนั้นจากนั้นคุณสามารถตัดสินใจได้ว่าจะทำอย่างไรในกรณีนี้
ตรงจาก 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
ไม่ใช่แนวทางปฏิบัติที่ไม่ดีหากคุณไม่สามารถมีฟองข้อยกเว้นออกจากวิธีการ
เป็นการปฏิบัติที่ไม่ดีหากคุณไม่สามารถจัดการกับข้อยกเว้นได้จริงๆ การเพิ่ม "การโยน" ลงในลายเซ็นของวิธีการดีกว่าเพียงแค่จับและโยนใหม่หรือแย่กว่านั้นคือห่อไว้ใน RuntimeException แล้วโยนใหม่
Throwable
อินสแตนซ์ทั้งหมดเช่นสำหรับการบันทึกข้อยกเว้นแบบกำหนดเอง
บางครั้งการจับโยนเป็นสิ่งจำเป็นหากคุณใช้ไลบรารีที่ทำให้เกิดข้อผิดพลาดอย่างกระตือรือร้นมิฉะนั้นห้องสมุดของคุณอาจฆ่าแอปพลิเคชันของคุณ
อย่างไรก็ตามมันจะเป็นการดีที่สุดภายใต้สถานการณ์เหล่านี้ในการระบุเฉพาะข้อผิดพลาดที่เกิดจากไลบรารีแทนที่จะเป็น Throwables ทั้งหมด
Throwable เป็นคลาสพื้นฐานสำหรับทุกคลาสที่สามารถโยนได้ (ไม่ใช่เฉพาะข้อยกเว้น) มีเพียงเล็กน้อยที่คุณสามารถทำได้หากคุณจับ OutOfMemoryError หรือ KernelError (ดูเมื่อจับ java.lang.Error? )
การจับข้อยกเว้นควรจะเพียงพอ
ขึ้นอยู่กับตรรกะของคุณหรือเฉพาะเจาะจงมากขึ้นสำหรับตัวเลือก / ความเป็นไปได้ของคุณ หากมีข้อยกเว้นเฉพาะใด ๆ ที่คุณสามารถตอบสนองได้อย่างมีความหมายคุณสามารถจับได้ก่อนและทำเช่นนั้น
หากไม่มีและคุณแน่ใจว่าจะทำสิ่งเดียวกันสำหรับข้อยกเว้นและข้อผิดพลาดทั้งหมด (เช่นออกด้วยข้อความแสดงข้อผิดพลาด) มากกว่าที่จะจับโยนได้ก็ไม่มีปัญหา
โดยปกติกรณีแรกจะถือและคุณจะจับโยนไม่ได้ แต่ยังมีอีกหลายกรณีที่การจับได้ผลดี
แม้ว่าจะมีการอธิบายว่าเป็นการปฏิบัติที่ไม่ดี แต่บางครั้งคุณอาจพบบางกรณีที่หาได้ยากซึ่งไม่เพียง แต่มีประโยชน์ แต่ยังจำเป็นด้วย นี่คือสองตัวอย่าง
ในเว็บแอปพลิเคชันที่คุณต้องแสดงหน้าแสดงข้อผิดพลาดที่มีความหมายต่อผู้ใช้ รหัสนี้ตรวจสอบให้แน่ใจว่าสิ่งนี้เกิดขึ้นเนื่องจากเป็นเรื่องใหญ่สำหรับ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.
}
}
อีกตัวอย่างหนึ่งให้พิจารณาว่าคุณมีชั้นบริการที่รองรับธุรกิจการโอนเงิน วิธีนี้จะส่งคืนTransferReceipt
if โอนเสร็จแล้วหรือทำ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
throwable
ได้ว่าคำถามนี้เกี่ยวกับอะไร
คำถามค่อนข้างคลุมเครือ คุณถามว่า " Throwable
จับThrowable
ได้หรือเปล่า" หรือ " จับได้แล้วไม่ทำอะไร" หลายคนที่นี่ตอบอย่างหลัง แต่นั่นเป็นปัญหาข้างเคียง 99% ของเวลาที่คุณไม่ควร "กิน" หรือยกเลิกข้อยกเว้นไม่ว่าคุณจะจับThrowable
หรือIOException
หรืออะไรก็ตาม
หากคุณเผยแพร่ข้อยกเว้นคำตอบ (เช่นเดียวกับคำตอบสำหรับคำถามจำนวนมาก) คือ "ขึ้นอยู่กับ" ขึ้นอยู่กับว่าคุณกำลังทำอะไรโดยมีข้อยกเว้น - ทำไมคุณถึงจับได้
ตัวอย่างที่ดีว่าเหตุใดคุณจึงต้องการตรวจจับThrowable
คือการจัดเตรียมการล้างข้อมูลบางประเภทหากมีข้อผิดพลาดใด ๆ ตัวอย่างเช่นใน JDBC หากข้อผิดพลาดเกิดขึ้นระหว่างธุรกรรมคุณต้องการย้อนกลับธุรกรรม:
try {
…
} catch(final Throwable throwable) {
connection.rollback();
throw throwable;
}
โปรดทราบว่าข้อยกเว้นจะไม่ถูกละทิ้ง แต่ถูกเผยแพร่
แต่ในฐานะนโยบายทั่วไปการจับได้Throwable
เพราะคุณไม่มีเหตุผลและขี้เกียจเกินไปที่จะดูว่าข้อยกเว้นใดที่ถูกโยนออกไปนั้นเป็นรูปแบบที่ไม่ดีและเป็นความคิดที่ไม่ดี
โดยทั่วไปแล้วคุณต้องการหลีกเลี่ยงการจับError
s แต่ฉันคิดได้ (อย่างน้อย) สองกรณีเฉพาะที่เหมาะสมที่จะทำเช่นนั้น:
AssertionError
ซึ่งไม่เป็นอันตรายหากเราใช้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)
Throwable คือ superclass ของข้อผิดพลาดและ excetions ทั้งหมด หากคุณใช้ Throwable ในประโยค catch จะไม่เพียงจับข้อยกเว้นทั้งหมด แต่ยังตรวจจับข้อผิดพลาดทั้งหมดด้วย ข้อผิดพลาดถูกส่งโดย JVM เพื่อบ่งชี้ปัญหาร้ายแรงที่ไม่ได้ตั้งใจให้แอปพลิเคชันจัดการ ตัวอย่างทั่วไปสำหรับสิ่งนั้นคือ OutOfMemoryError หรือ StackOverflowError ทั้งสองอย่างเกิดจากสถานการณ์ที่อยู่นอกเหนือการควบคุมของแอปพลิเคชันและไม่สามารถจัดการได้ ดังนั้นคุณไม่ควรจับ Throwables เว้นแต่ว่าคุณค่อนข้างมั่นใจว่าจะเป็นข้อยกเว้นที่อยู่ใน Throwable เท่านั้น
แม้ว่าโดยทั่วไปแล้วจะเป็นวิธีปฏิบัติที่ไม่ดีในการจับ 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
ถูกจับได้และมีการบันทึกข้อยกเว้นใด ๆ เพื่อให้ขั้นตอนที่เหลือดำเนินต่อไปได้
Exception
s โดยทุกวิถีทาง Throwable
แต่ไม่