Là tốt hơn để sử dụng assert hoặc IllegalArgumentException cho các tham số phương thức cần thiết?


87

Trong Java, cái nào được khuyến khích cao hơn, và tại sao? Cả hai loại sẽ ném ngoại lệ, vì vậy về mặt xử lý chúng là như nhau. assertngắn hơn một chút, nhưng tôi không chắc vấn đề đó quan trọng đến mức nào.

public void doStuff(Object obj) {
    assert obj != null;
    ...
}

đấu với

public void doStuff(Object obj) {
    if (obj == null) {
        throw new IllegalArgumentException("object was null");
    }
    ...
}

Tôi thích đơn giản hơn obj.hashCode()thay vào đó ;-)
Marco

Câu trả lời:


118

HÃY THỬ!

Các xác nhận được loại bỏ trong thời gian chạy trừ khi bạn xác định rõ ràng là "bật các xác nhận" khi biên dịch mã của bạn. Các xác nhận Java không được sử dụng trên mã sản xuất và nên được giới hạn ở các phương thức riêng tư (xem Ngoại lệ so với Xác nhận ), vì các phương thức riêng tư dự kiến ​​chỉ được các nhà phát triển biết và sử dụng. Cũng assertsẽ ném AssertsError kéo dài Errorkhông Exception, và thông thường cho thấy bạn có một lỗi rất bất thường (như "OutOfMemoryError" rất khó để phục hồi, phải không?) Bạn không thể điều trị được.

Xóa cờ "bật xác nhận" và kiểm tra với trình gỡ lỗi và bạn sẽ thấy rằng bạn sẽ không thực hiện cuộc gọi ném IllegalArgumentException ... vì mã này chưa được biên dịch (một lần nữa, khi "e" bị xóa)

Tốt hơn là sử dụng cấu trúc thứ hai cho các phương thức công khai / được bảo vệ và nếu bạn muốn một cái gì đó được thực hiện trong một dòng mã, có ít nhất một cách mà tôi biết. Cá nhân tôi sử dụng Spring Framework 's Assertlớp mà có một vài phương pháp để kiểm tra đối số và ném 'IllegalArgumentException' trên thất bại. Về cơ bản, những gì bạn làm là:

Assert.notNull(obj, "object was null");

... Thực tế sẽ thực thi chính xác cùng mã mà bạn đã viết trong ví dụ thứ hai. Có một vài phương pháp hữu ích khác như hasText, hasLengthtrong đó.

Tôi không thích viết nhiều mã hơn mức cần thiết, vì vậy tôi rất vui khi giảm số dòng viết xuống 2 (2 dòng> 1 dòng) :-)


Ah, tôi quên mất việc xác nhận bị xóa! Câu trả lời chính xác. Tôi sẽ đợi một chút để xem có gì khác đến không, sau đó chấp nhận nó :)
Daenyth

2
Lưu ý rằng không có cờ loại bỏ các xác nhận tại thời điểm biên dịch (mặc dù chúng có thể bị xóa thông qua khiếu nại có điều kiện ). Các xác nhận được tắt theo thời gian chạy theo mặc định (tôi nghĩ rằng JVM coi chúng là NOOP), nhưng có thể được kích hoạt thông qua java -eavà lập trình. @Jalayn Tôi nghĩ rằng việc có các xác nhận trong mã sản xuất là hoàn toàn hợp lệ, vì chúng rất hữu ích cho việc gỡ lỗi trong lĩnh vực này
Justin Muller

@Jalayn, -1. Trình biên dịch không loại bỏ mã xác nhận. Mặc dù chúng sẽ không được chạy trừ khi bạn làm cmd java -ea.
Pacerier

5
không cần khung công tác Spring khi bạn có thể sử dụngObjects.requreNonNull
đúng giờ

46

Bạn cần sử dụng một ngoại lệ. Sử dụng một xác nhận sẽ là sử dụng sai tính năng.

Các ngoại lệ không được kiểm tra được thiết kế để phát hiện lỗi lập trình của người dùng thư viện của bạn, trong khi các xác nhận được thiết kế để phát hiện lỗi trong logic của riêng bạn . Đây là những vấn đề riêng biệt không nên trộn lẫn.

Ví dụ: một khẳng định

assert myConnection.isConnected();

có nghĩa là "Tôi biết rằng mỗi đường dẫn mã dẫn đến xác nhận này đảm bảo myConnectionđược kết nối; nếu mã ở trên không có kết nối hợp lệ, thì nó sẽ ném ngoại lệ hoặc quay lại trước khi đạt đến điểm này."

Mặt khác, kiểm tra

if (!myConnection.isConnected()) {
    throw new IllegalArgumentException("connection is not established");
}

có nghĩa là "Gọi thư viện của tôi mà không thiết lập kết nối là lỗi lập trình".


1
Thông tin này thực sự hữu ích, nhưng tôi chấp nhận Jalayn vì nó chỉ ra cách tôi có khả năng giới thiệu một lỗi với phương thức khẳng định.
Daenyth

4
điểm tuyệt vời "Các ngoại lệ không được kiểm tra được thiết kế để phát hiện lỗi lập trình của người dùng thư viện của bạn, trong khi các xác nhận được thiết kế để phát hiện lỗi trong logic của riêng bạn. Đây là những vấn đề riêng biệt không nên trộn lẫn."
Asim Ghaffar

