Làm cách nào để sử dụng chú thích @Nullable và @Nonnull hiệu quả hơn?


140

Tôi có thể thấy rằng @Nullable@Nonnullcác chú thích thể hữu ích trong việc ngăn chặn NullPointerExceptions nhưng chúng không lan truyền rất xa.

  • Hiệu quả của các chú thích này giảm hoàn toàn sau một cấp độ gián tiếp, vì vậy nếu bạn chỉ thêm một vài chú thích thì chúng không lan truyền rất xa.
  • Vì các chú thích này không được thi hành tốt, nên có nguy cơ giả sử giá trị được đánh dấu @Nonnulllà không rỗng và do đó không thực hiện kiểm tra null.

Mã dưới đây gây ra một tham số được đánh dấu @Nonnullnullkhông có bất kỳ khiếu nại nào. Nó ném một NullPointerExceptionkhi nó được chạy.

public class Clazz {
    public static void main(String[] args){
        Clazz clazz = new Clazz();

        // this line raises a complaint with the IDE (IntelliJ 11)
        clazz.directPathToA(null);

        // this line does not
        clazz.indirectPathToA(null); 
    }

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }
}

Có cách nào để làm cho các chú thích này được thực thi nghiêm ngặt hơn và / hoặc tuyên truyền hơn nữa không?


1
Tôi thích ý tưởng của @Nullablehoặc @Nonnull, nhưng nếu chúng có giá trị thì rất "có khả năng thu hút tranh luận"
Maarten Bodewes

Tôi nghĩ rằng cách để di chuyển đến một thế giới nơi điều này gây ra lỗi hoặc cảnh báo của trình biên dịch là yêu cầu truyền vào @Nonnullkhi gọi một @Nonnullphương thức có biến nullable. Tất nhiên, việc truyền với một chú thích là không thể trong Java 7, nhưng Java 8 sẽ thêm khả năng áp dụng các chú thích vào việc sử dụng một biến, bao gồm cả các phôi. Vì vậy, điều này có thể trở nên có thể thực hiện trong Java 8.
Theodore Murdock

1
@TheodoreMurdock, vâng, trong Java 8, việc truyền (@NonNull Integer) ycó thể về mặt cú pháp là có thể, nhưng trình biên dịch không được phép phát ra bất kỳ mã byte cụ thể nào dựa trên chú thích. Đối với các xác nhận thời gian chạy, các phương thức trợ giúp nhỏ là đủ như đã thảo luận trong bug.eclipse.org/442103 (ví dụ directPathToA(assertNonNull(y)):) - nhưng hãy nhớ, điều này chỉ giúp thất bại nhanh. Cách an toàn duy nhất là bằng cách thực hiện kiểm tra null thực tế (cộng với hy vọng việc triển khai thay thế trong nhánh khác).
Stephan Herrmann

1
Nó sẽ hữu ích trong câu hỏi này để nói @Nonnull@Nullablebạn đang nói về điều gì, vì có nhiều chú thích tương tự (Xem câu hỏi này ). Bạn đang nói về các chú thích trong gói javax.annotation?
James Dunn

1
@TJamesBoone Đối với bối cảnh của câu hỏi này không thành vấn đề, đây là về cách sử dụng bất kỳ câu hỏi nào một cách hiệu quả.
Mike Rylander

Câu trả lời:


66

Câu trả lời ngắn: Tôi đoán những chú thích này chỉ hữu ích cho IDE của bạn để cảnh báo bạn về các lỗi con trỏ null.

Như đã nói trong cuốn sách "Clean Code", bạn nên kiểm tra các tham số của phương thức công khai của mình và cũng tránh kiểm tra các bất biến.

Một mẹo hay khác là không bao giờ trả về giá trị null, nhưng sử dụng Null Object Pattern thay thế.


10
Đối với các giá trị trả về có thể trống, tôi thực sự khuyên bạn nên sử dụng Optionalloại thay vì đơn giảnnull
Patrick

7
Tùy chọn ist không tốt hơn "null". Tùy chọn # get () ném NoSuchEuityException trong khi việc sử dụng null ném NullPulumException. Cả hai đều là RuntimeException mà không có mô tả có ý nghĩa. Tôi thích các biến nullable.
30

4
@ 30thh tại sao bạn sẽ sử dụng Tùy chọn.get () trực tiếp và không Tùy chọn.isPftime () hoặc Tùy chọn.map trước?
GauravJ

7
@GauravJ Tại sao bạn sẽ sử dụng một biến nullable trực tiếp và không kiểm tra, nếu nó là null trước? ;-)
30

