Khi nào nên chọn ngoại lệ được kiểm tra và không được kiểm tra


213

Trong Java (hoặc bất kỳ ngôn ngữ nào khác có ngoại lệ được kiểm tra), khi tạo lớp ngoại lệ của riêng bạn, làm thế nào để bạn quyết định liệu nó nên được kiểm tra hay bỏ chọn?

Bản năng của tôi là nói rằng một ngoại lệ được kiểm tra sẽ được gọi trong trường hợp người gọi có thể phục hồi theo cách hiệu quả, trong đó một ngoại lệ không được kiểm soát sẽ nhiều hơn cho các trường hợp không thể phục hồi, nhưng tôi quan tâm đến suy nghĩ của người khác.


11
Barry Ruzek đã viết một hướng dẫn tuyệt vời về việc chọn các ngoại lệ được kiểm tra hoặc không được kiểm tra.
sigget

Câu trả lời:


241

Các trường hợp ngoại lệ được kiểm tra là rất tốt, miễn là bạn hiểu khi nào chúng nên được sử dụng. API lõi Java không tuân theo các quy tắc này đối với SQLException (và đôi khi đối với IOException), đó là lý do tại sao chúng rất khủng khiếp.

Trường hợp ngoại lệ kiểm tra nên được sử dụng cho dự đoán được , nhưng unpreventable lỗi mà là hợp lý để phục hồi từ .

Các trường hợp ngoại lệ không được kiểm tra nên được sử dụng cho mọi thứ khác.

Tôi sẽ chia sẻ điều này cho bạn, bởi vì hầu hết mọi người hiểu sai điều này có nghĩa là gì.

  1. Có thể dự đoán nhưng không thể chấp nhận được : Người gọi đã làm mọi thứ trong khả năng của mình để xác thực các tham số đầu vào, nhưng một số điều kiện nằm ngoài sự kiểm soát của họ đã khiến hoạt động không thành công. Ví dụ: bạn thử đọc một tệp nhưng ai đó sẽ xóa nó trong khoảng thời gian bạn kiểm tra xem nó có tồn tại không và thời gian hoạt động đọc bắt đầu. Bằng cách khai báo một ngoại lệ được kiểm tra, bạn đang nói với người gọi để lường trước sự thất bại này.
  2. Hợp lý để phục hồi từ : Không có điểm nào nói với người gọi để dự đoán các ngoại lệ mà họ không thể phục hồi. Nếu người dùng cố gắng đọc từ một tệp không tồn tại, người gọi có thể nhắc họ cho một tên tệp mới. Mặt khác, nếu phương thức không thành công do lỗi lập trình (đối số phương thức không hợp lệ hoặc thực thi phương thức lỗi) thì ứng dụng không thể làm gì để khắc phục sự cố khi thực thi giữa chừng. Điều tốt nhất nó có thể làm là ghi lại sự cố và chờ nhà phát triển sửa nó sau.

Trừ khi ngoại lệ bạn ném đáp ứng tất cả các điều kiện trên, nó sẽ sử dụng Ngoại lệ không được kiểm tra.

Đánh giá lại ở mọi cấp độ : Đôi khi phương pháp bắt ngoại lệ được kiểm tra không phải là nơi thích hợp để xử lý lỗi. Trong trường hợp đó, hãy xem xét những gì là hợp lý cho người gọi của riêng bạn. Nếu ngoại lệ là có thể dự đoán được, không thể chấp nhận được và hợp lý để họ phục hồi từ đó thì bạn nên tự mình ném một ngoại lệ được kiểm tra. Nếu không, bạn nên bọc ngoại lệ trong một ngoại lệ không được kiểm tra. Nếu bạn tuân theo quy tắc này, bạn sẽ thấy mình chuyển đổi các ngoại lệ được kiểm tra thành các ngoại lệ không được kiểm tra và ngược lại tùy thuộc vào lớp bạn đang ở.

Đối với cả ngoại lệ được kiểm tra và không được kiểm tra, hãy sử dụng mức độ trừu tượng đúng . Ví dụ, một kho lưu trữ mã với hai triển khai khác nhau (cơ sở dữ liệu và hệ thống tệp) nên tránh phơi bày các chi tiết cụ thể về triển khai bằng cách ném SQLExceptionhoặc IOException. Thay vào đó, nó nên bao bọc ngoại lệ trong một bản tóm tắt kéo dài tất cả các cài đặt (ví dụ RepositoryException).


2
"bạn thử đọc một tập tin nhưng ai đó sẽ xóa nó trong khoảng thời gian bạn kiểm tra xem nó có tồn tại không và thời điểm hoạt động đọc bắt đầu." => Làm thế nào điều này thậm chí là 'mong đợi'? Đối với tôi nghe có vẻ giống như: Bất ngờ và có thể ngăn chặn được .. Ai có thể mong đợi một tệp sẽ bị xóa chỉ giữa 2 câu lệnh?
Koray Tugay

9
@KorayTugay Dự kiến không có nghĩa là kịch bản là điển hình. Điều đó chỉ có nghĩa là chúng ta có thể thấy trước lỗi này xảy ra trước thời hạn (so với các lỗi lập trình không thể dự đoán trước). Unpreventable đề cập đến thực tế là không có gì mà lập trình viên có thể làm để ngăn người dùng hoặc các ứng dụng khác xóa một tệp trong khoảng thời gian chúng tôi kiểm tra xem nó có tồn tại hay không và thời gian hoạt động đọc bắt đầu.
Gili

