Có phải là một thực hành xấu để bắt Throwable?


Câu trả lời:


104

Bạn cần phải càng cụ thể càng tốt. Nếu không, những con bọ không lường trước có thể biến mất theo cách này.

Bên cạnh đó, các Throwabletrang bìa Errorcũng vậy và điều đó thường không có lợi . Bạn không muốn bắt / xử lý điều đó, bạn muốn chương trình của mình chết ngay lập tức để có thể sửa chữa nó đúng cách.


38
Có những tình huống mà việc bắt lỗi và tiếp tục là phù hợp. Ví dụ: Trong một servlet, nếu bạn chèn một OutOfMemoryError vì một yêu cầu cụ thể xảy ra ăn tất cả bộ nhớ, bạn có thể cố gắng tiếp tục vì các đối tượng sẽ là GC sau khi yêu cầu được xử lý. Tương tự đối với lỗi xác nhận. Bạn không tắt ứng dụng vì đã xảy ra sự cố trong một yêu cầu.
gawi

7
Làm thế nào để bạn biết những gì đã được phân bổ và những gì không có trước OOME? Tất cả các cược sẽ tắt khi bạn nhận được điều đó, ngay cả trong một thùng chứa J2EE như Tomcat hoặc JBoss.
bmauter

10
Chúng tôi đã có NoSuchMethodError và rất may đã không loại bỏ tất cả khách hàng của chúng tôi bằng cách tắt máy chủ vì nó đã xảy ra hai tuần sau khi triển khai. I E. Chúng tôi luôn có thông tin bắt được Throwable và nỗ lực cao nhất để xử lý và gửi lỗi cho khách hàng. Rốt cuộc, có rất nhiều loại Lỗi có thể khôi phục được trong đó nó có thể chỉ ảnh hưởng đến 1 trong số 1000 khách hàng.
Dean Hiller

10
" bạn muốn chương trình của bạn chết ngay lập tức để bạn có thể sửa chữa nó đúng cách " => nếu chương trình của bạn chết, làm thế nào để bạn biết điều gì đã xảy ra? Bắt Throwable / Lỗi đăng nhập vấn đề là một điều hợp lý để làm ...
assylias

3
@assylias Một ứng dụng độc lập sẽ gây ra một lỗi nghiêm trọng cho stderr.
Philip Whitehouse

36

Đây là một ý tưởng tồi. Trong thực tế, thậm chí bắt Exceptionthường là một ý tưởng tồi. Hãy xem xét một ví dụ:

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

Bây giờ, giả sử rằng getUserInput () chặn một lúc và một luồng khác dừng luồng của bạn theo cách tồi tệ nhất có thể (nó gọi thread.stop ()). Khối bắt của bạn sẽ gặp ThreadDeathlỗi. Thật là tệ. Hành vi của mã của bạn sau khi bắt được Ngoại lệ đó phần lớn là không xác định.

Một vấn đề tương tự xảy ra với việc bắt Exception. Có lẽgetUserInput() không thành công do InterruptException hoặc ngoại lệ bị từ chối cấp quyền trong khi cố gắng ghi lại kết quả hoặc tất cả các loại lỗi khác. Bạn không biết điều gì đã xảy ra, vì vậy, bạn cũng không biết làm thế nào để khắc phục sự cố.

Bạn có ba lựa chọn tốt hơn:

1 - Nắm bắt chính xác (các) Ngoại lệ mà bạn biết cách xử lý:

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

2 - Rethrow bất kỳ ngoại lệ nào mà bạn gặp phải và không biết cách xử lý:

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

3 - Sử dụng một khối cuối cùng để bạn không phải nhớ đánh lại:

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

4
+1 vì nói rằng ngay cả việc bắt Ngoại lệ cũng không tốt. Ít nhất có một ThreadInterruptedException đòi hỏi chăm sóc đặc biệt (trong ngắn hạn - sau khi bắt nó, bạn phải thiết lập chủ đề của tình trạng lại bị gián đoạn để 'true')
Kirill Gamazkov

