วิธีการใช้คำอธิบายประกอบ @Nullable และ @Nonnull มีประสิทธิภาพมากขึ้นได้อย่างไร


140

ฉันเห็นว่าคำอธิบายประกอบ@Nullableและอาจมีประโยชน์ในการป้องกันs แต่พวกเขาไม่เผยแพร่ไกลมาก@NonnullNullPointerException

  • ประสิทธิผลของการเพิ่มความคิดเห็นเหล่านี้จะลดลงอย่างสมบูรณ์หลังจากผ่านไปหนึ่งระดับทางอ้อมดังนั้นหากคุณเพิ่มเพียงไม่กี่อย่างพวกเขาก็จะไม่เผยแพร่ไกลมากนัก
  • เนื่องจากคำอธิบายประกอบเหล่านี้ไม่ได้บังคับใช้อย่างดีมีความเสี่ยงที่จะถือว่าค่าที่ทำเครื่องหมายด้วย@Nonnullนั้นไม่เป็นโมฆะดังนั้นจึงไม่สามารถทำการตรวจสอบโมฆะได้

โค้ดข้างล่างนี้เป็นสาเหตุของพารามิเตอร์ที่มีเครื่องหมาย@Nonnullที่จะเป็นnullโดยไม่ต้องเพิ่มข้อร้องเรียนใด ๆ มันจะพ่นNullPointerExceptionเมื่อมันถูกเรียกใช้

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

มีวิธีที่จะทำให้คำอธิบายประกอบเหล่านี้มีการบังคับใช้อย่างเข้มงวดมากขึ้นและ / หรือเผยแพร่เพิ่มเติมหรือไม่


1
ฉันชอบความคิดของ@Nullableหรือ@Nonnullแต่ถ้าพวกเขามีค่ามันมาก "น่าจะเรียกร้องการอภิปราย"
Maarten Bodewes

ฉันคิดว่าวิธีที่จะย้ายไปยังโลกที่สิ่งนี้ทำให้เกิดข้อผิดพลาดหรือคำเตือนของคอมไพเลอร์คือต้องใช้ cast @Nonnullเมื่อโทรหา@Nonnullวิธีที่มีตัวแปรที่ไม่สามารถใช้ได้ แน่นอนว่าการหล่อด้วยคำอธิบายประกอบนั้นเป็นไปไม่ได้ใน Java 7 แต่ Java 8 จะเพิ่มความสามารถในการใช้คำอธิบายประกอบกับการใช้ตัวแปรรวมถึงการปลดเปลื้อง ดังนั้นนี่อาจเป็นไปได้ที่จะนำไปใช้ใน Java 8
Theodore Murdock

1
@TheodoreMurdock, ใช่, ใน Java 8 การส่งสัญญาณ(@NonNull Integer) yมีความเป็นไปได้ทาง syntactically, แต่คอมไพเลอร์ไม่ได้รับอนุญาตให้ปล่อยรหัสไบต์เฉพาะใด ๆ ที่อ้างอิงจากคำอธิบายประกอบ. สำหรับวิธีการยืนยันแบบรันไทม์วิธีใช้ตัวช่วยตัวจิ๋วนั้นก็เพียงพอแล้วดังที่กล่าวไว้ในbugs.eclipse.org/442103 (เช่น, directPathToA(assertNonNull(y))) - แต่โปรดจำไว้ว่านี่จะช่วยให้ล้มเหลวอย่างรวดเร็วเท่านั้น วิธีที่ปลอดภัยเพียงอย่างเดียวคือการตรวจสอบ null จริง (บวกกับการใช้งานทางเลือกในสาขาอื่น)
Stephan Herrmann

1
มันจะเป็นประโยชน์ในคำถามนี้จะพูดถึงที่@Nonnullและ@Nullableคุณกำลังพูดถึงในขณะที่มีหลาย annoations คล้ายกัน (ดูคำถามนี้ ) คุณกำลังพูดถึงคำอธิบายประกอบในแพ็คเกจjavax.annotationหรือไม่
James Dunn

1
@TJamesBoone สำหรับบริบทของคำถามนี้มันไม่สำคัญนี่เป็นเรื่องเกี่ยวกับวิธีการใช้งานใด ๆ ของพวกเขาอย่างมีประสิทธิภาพ
Mike Rylander

คำตอบ:


66

คำตอบสั้น ๆ : ฉันเดาว่าคำอธิบายประกอบเหล่านี้จะเป็นประโยชน์สำหรับ IDE ของคุณเท่านั้นที่จะเตือนคุณถึงข้อผิดพลาดที่อาจเป็นไปได้