Vì vậy, bất kỳ vấn đề liên quan đến cơ sở dữ liệu trong phương pháp phải ném ngoại lệ được kiểm tra?
ivanjermakov

59

Từ một người học Java :

Khi xảy ra ngoại lệ, bạn phải bắt và xử lý ngoại lệ hoặc thông báo cho trình biên dịch rằng bạn không thể xử lý bằng cách khai báo rằng phương thức của bạn ném ngoại lệ đó, sau đó mã sử dụng phương thức của bạn sẽ phải xử lý ngoại lệ đó (ngay cả nó cũng có thể chọn tuyên bố rằng nó ném ngoại lệ nếu không thể xử lý nó).

Trình biên dịch sẽ kiểm tra xem chúng tôi đã thực hiện một trong hai điều chưa (bắt hoặc khai báo). Vì vậy, chúng được gọi là ngoại lệ được kiểm tra. Nhưng Lỗi và Ngoại lệ Thời gian chạy không được kiểm tra bởi trình biên dịch (mặc dù bạn có thể chọn bắt hoặc khai báo, nhưng không bắt buộc). Vì vậy, hai cái này được gọi là ngoại lệ Unchecked.

Lỗi được sử dụng để thể hiện những điều kiện xảy ra bên ngoài ứng dụng, chẳng hạn như sự cố của hệ thống. Ngoại lệ thời gian chạy thường xảy ra do lỗi trong logic ứng dụng. Bạn không thể làm bất cứ điều gì trong những tình huống này. Khi ngoại lệ thời gian chạy xảy ra, bạn phải viết lại mã chương trình của bạn. Vì vậy, những điều này không được kiểm tra bởi trình biên dịch. Những ngoại lệ thời gian chạy này sẽ phát hiện ra trong giai đoạn phát triển và thử nghiệm. Sau đó, chúng tôi phải cấu trúc lại mã của chúng tôi để loại bỏ các lỗi này.


13
Đó là quan điểm chính thống. Nhưng có rất nhiều tranh cãi về nó.
artbristol

49

Quy tắc tôi sử dụng là: không bao giờ sử dụng các ngoại lệ không được kiểm tra! (hoặc khi bạn không thấy bất kỳ cách nào xung quanh nó)

Có một trường hợp rất mạnh cho điều ngược lại: Không bao giờ sử dụng các ngoại lệ được kiểm tra. Tôi miễn cưỡng đứng về phía tranh luận nhưng dường như có một sự đồng thuận rộng rãi rằng việc đưa ra các ngoại lệ được kiểm tra là một quyết định sai lầm trong nhận thức muộn màng. Xin đừng bắn sứ giả và tham khảo những lập luận đó .


3
IMHO, các ngoại lệ được kiểm tra có thể là một tài sản lớn nếu có một cách dễ dàng để một phương thức tuyên bố rằng nó không mong đợi các phương thức được gọi trong một khối mã cụ thể sẽ ném ra một số ngoại lệ nhất định (hoặc bất kỳ trường hợp nào) các trường hợp ngoại lệ được ném trái với mong đợi như vậy nên được gói trong một số loại ngoại lệ khác và rút lại. Tôi đã khẳng định rằng 90% thời gian khi mã không được chuẩn bị để đối phó với một ngoại lệ được kiểm tra, việc quấn và lấy lại như vậy sẽ là cách tốt nhất để xử lý nó, nhưng vì không có hỗ trợ ngôn ngữ nào nên nó hiếm khi được thực hiện.
supercat

@supercat Về cơ bản là như vậy: Tôi là một fan hâm mộ cuồng nhiệt của các trường hợp ngoại lệ kiểm tra và kiểm tra nghiêm ngặt là một phần mở rộng hợp lý của điều đó. Tôi đã hoàn toàn từ bỏ các ngoại lệ được kiểm tra mặc dù về mặt khái niệm tôi rất thích chúng.
Konrad Rudolph

1
Một điều tôi có với thiết kế các ngoại lệ, mà cơ chế mà tôi mô tả sẽ giải quyết, đó là nếu foođược ghi lại là ném barExceptionkhi đọc qua phần cuối của tệp và foogọi một phương thức ném barExceptionngay cả khi fookhông mong đợi nó, mã mà các cuộc gọi foosẽ nghĩ rằng đã kết thúc tập tin và sẽ không có manh mối nào xảy ra. Tôi coi tình huống đó là một trong những trường hợp ngoại lệ được kiểm tra sẽ hữu ích nhất, nhưng đó cũng là trường hợp duy nhất mà trình biên dịch sẽ cho phép các ngoại lệ được kiểm tra không được xử lý.
supercat

@supercat: Đã có một cách dễ dàng để mã thực hiện những gì bạn muốn trong bình luận đầu tiên: bọc mã trong một khối thử, bắt Exception và bọc Exception trong RuntimeException và suy nghĩ lại.
Warren Dew

1
@KonradRudolph supercat đề cập đến "một khối mã cụ thể"; cho khối phải được xác định, cú pháp khai báo sẽ không làm giảm đáng kể sự phình to. Nếu bạn nghĩ rằng nó sẽ là khai báo cho toàn bộ chức năng, điều đó sẽ khuyến khích lập trình xấu vì mọi người sẽ chỉ dán vào khai báo thay vì thực sự nhìn vào các ngoại lệ được kiểm tra có khả năng bị bắt và đảm bảo không có cách nào khác tốt hơn để xử lý chúng.
Warren Dew

