Using null type annotations in practice...Using null type annotations in practice Till Brychcy,...

34
Using null type annotations in practice Till Brychcy, Mercateo EclipseCon Europe, 2017

Transcript of Using null type annotations in practice...Using null type annotations in practice Till Brychcy,...

Using null type annotations in practice

Till Brychcy, Mercateo

EclipseCon Europe, 2017

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

EclipseCon Europe, 2017

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

Workspace-wide EEA for the JDK

EclipseCon Europe, 2017

EclipseCon Europe, 2017

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

EclipseCon Europe, 2017

Preview to Photon

• Quick fix to add @NonNullByDefault to package

• Java 9: @NonNullByDefault in module-info.java

• Maybe support 3rd-party NonNullByDefault variations (TypeQualifierDefault)

• More quick assists that avoid creating redundant @NonNull