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

Upload: others

Post on 22-May-2020

16 views

Category:

Documents


0 download

TRANSCRIPT

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