46

Trên bất kỳ hệ thống đủ lớn nào, với nhiều lớp, ngoại lệ được kiểm tra đều vô dụng vì dù sao, bạn cần một chiến lược cấp kiến ​​trúc để xử lý cách xử lý ngoại lệ đó (sử dụng hàng rào lỗi)

Với các ngoại lệ được kiểm tra, danh mục xử lý lỗi của bạn được quản lý vi mô và không thể chịu đựng được trên bất kỳ hệ thống lớn nào.

Hầu hết thời gian bạn không biết liệu có lỗi "có thể phục hồi được không" bởi vì bạn không biết người gọi API của bạn nằm ở lớp nào.

Giả sử tôi tạo API StringToInt để chuyển đổi biểu diễn chuỗi của một số nguyên thành Int. Tôi có phải ném ngoại lệ được kiểm tra nếu API được gọi bằng chuỗi "foo" không? Có thể phục hồi? Tôi không biết vì trong lớp của anh ấy, người gọi API StringToInt của tôi có thể đã xác thực đầu vào và nếu ngoại lệ này bị ném thì đó là lỗi hoặc hỏng dữ liệu và nó không thể phục hồi được cho lớp này.

Trong trường hợp này, người gọi API không muốn bắt ngoại lệ. Anh ta chỉ muốn để ngoại lệ "nổi bong bóng". Nếu tôi chọn một ngoại lệ được kiểm tra, người gọi này sẽ có rất nhiều khối bắt vô dụng chỉ để lấy lại ngoại lệ một cách giả tạo.

Những gì có thể phục hồi phụ thuộc hầu hết thời gian vào người gọi API, không phụ thuộc vào người viết API. API không nên sử dụng các ngoại lệ được kiểm tra vì chỉ các ngoại lệ không được kiểm tra mới cho phép chọn bắt hoặc bỏ qua một ngoại lệ.


3
Điều này rất gần với userstories.blogspot.com/2008/12/ Từ
alexsmail

15
@alexsmail Internet không bao giờ làm tôi ngạc nhiên, thực sự đó là blog của tôi :)
Stephane

30

Bạn hoàn toàn đúng.

Các ngoại lệ không được kiểm tra được sử dụng để cho hệ thống bị lỗi nhanh , đó là một điều tốt. Bạn nên nói rõ phương pháp của bạn mong đợi để hoạt động đúng. Bằng cách này, bạn có thể xác nhận đầu vào chỉ một lần.

Ví dụ:

/**
 * @params operation - The operation to execute.
 * @throws IllegalArgumentException if the operation is "exit"
 */
 public final void execute( String operation ) {
     if( "exit".equals(operation)){
          throw new IllegalArgumentException("I told you not to...");
     }
     this.operation = operation; 
     .....  
 }
 private void secretCode(){
      // we perform the operation.
      // at this point the opreation was validated already.
      // so we don't worry that operation is "exit"
      .....  
 }

Chỉ để đưa ra một ví dụ. Vấn đề là, nếu hệ thống bị lỗi nhanh, thì bạn sẽ biết nó bị lỗi ở đâu và tại sao. Bạn sẽ nhận được một stacktrace như:

 IllegalArgumentException: I told you not to use "exit" 
 at some.package.AClass.execute(Aclass.java:5)
 at otherPackage.Otherlass.delegateTheWork(OtherClass.java:4569)
 ar ......

Và bạn sẽ biết chuyện gì đã xảy ra. OtherClass trong phương thức "ủy nhiệm TheWork" (ở dòng 4569) đã gọi lớp của bạn với giá trị "thoát", ngay cả khi không nên, v.v.

Nếu không, bạn sẽ phải rắc các xác nhận lên toàn bộ mã của mình và đó là lỗi dễ xảy ra. Ngoài ra, đôi khi rất khó để theo dõi những gì đã sai và bạn có thể mong đợi hàng giờ gỡ lỗi bực bội

Điều tương tự cũng xảy ra với NullPulumExceptions. Nếu bạn có một lớp 700 dòng với khoảng 15 phương thức, sử dụng 30 thuộc tính và không có thuộc tính nào trong số chúng có thể là null, thay vì xác thực trong mỗi phương thức đó để vô hiệu hóa, bạn có thể làm cho tất cả các thuộc tính đó chỉ đọc và xác thực chúng trong hàm tạo hoặc phương pháp nhà máy.

 public static MyClass createInstane( Object data1, Object data2 /* etc */ ){ 
      if( data1 == null ){ throw NullPointerException( "data1 cannot be null"); }

  }


  // the rest of the methods don't validate data1 anymore.
  public void method1(){ // don't worry, nothing is null 
      ....
  }
  public void method2(){ // don't worry, nothing is null 
      ....
  }
  public void method3(){ // don't worry, nothing is null 
      ....
  }

Các ngoại lệ được kiểm tra Rất hữu ích khi lập trình viên (bạn hoặc đồng nghiệp của bạn) làm mọi thứ đúng, xác thực đầu vào, chạy thử nghiệm và tất cả mã đều hoàn hảo, nhưng mã kết nối với dịch vụ web của bên thứ ba có thể bị hỏng (hoặc tệp bạn đang sử dụng đã bị xóa bởi một quá trình bên ngoài khác, v.v.). Dịch vụ web thậm chí có thể được xác nhận trước khi kết nối được thử, nhưng trong quá trình truyền dữ liệu đã xảy ra sự cố.

