using null type annotations in practice...using null type annotations in practice till brychcy,...
TRANSCRIPT
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