Tôi biết nó chỉ để minh họa cho lời nói của bạn nhưng tôi nghĩ bạn có thể kiểm tra bằng regex xem thông tin đầu vào của người dùng là Chữ và số hay định dạng bạn cần và không sử dụng, hãy thử bắt mọi nơi mọi lúc.
amdev

Làm cách nào để biết được là có Lỗi / Có thể ném nếu tôi không nắm bắt được? Tôi không thấy gì trong nhật ký. Ứng dụng Java EE. Chỉ dành nhiều ngày bị mắc kẹt không biết vấn đề là gì cho đến khi tôi thêm bắt này.
Philip Rego

2
Tôi coi tùy chọn số 2 là cách làm không tốt. Hãy tưởng tượng 10 cuộc gọi được xâu chuỗi với log & rethrow. Nếu bạn nhìn vào tệp nhật ký, bạn sẽ không hài lòng. Ngoại lệ là ghi nhật ký 10 lần làm cho nhật ký rất khó đọc. IMHO tốt hơn nhiều là phải làm throw new Exception("Some additional info, eg. userId " + userId, e);. Điều này sẽ được đăng nhập trong một ngoại lệ tốt đẹp với 10 nguyên nhân.
Petr Újezdský

21

Cũng cần lưu ý rằng khi bạn bắt Throwable, bạn cũng có thể bắt InterruptedExceptionđược cần phải điều trị đặc biệt. Xem Xử lý ngoại lệ bị gián đoạn để biết thêm chi tiết.

Nếu bạn chỉ muốn nắm bắt các trường hợp ngoại lệ không được chọn, bạn cũng có thể xem xét mẫu này

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

Bằng cách này, khi bạn sửa đổi mã của mình và thêm một lệnh gọi phương thức có thể đưa ra một ngoại lệ đã được kiểm tra, trình biên dịch sẽ nhắc bạn về điều đó và sau đó bạn có thể quyết định phải làm gì cho trường hợp này.


14

trực tiếp từ javadoc của lớp Lỗi (khuyến nghị không mắc các lỗi này):

 * 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

Đó không phải là một thực tiễn tồi nếu bạn hoàn toàn không thể có bong bóng ngoại lệ trong một phương pháp.

Đó là một thực tiễn tồi nếu bạn thực sự không thể xử lý ngoại lệ. Tốt hơn nên thêm "ném" vào chữ ký phương thức hơn là chỉ bắt và ném lại hoặc tệ hơn, bọc nó trong một RuntimeException và ném lại.


10
Hoàn toàn đồng ý - có những trường hợp hoàn toàn hợp pháp để xử lý tất cả các Throwabletrường hợp - ví dụ: ghi nhật ký ngoại lệ tùy chỉnh.
Yuriy Nakonechnyy

9

Đôi khi, việc nắm bắt Throwable là cần thiết nếu bạn đang sử dụng các thư viện ném lỗi một cách quá nhiệt tình, nếu không thư viện của bạn có thể giết ứng dụng của bạn.

Tuy nhiên, tốt nhất là trong những trường hợp này, chỉ nên chỉ định các lỗi cụ thể do thư viện ném ra, thay vì tất cả các Bảng ném.


12
Hoặc sử dụng một thư viện viết tốt hơn?
Raedwald

6
Thật vậy, nếu bạn có sự lựa chọn ;-)
DNA

đó là vấn đề lớn nhất với việc bắt và ném lại chúng. nó thực sự tạo ra một giao diện không thể cho tất cả các phương thức ngược dòng trong ngăn xếp. Họ hoặc phải đối phó với một cú ném có thể ném được hoặc có một chữ ký ném không thể sử dụng được mà những người khác sẽ phải đối phó với.
Andrew Norman

6