Trong kịch bản đó, không có gì mà bạn hoặc đồng nghiệp của bạn có thể làm để giúp đỡ nó. Nhưng bạn vẫn phải làm một cái gì đó và không để ứng dụng chỉ chết và biến mất trong mắt người dùng. Bạn sử dụng một ngoại lệ được kiểm tra cho điều đó và xử lý ngoại lệ đó, bạn có thể làm gì khi điều đó xảy ra?, Hầu hết thời gian, chỉ để cố gắng ghi lại lỗi, có thể lưu công việc của bạn (ứng dụng hoạt động) và gửi thông báo cho người dùng . (Trang web blabla không hoạt động, vui lòng thử lại sau, v.v.)

Nếu ngoại lệ được kiểm tra bị lạm dụng (bằng cách thêm "ngoại lệ ném" vào tất cả các chữ ký phương thức), thì mã của bạn sẽ trở nên rất dễ hỏng, bởi vì mọi người sẽ bỏ qua ngoại lệ đó (vì quá chung chung) và chất lượng mã sẽ nghiêm trọng thỏa hiệp.

Nếu bạn lạm dụng ngoại lệ không được kiểm tra, một cái gì đó tương tự sẽ xảy ra. Những người sử dụng mã đó không biết liệu có điều gì đó có thể sai hay không, rất nhiều lần thử {...} bắt (Ném được t) sẽ xuất hiện.


2
Nói hay lắm! +1. Nó luôn làm tôi ngạc nhiên người gọi phân biệt này (không được kiểm tra) / callee (đã kiểm tra) không rõ ràng hơn ...
VonC

19

Đây là 'quy tắc cuối cùng' của tôi.
Tôi sử dụng:

  • ngoại lệ không được kiểm tra trong mã phương thức của tôi cho một lỗi do người gọi (liên quan đến một tài liệu rõ ràng và đầy đủ )
  • đã kiểm tra ngoại lệ cho một lỗi do callee mà tôi cần phải làm rõ cho bất kỳ ai muốn sử dụng mã của tôi

So với câu trả lời trước, đây là một lý do rõ ràng (theo đó người ta có thể đồng ý hoặc không đồng ý) cho việc sử dụng một hoặc loại khác (hoặc cả hai) ngoại lệ.


Đối với cả hai trường hợp ngoại lệ đó, tôi sẽ tạo Ngoại lệ không được kiểm tra và kiểm tra riêng cho ứng dụng của mình (một cách thực hành tốt, như đã đề cập ở đây ), ngoại trừ trường hợp ngoại lệ không được kiểm tra rất phổ biến (như NullPulumException)

Vì vậy, ví dụ, mục tiêu của chức năng cụ thể này dưới đây là tạo (hoặc lấy nếu đã tồn tại) một đối tượng,
nghĩa là:

  • vùng chứa của đối tượng để tạo / nhận PHẢI tồn tại (trách nhiệm của CALLER
    => ngoại lệ không được kiểm tra, và xóa nhận xét javadoc cho hàm được gọi này)
  • các tham số khác không thể là null
    (sự lựa chọn của bộ mã hóa để đưa nó vào CALLER: bộ mã hóa sẽ không kiểm tra tham số null mà là bộ mã hóa DOES DOCUMENT IT)
  • kết quả KHÔNG THỂ ĐƯỢC NULL
    (trách nhiệm và lựa chọn mã của callee, sự lựa chọn sẽ được người gọi quan tâm
    => ngoại lệ được kiểm tra vì mọi người gọi PHẢI đưa ra quyết định nếu không thể tạo / tìm thấy đối tượng và điều đó quyết định phải được thi hành tại thời điểm biên dịch: họ không thể sử dụng chức năng này mà không phải đối phó với khả năng này, nghĩa là với ngoại lệ được kiểm tra này ).

Thí dụ:


/**
 * Build a folder. <br />
 * Folder located under a Parent Folder (either RootFolder or an existing Folder)
 * @param aFolderName name of folder
 * @param aPVob project vob containing folder (MUST NOT BE NULL)
 * @param aParent parent folder containing folder 
 *        (MUST NOT BE NULL, MUST BE IN THE SAME PVOB than aPvob)
 * @param aComment comment for folder (MUST NOT BE NULL)
 * @return a new folder or an existing one
 * @throws CCException if any problems occurs during folder creation
 * @throws AssertionFailedException if aParent is not in the same PVob
 * @throws NullPointerException if aPVob or aParent or aComment is null
 */