ตามที่กล่าวไว้ในหนังสือ "Clean Code" คุณควรตรวจสอบพารามิเตอร์ของวิธีสาธารณะและหลีกเลี่ยงการตรวจสอบค่าคงที่

เคล็ดลับที่ดีอีกข้อหนึ่งคือไม่ส่งคืนค่าNullแต่ใช้Null Object Patternแทน


10
สำหรับค่าตอบแทนอาจว่างเปล่าฉันขอแนะนำให้ใช้Optionalประเภทแทนธรรมดาnull
Patrick

7
ตัวเลือกไม่ดีกว่า "null" เป็นตัวเลือก # get () จะพ่น NoSuchElementException ขณะที่การใช้ null จะส่ง NullPointerException ทั้งคู่เป็น RuntimeException โดยไม่มีคำอธิบายที่มีความหมาย ฉันชอบตัวแปรที่ไม่มีค่า
30thth

4
@ 30thh ทำไมคุณถึงใช้ตัวเลือกรับ () โดยตรงและไม่ใช่ตัวเลือกตัวเลือกคือ () หรือตัวเลือกเพิ่มเติม
GauravJ

7
@GauravJ เหตุใดคุณต้องใช้ตัวแปรที่สามารถทำให้เป็นโมฆะได้โดยตรงและไม่ตรวจสอบหากเป็นโมฆะอันดับแรก ;-)
30thh

5
ความแตกต่างระหว่างOptionalและเป็นโมฆะในกรณีนี้คือOptionalการสื่อสารที่ดีกว่าว่าค่านี้สามารถว่างเปล่าโดยเจตนา แน่นอนว่ามันไม่ใช่ไม้เท้าวิเศษและในรันไทม์มันอาจล้มเหลวในลักษณะเดียวกับตัวแปร nullable อย่างไรก็ตามการรับ API โดยโปรแกรมเมอร์ดีกว่าด้วยOptionalในความคิดของฉัน
1053510

31

นอกเหนือจาก IDE ของคุณให้คำแนะนำเมื่อคุณผ่านnullไปยังวิธีการที่คาดว่าอาร์กิวเมนต์จะไม่เป็นโมฆะมีข้อดีเพิ่มเติม:

  • เครื่องมือวิเคราะห์รหัสแบบคงที่สามารถทดสอบเช่นเดียวกับ IDE ของคุณ (เช่น FindBugs)
  • คุณสามารถใช้ AOP เพื่อตรวจสอบการยืนยันเหล่านี้

วิธีนี้จะช่วยให้รหัสของคุณสามารถบำรุงรักษาได้มากขึ้น (เนื่องจากคุณไม่จำเป็นต้องnullตรวจสอบ) และมีข้อผิดพลาดน้อยลง


9
ฉันเห็นอกเห็นใจกับ OP ที่นี่เพราะแม้ว่าคุณจะอ้างถึงข้อได้เปรียบทั้งสองนี้ในทั้งสองกรณีคุณใช้คำว่า "ทำได้" นั่นหมายความว่าไม่มีการรับประกันว่าการตรวจสอบเหล่านี้จะเกิดขึ้นจริง assertตอนนี้ที่แตกต่างพฤติกรรมอาจจะมีประโยชน์สำหรับการทดสอบประสิทธิภาพการทำงานที่สำคัญที่คุณต้องการที่จะหลีกเลี่ยงการทำงานในโหมดการผลิตที่เรามี ฉันค้นหา@Nullableและ@Nonnullเป็นแนวคิดที่มีประโยชน์ แต่ฉันต้องการบังคับมากกว่าเบื้องหลังพวกเขามากกว่าที่เราตั้งสมมติฐานเกี่ยวกับสิ่งที่สามารถทำได้กับพวกเขาซึ่งยังคงเปิดโอกาสในการทำอะไรกับพวกเขา
seh

2
คำถามคือที่จะเริ่มต้น ในขณะที่การเพิ่มความคิดเห็นของเขาเป็นตัวเลือก Somtimes ฉันจะชอบมันถ้าพวกเขาไม่ได้เพราะในบางสถานการณ์มันจะเป็นประโยชน์ในการบังคับใช้พวกเขา ...
Uwe Plonus

ฉันขอถาม AOP ที่คุณแนะนำที่นี่ได้ไหม
Chris.Zou

@ Chris.Zou AOP หมายถึงการเขียนโปรแกรมเชิงลักษณะเช่น AspectJ
Uwe Plonus