5
Sự khác biệt giữa Optionalvà nullable trong trường hợp này là Optionalgiao tiếp tốt hơn rằng giá trị này có thể cố ý để trống. Chắc chắn, nó không phải là cây đũa thần và trong thời gian chạy, nó có thể thất bại giống hệt như biến nullable. Tuy nhiên, theo tôi, việc tiếp nhận API tốt hơn với Optionaltôi.
dùng1053510

31

Khác với IDE của bạn cung cấp cho bạn các gợi ý khi bạn chuyển nullsang các phương thức dự kiến ​​đối số sẽ không có giá trị, còn có các ưu điểm khác:

  • Các công cụ phân tích mã tĩnh có thể kiểm tra giống như IDE của bạn (ví dụ FindBugs)
  • Bạn có thể sử dụng AOP để kiểm tra các xác nhận này

Điều này có thể giúp mã của bạn dễ bảo trì hơn (vì bạn không cần nullkiểm tra) và ít bị lỗi hơn.


9
Tôi đồng cảm với OP ở đây, bởi vì mặc dù bạn trích dẫn hai lợi thế này, trong cả hai trường hợp bạn đã sử dụng từ "có thể". Điều đó có nghĩa là không có gì đảm bảo rằng những kiểm tra này sẽ thực sự xảy ra. Bây giờ, sự khác biệt về hành vi đó có thể hữu ích cho các thử nghiệm nhạy cảm với hiệu suất mà bạn muốn tránh chạy trong chế độ sản xuất mà chúng tôi có assert. Tôi tìm thấy @Nullable@Nonnulltrở thành những ý tưởng hữu ích, nhưng tôi muốn có nhiều lực lượng hơn đằng sau chúng, thay vì chúng tôi đưa ra giả thuyết về những gì người ta có thể làm với chúng, điều này vẫn để ngỏ khả năng không làm gì với chúng.
seh

2
Câu hỏi là bắt đầu từ đâu. Tại thời điểm của anh ấy là tùy chọn. Đôi khi tôi muốn nó nếu họ không bởi vì trong một số trường hợp, sẽ rất hữu ích khi thực thi chúng ...
Uwe Plonus

Tôi có thể hỏi AOP bạn đang đề cập ở đây là gì không?
Chris.Zou 14/03/2015

@ Chris.Zou AOP có nghĩa là lập trình hướng theo khía cạnh, ví dụ AspectJ
Uwe Plonus 14/03/2015

13

Tôi nghĩ rằng câu hỏi ban đầu này gián tiếp chỉ ra một khuyến nghị chung rằng kiểm tra con trỏ null thời gian chạy vẫn cần thiết, mặc dù @NonNull được sử dụng. Tham khảo liên kết sau:

Chú thích loại mới của Java 8

Trong blog trên, chúng tôi khuyên bạn nên:

Chú thích loại tùy chọn không thể thay thế cho xác thực thời gian chạy Trước khi chú thích loại, vị trí chính để mô tả những thứ như nullable hoặc phạm vi là trong javadoc. Với các chú thích Loại, giao tiếp này đi vào mã byte theo cách để xác minh thời gian biên dịch. Mã của bạn vẫn nên thực hiện xác nhận thời gian chạy.


1
Hiểu, nhưng kiểm tra lint mặc định cảnh báo rằng kiểm tra null thời gian chạy là không cần thiết, mà ở ấn tượng đầu tiên dường như không khuyến khích khuyến nghị này.
swooby

1
@swooby Thông thường tôi bỏ qua các cảnh báo lint nếu tôi chắc chắn mã của mình là chính xác. Những cảnh báo đó không phải là lỗi.
jonathanzh

12

Biên dịch ví dụ ban đầu trong Eclipse ở mức tuân thủ 1.8 và với phân tích null dựa trên chú thích được bật, chúng tôi nhận được cảnh báo này:

    directPathToA(y);
                  ^
Null type safety (type annotations): The expression of type 'Integer' needs unchecked conversion to conform to '@NonNull Integer'

Cảnh báo này được diễn đạt tương tự như những cảnh báo bạn nhận được khi trộn mã được tạo ra với mã kế thừa bằng cách sử dụng các loại thô ("chuyển đổi không được kiểm tra"). Chúng ta có cùng một tình huống ở đây: phương thức indirectPathToA()có chữ ký "di sản" ở chỗ nó không chỉ định bất kỳ hợp đồng null nào. Các công cụ có thể dễ dàng báo cáo điều này, vì vậy chúng sẽ đuổi bạn xuống tất cả các con hẻm nơi chú thích null cần được truyền bá nhưng chưa được.