static public Folder makeOrGetFolder(final String aFoldername, final Folder aParent,
    final IPVob aPVob, final Comment aComment) throws CCException {
    Folder aFolderRes = null;
    if (aPVob.equals(aParent.getPVob() == false) { 
       // UNCHECKED EXCEPTION because the caller failed to live up
       // to the documented entry criteria for this function
       Assert.isLegal(false, "parent Folder must be in the same PVob than " + aPVob); }

    final String ctcmd = "mkfolder " + aComment.getCommentOption() + 
        " -in " + getPNameFromRepoObject(aParent) + " " + aPVob.getFullName(aFolderName);

    final Status st = getCleartool().executeCmd(ctcmd);

    if (st.status || StringUtils.strictContains(st.message,"already exists.")) {
        aFolderRes = Folder.getFolder(aFolderName, aPVob);
    }
    else {
        // CHECKED EXCEPTION because the callee failed to respect his contract
        throw new CCException.Error("Unable to make/get folder '" + aFolderName + "'");
    }
    return aFolderRes;
}

19

Đó không chỉ là vấn đề về khả năng phục hồi từ ngoại lệ. Điều quan trọng nhất, theo tôi, là liệu người gọi có quan tâm đến việc bắt ngoại lệ hay không.

Nếu bạn viết một thư viện sẽ được sử dụng ở nơi khác hoặc lớp cấp thấp hơn trong ứng dụng của bạn, hãy tự hỏi liệu người gọi có quan tâm đến việc nắm bắt (biết về) ngoại lệ của bạn không. Nếu anh ta không, sau đó sử dụng một ngoại lệ không được kiểm soát, vì vậy bạn không gây gánh nặng cho anh ta một cách không cần thiết.

Đây là triết lý được sử dụng bởi nhiều khung. Mùa xuân và ngủ đông, đặc biệt, đến với tâm trí - họ chuyển đổi ngoại lệ đã kiểm tra đã biết thành ngoại lệ không được kiểm tra chính xác vì các ngoại lệ được kiểm tra được sử dụng quá mức trong Java. Một ví dụ mà tôi có thể nghĩ đến là nhận thức về JSONEx từ json.org, đây là một ngoại lệ được kiểm tra và chủ yếu gây khó chịu - không nên kiểm tra, nhưng nhà phát triển chỉ đơn giản là không nghĩ đến nó.

Nhân tiện, hầu hết thời gian người gọi quan tâm đến ngoại lệ có liên quan trực tiếp đến khả năng phục hồi từ ngoại lệ, nhưng điều đó không phải lúc nào cũng đúng.


13

Đây là một giải pháp rất đơn giản cho tình huống khó xử Đã kiểm tra / Không được kiểm tra của bạn.

Quy tắc 1: Hãy nghĩ về một ngoại lệ không được kiểm tra như một điều kiện có thể kiểm tra trước khi mã thực thi. ví dụ…

x.doSomething(); // the code throws a NullPointerException

trong đó x là null ... Mã có thể có mã sau

if (x==null)
{
    //do something below to make sure when x.doSomething() is executed, it won’t throw a NullPointerException.
    x = new X();
}
x.doSomething();

Quy tắc 2: Hãy nghĩ về một ngoại lệ được kiểm tra là một điều kiện không thể kiểm tra có thể xảy ra trong khi mã thực thi.

Socket s = new Socket(“google.com”, 80);
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();

Trong ví dụ trên, URL (google.com) có thể không khả dụng do máy chủ DNS bị hỏng. Ngay cả khi máy chủ DNS hoạt động và giải quyết tên 'google.com' thành địa chỉ IP, nếu kết nối được thực hiện với google.com, bất cứ lúc nào có lời bạt, mạng có thể bị hỏng. Bạn chỉ đơn giản là không thể kiểm tra mạng mọi lúc trước khi đọc và ghi vào luồng.

Đôi khi mã đơn giản phải thực thi trước khi chúng ta có thể biết nếu có vấn đề. Bằng cách buộc các nhà phát triển viết mã của họ theo cách để buộc họ xử lý các tình huống này thông qua Kiểm tra ngoại lệ, tôi phải ngả mũ trước người tạo ra Java đã phát minh ra khái niệm này.

Nói chung, hầu hết tất cả các API trong Java đều tuân theo 2 quy tắc trên. Nếu bạn cố ghi vào một tệp, đĩa có thể lấp đầy trước khi hoàn thành ghi. Có thể các quá trình khác đã khiến đĩa trở nên đầy. Đơn giản là không có cách nào để kiểm tra tình huống này. Đối với những người tương tác với phần cứng bất cứ lúc nào, sử dụng phần cứng có thể thất bại, Kiểm tra ngoại lệ dường như là một giải pháp tao nhã cho vấn đề này.

Có một khu vực màu xám này. Trong trường hợp cần nhiều bài kiểm tra (một ý nghĩ nếu có nhiều && và ||), ngoại lệ được ném sẽ là một CheckedException đơn giản vì quá khó để hiểu đúng - bạn chỉ đơn giản là không thể nói vấn đề này là một lỗi lập trình. Nếu có ít hơn 10 bài kiểm tra (ví dụ: 'if (x == null)'), thì lỗi lập trình viên phải là UncheckedException.

Mọi thứ trở nên thú vị khi làm việc với các thông dịch viên ngôn ngữ. Theo các quy tắc trên, Lỗi Cú pháp có nên được coi là Ngoại lệ được kiểm tra hoặc không được kiểm tra không? Tôi sẽ lập luận rằng nếu cú ​​pháp của ngôn ngữ có thể được kiểm tra trước khi nó được thực thi, thì nó phải là một UncheckedException. Nếu ngôn ngữ không thể được kiểm tra - tương tự như cách mã lắp ráp chạy trên máy tính cá nhân, thì Lỗi Cú pháp phải là Ngoại lệ được kiểm tra.

Hai quy tắc trên có thể sẽ loại bỏ 90% mối quan tâm của bạn về việc lựa chọn. Để tóm tắt các quy tắc, hãy làm theo mẫu này 1) nếu mã được thực thi có thể được kiểm tra trước khi nó được thực thi để chạy chính xác và nếu xảy ra Ngoại lệ - còn gọi là lỗi lập trình viên, Ngoại lệ phải là UncheckedException (một lớp con của RuntimeException ). 2) nếu mã được thực thi không thể được kiểm tra trước khi mã được thực thi để mã chạy chính xác, Ngoại lệ phải là Ngoại lệ được kiểm tra (một lớp con của Ngoại lệ).