13

ฉันคิดว่าคำถามเดิมนี้ชี้ไปที่คำแนะนำทั่วไปโดยอ้อมว่ายังจำเป็นต้องใช้การตรวจสอบตัวชี้โมฆะในขณะใช้งานแม้ว่าจะใช้ @NonNull ก็ตาม อ้างอิงถึงลิงค์ต่อไปนี้:

คำอธิบายประกอบแบบใหม่ของ Java 8

ในบล็อกด้านบนขอแนะนำว่า:

คำอธิบายประกอบแบบตัวเลือกไม่ได้ใช้แทนการตรวจสอบความถูกต้องแบบรันไทม์ ก่อนที่จะทำการเพิ่มความคิดเห็นแบบชนิดตำแหน่งหลักสำหรับการอธิบายสิ่งต่าง ๆ เช่นความสามารถในการ null หรือช่วงอยู่ใน javadoc ด้วยคำอธิบายประกอบประเภทการสื่อสารนี้เข้ามาใน bytecode ในวิธีการตรวจสอบเวลารวบรวม รหัสของคุณยังควรตรวจสอบความถูกต้องรันไทม์


1
เข้าใจ แต่การตรวจสอบผ้าสำลีเริ่มต้นเตือนว่าการตรวจสอบโมฆะแบบรันไทม์นั้นไม่จำเป็น
swooby

1
@swooby ฉันมักจะไม่สนใจคำเตือนที่เป็นขุยถ้าฉันแน่ใจว่ารหัสของฉันถูกต้อง คำเตือนเหล่านั้นไม่ใช่ข้อผิดพลาด
jonathanzh

12

รวบรวมตัวอย่างต้นฉบับใน Eclipse ที่การปฏิบัติตาม 1.8 และเปิดใช้งานการวิเคราะห์โมฆะตามบันทึกย่อเราได้รับคำเตือนนี้:

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

คำเตือนนี้เป็นคำเปรียบเทียบกับคำเตือนที่คุณได้รับเมื่อผสมรหัสที่สร้างกับรหัสดั้งเดิมโดยใช้ประเภทดิบ ("การแปลงที่ไม่ถูกตรวจสอบ") เรามีสถานการณ์เดียวกันที่นี่: เมธอดindirectPathToA()มีลายเซ็น "ดั้งเดิม" ซึ่งไม่ได้ระบุสัญญาที่เป็นโมฆะใด ๆ เครื่องมือสามารถรายงานสิ่งนี้ได้อย่างง่ายดายดังนั้นพวกเขาจะไล่ล่าคุณตามตรอกซอกซอยทั้งหมดที่จำเป็นต้องเผยแพร่คำอธิบายประกอบแบบ null แต่ยังไม่มี

และเมื่อใช้ฉลาด@NonNullByDefaultเราไม่จำเป็นต้องพูดแบบนี้ทุกครั้ง

กล่าวอีกนัยหนึ่ง: คำอธิบายประกอบที่เป็นโมฆะหรือไม่ "เผยแพร่ไปไกลมาก" อาจขึ้นอยู่กับเครื่องมือที่คุณใช้และขึ้นอยู่กับว่าคุณเข้าร่วมอย่างจริงจังกับคำเตือนทั้งหมดที่ออกโดยเครื่องมือนี้หรือไม่ ด้วยTYPE_USE คำอธิบายประกอบ nullคุณก็มีทางเลือกที่จะให้เครื่องมือเตือนคุณเกี่ยวกับทุก NPE เป็นไปได้ในโปรแกรมของคุณเพราะ nullness ได้กลายเป็นสถานที่ให้เนื้อเสียงของระบบการพิมพ์


8

ฉันยอมรับว่าคำอธิบายประกอบ "อย่าเผยแพร่ไปไกลมาก" อย่างไรก็ตามฉันเห็นความผิดพลาดในด้านของโปรแกรมเมอร์

ฉันเข้าใจNonnullคำอธิบายประกอบเป็นเอกสาร เป็นการแสดงออกถึงวิธีการดังต่อไปนี้คือต้อง (เป็นสิ่งที่จำเป็น) xอาร์กิวเมนต์

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

ตัวอย่างโค้ดต่อไปนี้มีข้อบกพร่อง การเรียกdirectPathToA()ใช้เมธอดโดยไม่บังคับใช้ที่yไม่ใช่ค่าว่าง (นั่นคือไม่รับประกันว่าเงื่อนไขของวิธีการที่เรียกใช้) ความเป็นไปได้อย่างหนึ่งคือการเพิ่มNonnullคำอธิบายประกอบลงในindirectPathToA()(เผยแพร่สิ่งที่จำเป็น) ความเป็นไปได้ที่สองคือการตรวจสอบค่าว่างyในindirectPathToA()และหลีกเลี่ยงการเรียกdirectPathToA()เมื่อyเป็นค่าว่าง

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