Có thể ném là lớp cơ sở cho tất cả các lớp có thể được ném (không chỉ ngoại lệ). Bạn chỉ có thể làm rất ít điều nếu bắt gặp lỗi OutOfMemoryError hoặc KernelError (xem Khi nào bắt được java.lang.Error? )

bắt Ngoại lệ phải là đủ.


5

nó phụ thuộc vào logic của bạn hoặc cụ thể hơn cho các tùy chọn / khả năng của bạn. Nếu có bất kỳ ngoại lệ cụ thể nào mà bạn có thể phản ứng theo cách có ý nghĩa, bạn có thể nắm bắt nó trước và làm như vậy.

Nếu không có và bạn chắc chắn rằng bạn sẽ làm điều tương tự đối với tất cả các ngoại lệ và lỗi (ví dụ: thoát với thông báo lỗi), thì không vấn đề gì khi bắt được tệp có thể ném.

Thường thì trường hợp đầu tiên giữ được và bạn sẽ không bắt được vật ném được. Nhưng vẫn có rất nhiều trường hợp bắt nó hoạt động tốt.


4

Mặc dù nó được mô tả là một thực hành rất xấu, đôi khi bạn có thể tìm thấy những trường hợp hiếm hoi mà nó không chỉ hữu ích mà còn bắt buộc. Đây là hai ví dụ.

Trong một ứng dụng web, nơi bạn phải hiển thị trang lỗi đầy đủ có ý nghĩa cho người dùng. Mã này đảm bảo điều này xảy ra vì nó là một vấn đề lớn try/catchxung quanh tất cả các trình xử lý yêu cầu của bạn (các servlet, hành động thanh chống hoặc bất kỳ bộ điều khiển nào ...)

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

}

Ví dụ khác, hãy xem xét bạn có một lớp dịch vụ phục vụ hoạt động kinh doanh chuyển tiền. Phương thức này trả về TransferReceiptnếu quá trình chuyển được thực hiện hoặc NULLnếu không thể.

String FoundtransferService.doTransfer( fundtransferVO);

Bây giờ hình ảnh bạn nhận được một Listkhoản chuyển tiền từ người dùng và bạn phải sử dụng dịch vụ trên để thực hiện tất cả.

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

Nhưng điều gì sẽ xảy ra nếu ngoại lệ xảy ra? Bạn không nên dừng lại, vì một lần chuyển có thể thành công và một lần không, bạn nên tiếp tục thông qua tất cả người dùng Listvà hiển thị kết quả cho mỗi lần chuyển. Vì vậy, bạn kết thúc với mã này.

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

Bạn có thể duyệt qua nhiều dự án mã nguồn mở để thấy rằng dự án đó throwablethực sự được lưu trữ và xử lý. Ví dụ ở đây là một tìm kiếm của tomcat, struts2primefaces:

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
Thấy mã trong các liên kết này. Có thể ném không chỉ là một trong những được nắm bắt! Cũng có những trường hợp ngoại lệ khác được giải quyết trước khi có thể ném.
developer1011

@ developer101 tất nhiên, nhưng họ làm bắt throwable, đó là câu hỏi này về là gì
Alireza Fattahi

4

Câu hỏi hơi mơ hồ; bạn đang hỏi "có được không để bắt Throwable", hoặc "có được không để bắt một Throwablevà không làm gì cả"? Nhiều người ở đây đã trả lời câu sau, nhưng đó là một vấn đề phụ; 99% thời gian bạn không nên "tiêu thụ" hoặc loại bỏ các ngoại lệ, cho dù bạn đang đánh bắt Throwablehoặc IOExceptionhoặc bất cứ điều gì.

Nếu bạn tuyên truyền ngoại lệ, câu trả lời (giống như câu trả lời cho rất nhiều câu hỏi) là "nó phụ thuộc". Nó phụ thuộc vào những gì bạn đang làm với ngoại lệ — tại sao bạn lại nắm bắt được nó.