Và khi sử dụng một cách thông minh @NonNullByDefault chúng ta thậm chí không cần phải nói điều này mỗi lần.

Nói cách khác: việc chú thích null có "tuyên truyền rất xa" hay không có thể phụ thuộc vào công cụ bạn sử dụng và mức độ nghiêm trọng của bạn đối với tất cả các cảnh báo do công cụ đưa ra. Với các chú thích null TYPE_USE, cuối cùng bạn cũng có tùy chọn để cho phép công cụ cảnh báo bạn về mọi NPE có thể có trong chương trình của bạn, bởi vì null đã trở thành một thuộc tính phức tạp của hệ thống loại.


8

Tôi đồng ý rằng các chú thích "không truyền bá rất xa". Tuy nhiên, tôi thấy lỗi ở phía lập trình viên.

Tôi hiểu Nonnullchú thích là tài liệu. Phương thức sau đây biểu thị yêu cầu (như một điều kiện tiên quyết) một đối số không null x.

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }

Đoạn mã sau đây có lỗi. Phương thức gọi directPathToA()mà không thực thi đó ylà không null (nghĩa là nó không đảm bảo điều kiện tiên quyết của phương thức được gọi). Một khả năng là thêm một Nonnullchú thích vào indirectPathToA()(tuyên truyền điều kiện tiên quyết). Khả năng hai là kiểm tra tính vô hiệu của yin indirectPathToA()và tránh cuộc gọi đến directPathToA()khi nào ylà null.

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }

1
Tuyên truyền @Nonnull cần có indirectPathToA(@Nonnull Integer y)là IMHO một thực tiễn tồi: bạn sẽ cần phải truyền bá trên ngăn xếp cuộc gọi đầy đủ (vì vậy nếu bạn thêm một nullđăng ký directPathToA(), bạn sẽ cần phải thay thế @Nonnullbằng @Nullabletrong ngăn xếp cuộc gọi đầy đủ). Đây sẽ là một nỗ lực bảo trì lớn cho các ứng dụng lớn.
Julien Kronegg

Chú thích @Nullull chỉ nhấn mạnh rằng xác minh null của đối số là về phía bạn (bạn phải đảm bảo rằng bạn vượt qua giá trị không null). Nó không phải là trách nhiệm của phương pháp.
Alexander Drobyshevsky

@Nonnull cũng là điều hợp lý khi giá trị null không có ý nghĩa gì đối với phương pháp này
Alexander Drobyshevsky

5

Những gì tôi làm trong các dự án của mình là kích hoạt tùy chọn sau trong kiểm tra mã "Điều kiện và ngoại lệ không đổi":
Đề xuất chú thích @Nullable cho các phương thức có thể trả về null và báo cáo các giá trị nullable được truyền cho các tham số không chú thích Kiểm tra

Khi được kích hoạt, tất cả các tham số không chú thích sẽ được coi là không null và do đó bạn cũng sẽ thấy cảnh báo về cuộc gọi gián tiếp của mình:

clazz.indirectPathToA(null); 