1
การเผยแพร่สิ่ง@Nonnull ที่มีindirectPathToA(@Nonnull Integer y)คือ IMHO เป็นวิธีปฏิบัติที่ไม่ดี: คุณจะต้องควบคุมการแพร่กระจายของสายการโทรแบบเต็ม (ดังนั้นหากคุณเพิ่มการnullเช็คอินdirectPathToA()คุณจะต้องแทนที่@Nonnullด้วย@Nullableในสแต็กการโทรแบบเต็ม) นี่จะเป็นความพยายามในการบำรุงรักษาครั้งใหญ่สำหรับแอปพลิเคชันขนาดใหญ่
Julien Kronegg

@Nonnull คำอธิบายประกอบเพียงแค่เน้นว่าการตรวจสอบข้อโต้แย้งเป็นโมฆะอยู่ข้างคุณ (คุณต้องรับประกันว่าคุณผ่านค่าที่ไม่เป็นโมฆะ) มันไม่ได้เป็นความรับผิดชอบของวิธีการ
Alexander Drobyshevsky

@Nonnull ยังเป็นสิ่งที่เหมาะสมเมื่อ null มูลค่าไม่ทำให้รู้สึกใด ๆ สำหรับวิธีนี้
อเล็กซานเด Drobyshevsky

5

สิ่งที่ฉันทำในโครงการของฉันคือการเปิดใช้งานตัวเลือกต่อไปนี้ในการตรวจสอบรหัส "ค่าคงที่และข้อยกเว้น":
แนะนำ @ คำอธิบายประกอบแบบไม่สามารถแก้ไขได้สำหรับวิธีการที่อาจส่งคืนค่า null และรายงานค่า nullable การตรวจสอบ

เมื่อเปิดใช้งานพารามิเตอร์ที่ไม่ได้ใส่คำอธิบายประกอบทั้งหมดจะถือเป็นไม่เป็นโมฆะดังนั้นคุณจะเห็นคำเตือนเกี่ยวกับการโทรทางอ้อมของคุณ:

clazz.indirectPathToA(null); 