Một ví dụ điển hình về lý do tại sao bạn muốn nắm bắt Throwablelà cung cấp một số loại dọn dẹp nếu có bất kỳ lỗi nào. Ví dụ: trong JDBC, nếu lỗi xảy ra trong một giao dịch, bạn sẽ muốn khôi phục giao dịch:

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

Lưu ý rằng ngoại lệ không bị loại bỏ mà được truyền lại.

Nhưng như một chính sách chung, bắt Throwablebởi vì bạn không có lý do và quá lười biếng để xem những ngoại lệ cụ thể nào đang được đưa ra là một hình thức tồi và một ý tưởng tồi.


1

Nói chung, bạn muốn tránh bị bắt Errornhưng tôi có thể nghĩ đến (ít nhất) hai trường hợp cụ thể khi làm như vậy là thích hợp:

  • Bạn muốn tắt ứng dụng để giải quyết lỗi, đặc biệt là AssertionError những vô hại.
  • Bạn có đang triển khai cơ chế gộp luồng tương tự như ExecutorService.submit () yêu cầu bạn chuyển tiếp các ngoại lệ trở lại người dùng để họ có thể xử lý không.

0

Nếu chúng ta sử dụng có thể ném , thì nó cũng bao gồm Lỗi và thế là xong.

Thí dụ.

    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();
}

}

Đầu ra:

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

0

Có thể ném là lớp cha của tất cả các lỗi và loại trừ. Nếu bạn sử dụng Throwable trong một mệnh đề bắt, nó sẽ không chỉ bắt tất cả các ngoại lệ mà còn bắt tất cả các lỗi. Các lỗi được đưa ra bởi JVM để chỉ ra các vấn đề nghiêm trọng mà một ứng dụng không có ý định xử lý. Ví dụ điển hình cho điều đó là OutOfMemoryError hoặc StackOverflowError. Cả hai đều do các tình huống nằm ngoài tầm kiểm soát của ứng dụng và không thể xử lý được. Vì vậy, bạn không nên bắt Throwable trừ khi bạn khá tự tin rằng nó sẽ chỉ là một ngoại lệ nằm bên trong Throwable.


-1

Mặc dù việc bắt Throwable nói chung là một phương pháp không tốt (như đã được giải thích bằng rất nhiều câu trả lời cho câu hỏi này), các tình huống mà việc bắt Throwablelà hữu ích là khá phổ biến. Hãy để tôi giải thích một trường hợp mà tôi sử dụng trong công việc của mình, với một ví dụ đơn giản.

Hãy xem xét một phương pháp thực hiện phép cộng hai số và sau khi cộng thành công, nó sẽ gửi thông báo qua email đến một số người nhất định. Giả sử rằng số được trả về là quan trọng và được sử dụng bởi phương thức gọi.

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
    }
}

Mã để gửi cảnh báo qua email đọc rất nhiều cấu hình hệ thống và do đó, có thể có nhiều trường hợp ngoại lệ được đưa ra từ khối mã đó. Nhưng chúng tôi không muốn bất kỳ ngoại lệ nào gặp phải trong quá trình gửi cảnh báo truyền đến phương thức người gọi, vì phương thức đó chỉ quan tâm đến tổng của hai giá trị Số nguyên mà nó cung cấp. Do đó, mã để gửi thông báo qua email được đặt trong một try-catchkhối, nơi Throwableđược bắt và mọi trường hợp ngoại lệ chỉ được ghi lại, cho phép phần còn lại của quy trình tiếp tục.


Tôi sẽ cố gắng tránh điều này bằng cách có một chuỗi dành riêng cho công việc (với một hàng đợi) gửi email.
boumbh

Tôi đề nghị rằng đây vẫn là mã xấu. Bắt Exceptions bằng mọi cách, nhưng không Throwable.
andrewf
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.