9

Bạn có thể gọi nó là một ngoại lệ được kiểm tra hoặc không được kiểm tra; tuy nhiên, cả hai loại ngoại lệ đều có thể bị lập trình viên bắt gặp, vì vậy câu trả lời tốt nhất là: viết tất cả các ngoại lệ của bạn dưới dạng không được kiểm tra và ghi lại chúng. Bằng cách đó, nhà phát triển sử dụng API của bạn có thể chọn liệu họ có muốn bắt ngoại lệ đó hay không và làm gì đó. Các ngoại lệ được kiểm tra là một sự lãng phí hoàn toàn thời gian của mọi người và nó làm cho mã của bạn trở thành một cơn ác mộng gây sốc khi xem xét. Kiểm tra đơn vị thích hợp sau đó sẽ đưa ra bất kỳ trường hợp ngoại lệ nào mà bạn có thể phải nắm bắt và làm điều gì đó với.


1
+1 Để đề cập rằng các bài kiểm tra đơn vị có thể là một cách tốt hơn để giải quyết các ngoại lệ được kiểm tra có vấn đề nhằm giải quyết.
Keith Pinson

+1 để kiểm tra đơn vị. Sử dụng các ngoại lệ được kiểm tra / Uncheked ít ảnh hưởng đến chất lượng mã. Vì vậy, đối số rằng nếu một người sử dụng các ngoại lệ được kiểm tra, nó sẽ dẫn đến chất lượng mã tốt hơn là đối số không có thật!
dùng1697575

7

Đã kiểm tra ngoại lệ: Nếu khách hàng có thể khôi phục từ một ngoại lệ và muốn tiếp tục, hãy sử dụng ngoại lệ được kiểm tra.

Ngoại lệ không được kiểm tra: Nếu khách hàng không thể làm bất cứ điều gì sau ngoại lệ, sau đó đưa ra ngoại lệ không được kiểm tra.

Ví dụ: Nếu bạn dự kiến ​​thực hiện phép toán số học trong phương thức A () và dựa trên đầu ra từ A (), bạn phải thực hiện thao tác khác. Nếu đầu ra là null từ phương thức A () mà bạn không mong đợi trong thời gian chạy, thì bạn sẽ ném ngoại lệ con trỏ Null ngoại lệ thời gian chạy.

Tham khảo tại đây


2

Tôi đồng ý với ưu tiên cho các ngoại lệ không được kiểm soát như một quy tắc, đặc biệt là khi thiết kế API. Người gọi luôn có thể chọn để bắt một ngoại lệ được ghi lại, không được kiểm tra. Bạn không cần phải ép buộc người gọi đến.

Tôi thấy các ngoại lệ được kiểm tra hữu ích ở cấp thấp hơn, như chi tiết triển khai. Nó thường có vẻ như là một luồng cơ chế kiểm soát tốt hơn so với việc phải quản lý một lỗi "mã trả về" được chỉ định. Đôi khi nó cũng có thể giúp thấy tác động của một ý tưởng đối với thay đổi mã cấp thấp quá ... khai báo một ngoại lệ được kiểm tra ở phía dưới và xem ai sẽ cần điều chỉnh. Điểm cuối cùng này không áp dụng nếu có nhiều điểm chung: bắt (Ngoại lệ e) hoặc ném Ngoại lệ thường không được suy nghĩ kỹ.


2

Dưới đây tôi muốn chia sẻ ý kiến ​​của tôi sau nhiều năm kinh nghiệm phát triển:

  1. Đã kiểm tra ngoại lệ. Đây là một phần của trường hợp sử dụng nghiệp vụ hoặc luồng cuộc gọi, đây là một phần của logic ứng dụng mà chúng tôi mong đợi hoặc không mong đợi. Ví dụ: kết nối bị từ chối, điều kiện không được thỏa mãn, v.v. Chúng tôi cần xử lý nó và hiển thị thông báo tương ứng cho người dùng với hướng dẫn những gì đã xảy ra và phải làm gì tiếp theo (thử lại sau, v.v.). Tôi thường gọi nó là ngoại lệ xử lý hậu kỳ hoặc ngoại lệ "người dùng".

  2. Ngoại lệ không được kiểm tra. Đây là một phần của ngoại lệ lập trình, một số lỗi trong lập trình mã phần mềm (lỗi, lỗi) và phản ánh cách các lập trình viên phải sử dụng API theo tài liệu. Nếu một tài liệu lib / framework bên ngoài nói rằng nó hy vọng sẽ nhận được dữ liệu trong một phạm vi nào đó và không null, bởi vì NPE hoặc IllegalArgumentException sẽ bị ném, lập trình viên nên mong đợi nó và sử dụng API chính xác theo tài liệu. Nếu không, ngoại lệ sẽ được ném. Tôi thường gọi nó là ngoại lệ tiền xử lý hoặc ngoại lệ "xác nhận".