Để kiểm tra mạnh mẽ hơn, Khung kiểm tra có thể là một lựa chọn tốt (xem hướng dẫn hay này .
Lưu ý : Tôi chưa sử dụng và có thể có vấn đề với trình biên dịch Jack: xem lỗi này


4

Trong Java, tôi sẽ sử dụng loại Tùy chọn của Guava . Là một loại thực tế bạn có được trình biên dịch đảm bảo về việc sử dụng nó. Thật dễ dàng để bỏ qua nó và có được một NullPointerException, nhưng ít nhất chữ ký của phương thức truyền đạt rõ ràng những gì nó mong đợi như là một đối số hoặc những gì nó có thể trả về.


16
Bạn phải cẩn thận với điều này. Tùy chọn chỉ nên được sử dụng khi giá trị thực sự là tùy chọn và sự vắng mặt của giá trị đó được sử dụng làm cổng quyết định cho logic tiếp theo. Tôi đã thấy điều này bị lạm dụng với việc thay thế các đối tượng bằng Tùy chọn và kiểm tra null được thay thế bằng các kiểm tra về sự hiện diện mà thiếu điểm.
Christopher Perry

nếu bạn đang nhắm mục tiêu JDK 8 hoặc mới hơn, hãy sử dụng java.util.Optionalthay vì lớp của Guava. Xem ghi chú / so sánh của Guava để biết chi tiết về sự khác biệt.
AndrewF

1
"kiểm tra null được thay thế bằng kiểm tra cho sự hiện diện mà thiếu điểm" bạn có thể giải thích rõ hơn, sau đó, vấn đề gì? Theo tôi, đây không phải là lý do duy nhất cho Tùy chọn, nhưng chắc chắn đây là lý do lớn nhất và tốt nhất.
scubbo

4

Nếu bạn sử dụng Kotlin, nó hỗ trợ các chú thích nullable này trong trình biên dịch của nó và sẽ ngăn bạn chuyển null sang phương thức java yêu cầu đối số không null. Sự kiện mặc dù câu hỏi này ban đầu được nhắm mục tiêu tại Java, tôi đề cập đến tính năng Kotlin này vì nó được nhắm mục tiêu cụ thể vào các chú thích Java này và câu hỏi là "Có cách nào để làm cho các chú thích này được thi hành nghiêm ngặt hơn và / hoặc tuyên truyền hơn nữa không?" và tính năng này làm cho các chú thích này được thực thi nghiêm ngặt hơn .

Lớp Java sử dụng @NotNullchú thích

public class MyJavaClazz {
    public void foo(@NotNull String myString) {
        // will result in an NPE if myString is null
        myString.hashCode();
    }
}

Lớp Kotlin gọi lớp Java và truyền null cho đối số được chú thích bằng @NotNull

class MyKotlinClazz {
    fun foo() {
        MyJavaClazz().foo(null)
    }
}  

Lỗi trình biên dịch Kotlin thi hành @NotNullchú thích.

Error:(5, 27) Kotlin: Null can not be a value of a non-null type String

xem: http://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations


3
Câu hỏi đề cập đến Java, trên mỗi thẻ đầu tiên của nó, chứ không phải là Kotlin.
seh

1
@seh xem cập nhật tại sao câu trả lời này có liên quan đến câu hỏi này.
Mike Rylander

2
Đủ công bằng. Đó là một tính năng hay của Kotlin. Tôi chỉ không nghĩ rằng nó sẽ làm hài lòng những người đến đây để tìm hiểu về Java.
seh

nhưng truy cập myString.hashCode()vẫn sẽ ném NPE ngay cả khi @NotNullkhông được thêm vào tham số. Vì vậy, những gì cụ thể hơn về việc thêm nó?
kAmol

@kAmol Sự khác biệt ở đây là khi sử dụng Kotlin, bạn sẽ gặp lỗi thời gian biên dịch thay vì lỗi thời gian chạy . Chú thích là để thông báo cho bạn rằng nhà phát triển cần đảm bảo rằng null không được truyền vào. Điều này sẽ không ngăn null được truyền vào trong thời gian chạy, nhưng nó sẽ ngăn bạn viết mã gọi phương thức này bằng null (hoặc với một chức năng có thể trả về null).
Mike Rylander

-4

Vì tính năng mới của Java 8 Tùy chọn, bạn không nên sử dụng @Nullable hoặc @Notnull trong mã của riêng bạn nữa . Lấy ví dụ dưới đây:

public void printValue(@Nullable myValue) {
    if (myValue != null) {
        System.out.print(myValue);
    } else {
        System.out.print("I dont have a value");
}

Nó có thể được viết lại bằng:

public void printValue(Optional<String> myValue) {
    if (myValue.ifPresent) {
        System.out.print(myValue.get());
    } else {
        System.out.print("I dont have a value");
}

Sử dụng một tùy chọn buộc bạn phải kiểm tra giá trị null . Trong đoạn mã trên, bạn chỉ có thể truy cập giá trị bằng cách gọiget phương thức.

Một ưu điểm khác là mã có thể dễ đọc hơn . Với việc bổ sung Java 9 ifPftimeOrElse , hàm thậm chí có thể được viết là:

public void printValue(Optional<String> myValue) {
    myValue.ifPresentOrElse(
        v -> System.out.print(v),
        () -> System.out.print("I dont have a value"),
    )
}

Ngay cả với Optional, vẫn còn nhiều thư viện và khung sử dụng các chú thích này sao cho không thể cập nhật / thay thế tất cả các phụ thuộc của bạn bằng các phiên bản được cập nhật để sử dụng Tùy chọn. Optionaltuy nhiên có thể giúp đỡ trong các tình huống mà bạn sử dụng null trong mã của riêng bạn.
Mike Rylander
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.