Using null type annotations in practice...Using null type annotations in practice Till Brychcy,...
Transcript of Using null type annotations in practice...Using null type annotations in practice Till Brychcy,...
EclipseCon Europe, 2017
• What they are, why and when to use them
• @Nullable vs. java.util.Optional
• Configuration choices
• Switching from declaration annotations to type annotations
• How to get warning free code - Code Patterns and Antipatterns
• Free Type variables and extends @Nullable
• Arrays
• Improvements released with Oxygen
• Preview to Photon
EclipseCon Europe, 2017
Some Statistics• First commit: Sep 18 2002
• First commit with null annotations: Jul 2 2012
• Switched to java 8 and null type annotations: Apr 15 2015
• Sample size: 7866 java files (approx. 20% of total code base)
• 3025 files use @NonNullByDefault (no package annotations)
• 10910 @Nullable annotation in 2175 files
• 1970 @NonNull annotations in 709 files
• 1070 @SuppressWarnings(„null")
EclipseCon Europe, 2017
Before null annotations /** * * @param catalogID * @param groupID * @param searchSpec * (may be null) * @param minIndex * @param maxIndex * @param sortBy * @param sortAscending * @return String */
EclipseCon Europe, 2017
Null annotation advantages
• One thing less to worry about
• Code that is easier to understand, change and debug
• No more NullPointerException
• Fewer other bugs
EclipseCon Europe, 2017
Null annotations disadvantages
• Compiler sometimes needs help: Avoid some code patterns
• Syntax a bit ugly
EclipseCon Europe, 2017
Newer languages: nice syntax• kotlin
• var x: String? = null;
• swift
• var x: String? = nil;
• c# (announced for version 8)
• string? x = null;
EclipseCon Europe, 2017
Null Annotations
• Normal use: @NonNullByDefault + @Nullable
• @NonNull: only for type parameters and during migration
EclipseCon Europe, 2017
Declaration vs. type annotations• Declaration annotation:
@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE }) public @interface Nullable { }
• Type annotation:@Target({ TYPE_USE }) public @interface Nullable { }
• "Mixed" annotations are allowed by Java, but bad as null annotations:@Target({ TYPE_USE, FIELD, METHOD, PARAMETER, LOCAL_VARIABLE }) public @interface Nullable { }
EclipseCon Europe, 2017
Declaration annotation usages@NonNullByDefault public class Example { @Nullable String field;
@Nullable String add(@Nullable String arg1, List<String> list, @Nullable String[] array) { // just to show the syntax (local variables usually don't need annotations) @Nullable String local = arg1;
list.add(null); // is this OK? if(array != null) { array[0] = null; // or this? }
return local; } }
EclipseCon Europe, 2017
Equivalent with type annotations@NonNullByDefault({ FIELD, PARAMETER, RETURN_TYPE }) public class Example { @Nullable String field;
@Nullable String add(@Nullable String arg1, List<String> list, String @Nullable [] array) { // just to show the syntax (local variables usually don't need annotations) @Nullable String local = arg1;
list.add(null); // is this OK? if(array != null) { array[0] = null; // or this? }
return local; } }
EclipseCon Europe, 2017
Completely annotated@NonNullByDefault({ FIELD, PARAMETER, RETURN_TYPE, ARRAY_CONTENTS, TYPE_ARGUMENT }) public class Example { @Nullable String field;
@Nullable String add(@Nullable String arg1, List<@Nullable String> list, @Nullable String @Nullable [] array) { // just to show the syntax (local variables usually don't need annotations) @Nullable String local = arg1;
list.add(null); // OK! if (array != null) { array[0] = null; // OK! }
return local; } }
EclipseCon Europe, 2017
@NonNullByDefault defaultspublic enum DefaultLocation { PARAMETER, RETURN_TYPE, FIELD, TYPE_PARAMETER, TYPE_BOUND, TYPE_ARGUMENT, ARRAY_CONTENTS }
@Target({ ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE }) public @interface NonNullByDefault { DefaultLocation[] value() default { DefaultLocation.PARAMETER, DefaultLocation.RETURN_TYPE, DefaultLocation.FIELD, DefaultLocation.TYPE_ARGUMENT, DefaultLocation.TYPE_BOUND };
}
EclipseCon Europe, 2017
Type bounds: "extends"
• Think in types: Object x = ""; // OK String s = new Object(); // error
@Nullable String s1 = ""; // OK @NonNull String s2 = null; // error
⇒"String extends Object" corresponds to "@NonNull String extends @Nullable String"
• So:"@NonNull String extends @NonNull Object""@NonNull String extends @Nullable Object""@Nullable String extends @Nullable Object"
EclipseCon Europe, 2017
Free Type variables and extends @NullableWith standard @NonNullByDefault:
• class List<T> {…} T can be @Nullable or @NonNull
• user chooses List<@Nullable Integer> or List<String> (=List<@NonNull String>)
• class NumberList<T extends Number>{…} T must be @NonNull
• same as class NumberList<T extends @NonNull Number>
• same as NumberList<@NonNull T>
• class NumberList<@Nullable Number>: T must be @Nullable
• class NumberList<T extends @Nullable Number>: T can be @Nullable or @NonNull
EclipseCon Europe, 2017
Principles• Warning free workspace
• @NonNullByDefault in new code
• Add @NonNullByDefault to existing code with other changes
• If necessary, annotate related code
• @SuppressWarnings("null") is OK in certain situations
• Must be easy to use with maven (now: no maven settings at all, IDE-only)
EclipseCon Europe, 2017
@SuppressWarnings
• Generated code: hashCode & equals
• stream.filter(x->x != null).
• optional.orElse(null)
• Tests (still add @NonNullByDefault)
• Overrides for which we don’t want to add external annotations
EclipseCon Europe, 2017
Configuration choices
• Enable annotations based analysis in workspace settings (not project)
• File template with @NonNullByDefault
• DefaultLocation as favorites
• External annotations added to the JDK in Workspace
• Hide INFO in problems view
EclipseCon Europe, 2017
Why custom annotations
• Different defaults for @NonNullByDefault (e.g. exclude FIELD)
• @Retention(RUNTIME) for testing framework
• Easier to accept for users of other IDEs
EclipseCon Europe, 2017
Challenges when switching to type annotations
• Syntax for qualified names java.io.@Nullable File file (easy to fix)
• Syntax for Arrays (we used some regular expressions)
• Generics:
• Map.get() (configure external annotations)
• Generics that take a .class literal
EclipseCon Europe, 2017
Arrays
• @Nullable String @NonNull [] x;
• Problem during migration from declaration annotations
• Problem: new @NonNull String[10] contains nulls
• methods that don't care about nullness about array contents
<T extends @Nullable String> @NonNull String concat(T[] strings)
EclipseCon Europe, 2017
Observed null parameter handling
1. Don't think about it, let method caller guess
2. if(param==null) return null
3. Objects.requireNonNull(param)
4. try{…}catch(Exception e){…}
5. Assume nonnull, use javadoc for nullable
EclipseCon Europe, 2017
Some Antipatterns• No correlation analysis
• boolean b=(x != null); if(b) {x.someMethod()}
• int length = array == null ? 0 : array.length; for (int i = 0; i < length; i++)…
• No intraprocedural analysis:
• init(…)-methods in constructor
• if(isValid(x)) { x.something() }
boolean isValid(Some x) {return x != null && …}
• Event callbacks without context: e.g., org.xml.sax.ContentHandler
• Struts form beans
• some builder patterns
EclipseCon Europe, 2017
Some Good Patterns
• Empty string / collection / NOP-implementation instead of null
• final fields or even completely immutable fields
• Avoid null literals except to define class specific NULL constants
EclipseCon Europe, 2017
@Nullable vs. java.util.Optional• Two solutions for the same topic.
• "Don't care": Convenient mapping to other Optionals
• Code with Optional gets reliable with @NonNullByDefault, so use both
• Use Optional only for return values
• Optional is not Serializable
• @Nullable better when overriding methods
• Problem Optional#orElse (Guava had: #orNull)
EclipseCon Europe, 2017
Improvements in Oxygen• 54 bug fixes and enhancements related to null analysis and null annotations
• Quick Fix to move type annotations
• @NonNullByDefault
• DefaultLocation.ARRAY_CONTENTS implemented
• @Target(ElementType.FIELD) implemented
• @Target(ElementType.LOCAL_VARIABLE) implemented
• No warning for "T extends @Nullable String"
• Many quick assists avoid creating redundant @NonNull
EclipseCon Europe, 2017
• extract local variable
• create local variable for missing symbol
• create field for missing symbol
• create parameter for missing symbol
• override method
• create method (parameters, details of return type)
• change method: (add parameter)
• change method: (change parameter)
• assign expression to new local variable
• create constructor using fields
• generate delegate methods
• create method
• type change and signature change
• introduce parameter object
• extract class