Theo đối tượng mục tiêu. Bây giờ, hãy nói về đối tượng mục tiêu hoặc nhóm người, các trường hợp ngoại lệ đã được thiết kế (theo ý kiến ​​của tôi):

  1. Đã kiểm tra ngoại lệ. Đối tượng mục tiêu là người dùng / khách hàng.
  2. Ngoại lệ không được kiểm tra. Đối tượng mục tiêu là nhà phát triển. Nói cách khác, ngoại lệ không được kiểm tra chỉ được thiết kế cho các nhà phát triển.

Bằng giai đoạn vòng đời phát triển ứng dụng.

  1. Ngoại lệ được kiểm tra được thiết kế để tồn tại trong toàn bộ vòng đời sản xuất như cơ chế bình thường và dự kiến, một ứng dụng xử lý các trường hợp đặc biệt.
  2. Ngoại lệ không được kiểm tra được thiết kế chỉ tồn tại trong vòng đời phát triển / thử nghiệm ứng dụng, tất cả chúng nên được sửa trong thời gian đó và không nên ném khi ứng dụng đang chạy.

Lý do tại sao các khung thường sử dụng các ngoại lệ không được kiểm tra (ví dụ Spring) là khung đó không thể xác định logic nghiệp vụ của ứng dụng của bạn, điều này phụ thuộc vào các nhà phát triển để bắt kịp và thiết kế logic riêng.


2

Chúng ta phải phân biệt hai loại ngoại lệ này dựa trên việc đó có phải là lỗi lập trình viên hay không.

  • Nếu một lỗi là lỗi lập trình viên, thì đó phải là Ngoại lệ không được kiểm tra . Ví dụ: SQLException / IOException / NullPulumException. Những ngoại lệ này là lỗi lập trình. Chúng nên được xử lý bởi lập trình viên. Trong khi trong API JDBC, SQLException được kiểm tra ngoại lệ, trong Spring JDBCTemplate, đó là một Exceptioned Unceptioned.Programmer không lo lắng về SqlException, khi sử dụng Spring.
  • Nếu một lỗi không phải là lỗi lập trình viên và lý do đến từ bên ngoài, thì đó phải là Ngoại lệ được kiểm tra. Ví dụ: nếu tập tin bị xóa hoặc sự cho phép tập tin bị thay đổi bởi người khác, nó sẽ được phục hồi.

FileNotFoundException là ví dụ điển hình để hiểu sự khác biệt tinh tế. FileNotFoundException được ném trong trường hợp không tìm thấy tệp. Có hai lý do cho ngoại lệ này. Nếu đường dẫn tệp được xác định bởi nhà phát triển hoặc lấy từ người dùng cuối thông qua GUI thì đó phải là Ngoại lệ không được kiểm tra. Nếu tập tin bị xóa bởi người khác, nó sẽ là Ngoại lệ được kiểm tra.

Đã kiểm tra Ngoại lệ có thể được xử lý theo hai cách. Đây là sử dụng thử bắt hoặc tuyên truyền ngoại lệ. Trong trường hợp lan truyền ngoại lệ, tất cả các phương thức trong ngăn xếp cuộc gọi sẽ được kết hợp chặt chẽ vì xử lý ngoại lệ. Đó là lý do tại sao, chúng ta phải sử dụng Kiểm tra ngoại lệ một cách cẩn thận.

Trong trường hợp bạn phát triển một hệ thống doanh nghiệp nhiều lớp, bạn phải chọn ngoại lệ hầu như không được kiểm tra để ném, nhưng đừng quên sử dụng ngoại lệ được kiểm tra cho trường hợp bạn không thể làm gì.


1

Các ngoại lệ được kiểm tra rất hữu ích cho các trường hợp có thể phục hồi khi bạn muốn cung cấp thông tin cho người gọi (nghĩa là không đủ quyền, không tìm thấy tệp, v.v.).

Các trường hợp ngoại lệ không được kiểm tra hiếm khi được sử dụng để thông báo cho người dùng hoặc lập trình viên về các lỗi nghiêm trọng hoặc các điều kiện không mong muốn trong thời gian chạy. Đừng ném chúng nếu bạn đang viết mã hoặc thư viện sẽ được người khác sử dụng, vì họ có thể không mong đợi phần mềm của bạn đưa ra các ngoại lệ không được kiểm soát do trình biên dịch không buộc chúng bị bắt hoặc khai báo.


Tôi không đồng ý với tuyên bố của bạn "Các trường hợp ngoại lệ không được kiểm tra hiếm khi được sử dụng, nếu thực tế" nó thực sự ngược lại! Sử dụng exceprions không được kiểm tra theo mặc định khi bạn thiết kế phân cấp ngoại lệ ứng dụng của bạn. Hãy để các nhà phát triển quyết định khi nào họ muốn xử lý ngoại lệ (ví dụ: họ không bị buộc phải đặt khối bắt hoặc đặt mệnh đề ném nếu họ không biết cách xử lý).
dùng1697575

1

Bất cứ khi nào một ngoại lệ ít có khả năng được mong đợi và chúng tôi có thể tiến hành ngay cả sau khi nắm bắt được điều đó và chúng tôi không thể làm gì để tránh ngoại lệ đó thì chúng tôi có thể sử dụng ngoại lệ được kiểm tra.

Bất cứ khi nào chúng ta muốn làm điều gì đó có ý nghĩa khi một ngoại lệ cụ thể xảy ra và khi ngoại lệ đó được mong đợi nhưng không chắc chắn, thì chúng ta có thể sử dụng ngoại lệ được kiểm tra.