สำหรับการตรวจสอบที่แข็งแกร่งตัวตรวจสอบกรอบอาจจะเป็นทางเลือกที่ดี (ดูนี้ดีกวดวิชา .
หมายเหตุ : ฉันไม่ได้ใช้ที่ยังและอาจจะมีปัญหากับคอมไพเลอร์แจ็ค: ดูรายงานข้อบกพร่องนี้


4

ใน Java ฉันต้องการใช้เลือกประเภทของฝรั่ง เป็นประเภทจริงคุณจะได้รับคอมไพเลอร์รับประกันเกี่ยวกับการใช้งาน มันง่ายที่จะข้ามมันและได้รับNullPointerExceptionแต่อย่างน้อยลายเซ็นของวิธีการสื่อสารอย่างชัดเจนสิ่งที่คาดว่าจะเป็นข้อโต้แย้งหรือสิ่งที่มันอาจกลับมา


16
คุณต้องระวังสิ่งนี้ ทางเลือกควรใช้เฉพาะเมื่อค่าเป็นทางเลือกจริงเท่านั้นและไม่มีการใช้ค่านี้เป็นประตูการตัดสินใจสำหรับตรรกะเพิ่มเติม ฉันเคยเห็นสิ่งนี้ถูกทำร้ายด้วยการแทนที่ผ้าห่มของวัตถุด้วยตัวเลือกและการตรวจสอบโมฆะแทนที่ด้วยการตรวจสอบสำหรับการปรากฏตัวซึ่งพลาดจุด
Christopher Perry

หากคุณกำลังกำหนดเป้าหมาย JDK 8 หรือใหม่กว่าต้องการใช้java.util.Optionalแทนคลาสของ Guava ดูบันทึก / การเปรียบเทียบของ Guavaสำหรับรายละเอียดเกี่ยวกับความแตกต่าง
AndrewF

1
"การตรวจสอบ null แทนที่ด้วยการตรวจสอบสำหรับการปรากฏตัวที่คิดถึงจุด" คุณสามารถทำอย่างละเอียดแล้วในสิ่งที่จุดคือ ? นี่ไม่ใช่เหตุผลเดียวสำหรับตัวเลือกในความคิดของฉัน แต่มันก็ไกลโดยที่ใหญ่ที่สุดและดีที่สุด
scubbo

4

หากคุณใช้ Kotlin จะสนับสนุนหมายเหตุประกอบความสามารถในการ null เหล่านี้ในคอมไพเลอร์และจะป้องกันไม่ให้คุณส่งผ่าน null ไปยังวิธีการ java ที่ต้องการอาร์กิวเมนต์ที่ไม่ใช่ค่า null เหตุการณ์แม้ว่าคำถามนี้มีการกำหนดเป้าหมายที่ Java แต่ฉันพูดถึงคุณลักษณะ Kotlin นี้เนื่องจากมีการกำหนดเป้าหมายเฉพาะที่คำอธิบายประกอบ Java เหล่านี้และคำถามคือ"มีวิธีที่จะทำให้คำอธิบายประกอบเหล่านี้มีการบังคับใช้อย่างเข้มงวดมากขึ้น และคุณลักษณะนี้จะให้คำอธิบายประกอบเหล่านี้มีผลบังคับใช้อย่างเคร่งครัดมากขึ้น

คลาส Java โดยใช้@NotNullหมายเหตุประกอบ

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

คลาส Kotlin เรียกคลาส Java และส่งผ่านค่า null สำหรับอาร์กิวเมนต์ที่เพิ่มความคิดเห็นด้วย @NotNull

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

ข้อผิดพลาดของคอมไพเลอร์ Kotlin บังคับใช้@NotNullคำอธิบายประกอบ

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

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


3
คำถามนี้กล่าวถึง Java ตามแท็กแรกไม่ใช่ Kotlin
seh

1
@seh ดูการอัปเดตสำหรับสาเหตุที่คำตอบนี้เกี่ยวข้องกับคำถามนี้
Mike Rylander

2
ยุติธรรมพอสมควร นั่นเป็นคุณสมบัติที่ดีของ Kotlin ฉันไม่คิดว่ามันจะเป็นที่พึงพอใจสำหรับผู้ที่มาที่นี่เพื่อเรียนรู้เกี่ยวกับ Java
seh

แต่การเข้าถึงmyString.hashCode()จะยังคงส่ง NPE แม้ว่า@NotNullจะไม่ได้เพิ่มไว้ในพารามิเตอร์ ดังนั้นมีความเฉพาะเจาะจงมากขึ้นเกี่ยวกับการเพิ่มมัน?
kAmol

@kAmol ความแตกต่างที่นี่คือเมื่อใช้ Kotlin คุณจะได้รับเวลารวบรวมข้อผิดพลาดแทนข้อผิดพลาดruntime คำอธิบายประกอบคือเพื่อแจ้งให้คุณทราบว่านักพัฒนาจำเป็นต้องตรวจสอบให้แน่ใจว่าไม่มีการส่งค่า null ซึ่งจะไม่ป้องกันการส่งผ่านค่า null ในขณะทำงาน แต่จะป้องกันไม่ให้คุณเขียนรหัสที่เรียกใช้วิธีนี้ด้วยค่า Null ด้วยฟังก์ชั่นที่สามารถคืนค่า null)
Mike Rylander

-4

ตั้งแต่ Java 8 คุณลักษณะใหม่ที่เป็นตัวเลือกที่คุณไม่ควรใช้ @Nullable หรือ @Notnull ในรหัสของคุณเองอีกต่อไป นำตัวอย่างด้านล่าง:

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

สามารถเขียนใหม่ด้วย:

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

โดยใช้ตัวเลือกบังคับให้คุณตรวจสอบค่า Null ในรหัสด้านบนคุณสามารถเข้าถึงค่าได้โดยการเรียกใช้getเมธอด

ประโยชน์อีกประการหนึ่งคือการที่รับโค้ดอ่านได้มากขึ้น ด้วยการเพิ่ม Java 9 ifPresentOrElseฟังก์ชันอาจถูกเขียนเป็น:

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

แม้ว่าจะOptionalมีไลบรารีและกรอบงานจำนวนมากที่ใช้คำอธิบายประกอบเหล่านี้ซึ่งไม่สามารถอัปเดต / แทนที่การอ้างอิงทั้งหมดของคุณด้วยเวอร์ชันที่อัพเดตเพื่อใช้ Optionals Optionalอย่างไรก็ตามสามารถช่วยในสถานการณ์ที่คุณใช้ null ภายในโค้ดของคุณเอง
Mike Rylander
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.