Đúng, nhưng điều này giả định OP đang viết một thư viện. Nếu mã của họ chỉ để sử dụng nội bộ, một khẳng định là chấp nhận được.
user949300

11

Nếu bạn đang viết một hàm không cho phép nulllàm giá trị tham số hợp lệ, bạn nên thêm @Nonnullchú thích vào chữ ký và sử dụng Objects.requireNonNullđể kiểm tra xem đối số có nullvà ném NullPointerExceptionnếu nó là. Các @Nonnullchú thích là dành cho tài liệu và sẽ cung cấp những cảnh báo hữu ích tại thời gian biên dịch trong một số trường hợp. Nó không ngăn chặn nullđược thông qua tại thời gian chạy.

void doStuff(@Nonnull Object obj) {
    Objects.requireNonNull(obj, "obj must not be null");

    // do stuff...
}

Cập nhật: Các @Nonnullchú thích không phải là một phần của thư viện chuẩn Java. Thay vào đó, có rất nhiều tiêu chuẩn cạnh tranh từ các thư viện bên thứ ba (xem Tôi nên sử dụng chú thích Java nào @NotNull? ). Điều đó không có nghĩa là đó là một ý tưởng tồi để sử dụng nó, chỉ là nó không chuẩn.


Cảm ơn đã chỉ ra Objects.requireNonNull(), đó là mới đối với tôi. Bạn có biết nếu có một phương thức "allowTrue ()" hoặc "allowEqual ()" tương tự ở bất cứ đâu không? Không có gì chống lại khẳng định của Spring nhưng không phải ai cũng sử dụng nó.
user949300

@ user949300 Objects.requireNonNull()là để xác thực đối số. Nếu một đối số được yêu cầu phải truebằng hoặc bằng một cái gì đó, thì đối số là vô nghĩa. Đối với trường hợp lỗi khác hơn là đối số bất hợp pháp, bạn nên throwmột Exceptionmô tả chính xác hơn lỗi. Ngoài ra còn có JUnit Assertnhưng đó là để thử nghiệm.
quay cuồng vào

Tôi đã suy nghĩ giống như, nói về hàm căn bậc hai Objects.requireTrue(x >= 0.0);, hoặc cho một số hàm băm,Objects.requireEquals(hash.length == 256)
user949300

Mà tôi phải sử dụng @Nonnull? javax.validation.constraint.NotNull?
Ác mộng

Tôi sẽ sử dụng các chú thích JDT của Eclipse , bởi vì chúng được tạo ra bởi những kẻ thông minh :). Tài liệu: help.eclipse.org/neon/ trộm - Bạn cũng có thể định cấu hình IntelliJ để sử dụng chúng.
koppor

2

Tôi luôn thích ném IllegalArgumentException hơn các xác nhận.

Các xác nhận được sử dụng chủ yếu trong JUnit hoặc các công cụ kiểm tra khác, để kiểm tra / xác nhận kết quả kiểm tra. Vì vậy, nó có thể mang lại ấn tượng sai cho các nhà phát triển khác rằng phương pháp của bạn là phương pháp thử nghiệm.

Ngoài ra, thật hợp lý khi ném IllegalArgumentException khi một phương thức đã được thông qua một đối số bất hợp pháp hoặc không phù hợp . Điều này phù hợp hơn với quy ước Xử lý ngoại lệ theo sau bởi các nhà phát triển Java.


5
Các xác nhận là khoảng 40 năm trước JUnit - hãy hỏi một lập trình viên C về các macro ASSERT.
JBRWilkinson

3
Câu hỏi này không phải là về c; về Java. Vì vậy, tôi đã trả lời trong bối cảnh của Java.
rai.skumar

Đừng nhầm lẫn giữa khẳng định (từ khóa dành riêng) so với Assert (lớp JUnit) cả hai đều được sử dụng để thực hiện kiểm tra một biến nhưng ngoài ra chúng là hai thứ rất khác nhau và hành xử rất khác nhau.
Newtopian

1

IMO cái thứ hai tốt hơn một chút vì nó mang lại nhiều thông tin hơn và có thể được mở rộng hơn nữa (ví dụ: bằng cách mở rộng lớp ngoại lệ) để có nhiều thông tin hơn, nó cũng không sử dụng so sánh tiêu cực dễ hiểu hơn.


1

Tôi không sử dụng aserts rất nhiều, nhưng cách tiếp cận phổ biến với Barkock @NonNull: https://projectlombok.org/features/NonNull

Triển khai Lombok: nhập lombok.NonNull;

public class NonNullExample extends Something {
  private String name;

  public NonNullExample(@NonNull Person person) {
    super("Hello");
    this.name = person.getName();
  }
}

Phiên bản Java:

 import lombok.NonNull;

public class NonNullExample extends Something {
  private String name;

  public NonNullExample(@NonNull Person person) {
    super("Hello");
    if (person == null) {
      throw new NullPointerException("person");
    }
    this.name = person.getName();
  }
}

Lombok là thư viện thực sự khá đẹp mà tôi sử dụng ở mọi nơi

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.