Bất cứ khi nào ngoại lệ điều hướng trong các lớp khác nhau, chúng ta không cần phải bắt nó trong mọi lớp, trong trường hợp đó, chúng ta có thể sử dụng ngoại lệ thời gian chạy hoặc ngoại lệ bọc là ngoại lệ không được kiểm soát.

Ngoại lệ thời gian chạy được sử dụng khi ngoại lệ rất có thể xảy ra, không có cách nào để đi xa hơn và không có gì có thể phục hồi được. Vì vậy, trong trường hợp này, chúng tôi có thể có biện pháp phòng ngừa đối với ngoại lệ đó. EX: NUllPulumException, ArrayOutofBoundException. Đây có nhiều khả năng xảy ra. Trong kịch bản này, chúng ta có thể đề phòng trong khi mã hóa để tránh ngoại lệ đó. Nếu không, chúng tôi sẽ phải viết thử bắt khối ở mọi nơi.

Các ngoại lệ chung hơn có thể được thực hiện Bỏ chọn, ít kiểm tra chung hơn.


1

Tôi nghĩ rằng chúng ta có thể nghĩ về ngoại lệ từ một số câu hỏi:

Tại sao sự miễn trừ xảy ra? Chúng ta có thể làm gì khi nó xảy ra

do nhầm lẫn, một lỗi. chẳng hạn như một phương thức của đối tượng null được gọi.

String name = null;
... // some logics
System.out.print(name.length()); // name is still null here

Loại ngoại lệ này nên được sửa trong quá trình thử nghiệm. Nếu không, nó sẽ phá vỡ quá trình sản xuất và bạn đã gặp một lỗi rất cao cần được khắc phục ngay lập tức. Loại ngoại lệ này không cần phải được kiểm tra.

bởi đầu vào từ bên ngoài, bạn không thể kiểm soát hoặc tin tưởng đầu ra của dịch vụ bên ngoài.

String name = ExternalService.getName(); // return null
System.out.print(name.length());    // name is null here

Tại đây, bạn có thể cần kiểm tra xem tên có phải là null hay không nếu bạn muốn tiếp tục khi nó là null, nếu không, bạn có thể để nó một mình và nó sẽ dừng ở đây và cung cấp cho người gọi ngoại lệ thời gian chạy. Loại ngoại lệ này không cần phải được kiểm tra.

bởi ngoại lệ thời gian chạy từ bên ngoài, bạn không thể kiểm soát hoặc tin tưởng dịch vụ bên ngoài.

Tại đây, bạn có thể cần nắm bắt tất cả các ngoại lệ từ InternalService nếu bạn muốn tiếp tục khi nó xảy ra, nếu không, bạn có thể để nó một mình và nó sẽ dừng ở đây và cung cấp cho người gọi ngoại lệ thời gian chạy.

bằng cách kiểm tra ngoại lệ từ bên ngoài, bạn không thể kiểm soát hoặc tin tưởng dịch vụ bên ngoài.

Tại đây, bạn có thể cần nắm bắt tất cả các ngoại lệ từ InternalService nếu bạn muốn tiếp tục khi nó xảy ra, nếu không, bạn có thể để nó một mình và nó sẽ dừng ở đây và cung cấp cho người gọi ngoại lệ thời gian chạy.

Trong trường hợp này, chúng ta có cần biết loại ngoại lệ nào đã xảy ra trong Dịch vụ bên ngoài không? Nó phụ thuộc:

  1. nếu bạn có thể xử lý một số loại ngoại lệ, bạn cần nắm bắt chúng và xử lý. Đối với những người khác, bong bóng chúng.

  2. nếu bạn cần đăng nhập hoặc phản hồi cho người dùng thực hiện cụ thể, bạn có thể bắt chúng. Đối với những người khác, bong bóng chúng.


0

Tôi nghĩ rằng khi khai báo Ngoại lệ ứng dụng, nó phải là Unchecked Exception, tức là lớp con của RuntimeException. Lý do là nó sẽ không làm lộn xộn mã ứng dụng với thử bắt và ném khai báo trên phương thức. Nếu ứng dụng của bạn đang sử dụng Java Api, sẽ đưa ra các ngoại lệ được kiểm tra mà dù sao cũng cần phải xử lý. Đối với các trường hợp khác, ứng dụng có thể ném ngoại lệ không được kiểm tra. Nếu người gọi ứng dụng vẫn cần xử lý ngoại lệ không được kiểm tra, nó có thể được thực hiện.


-12

Quy tắc tôi sử dụng là: không bao giờ sử dụng các ngoại lệ không được kiểm tra! (hoặc khi bạn không thấy bất kỳ cách nào xung quanh nó)

Từ quan điểm của nhà phát triển sử dụng thư viện của bạn hoặc người dùng cuối sử dụng thư viện / ứng dụng của bạn, thật sự rất hấp dẫn khi phải đối mặt với một ứng dụng gặp sự cố do ngoại lệ không thể nghĩ ra. Và dựa vào một cái tất cả cũng không tốt.

Bằng cách này, người dùng cuối vẫn có thể xuất hiện một thông báo lỗi, thay vì ứng dụng hoàn toàn biến mất.


1
Bạn không giải thích những gì bạn nghĩ là sai với tất cả các trường hợp ngoại lệ không được kiểm tra.
Matthew Flaschen

Hoàn toàn không đồng ý với câu trả lời không tranh cãi của bạn: "không bao giờ sử dụng các ngoại lệ không được kiểm tra"
user1697575 18/03/13
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.