Defeat NPE hell in Java 6 and 7 using Intellij Idea

Disclaimer


  • The article does not pretend to be the discovery of America and is of a popularizing and abstracting nature. The ways to deal with NPE in the code are far from new, but much less known than we would like.
  • One-time NPE is probably the simplest of all possible errors. This is precisely the situation when, due to the lack of a policy for their processing, NPE dominates.
  • This article does not discuss approaches that are not applicable to Java 6 and 7 (the MayBe monad, JSR-308, and Type Annotations).
  • Ubiquitous defensive programming is not considered as a method of struggle, as it litter code heavily, reduces performance, and as a result does not give the desired effect.
  • There may be some discrepancy in the terminology used and the generally accepted one. Also, the description of the checks used by Intellij Idea does not claim to be complete and accurate, since it is taken from the documentation and the observed behavior, and not from the source code.


JSR-305 to the rescue


Here I want to share the practice that I use that helps me write successfully almost completely NPE-free code. Its main idea is to use annotations about optional values ​​from a library that implements JSR-305 (com.google.code.findbugs: jsr305: 1.3.9):

  • @Nullable - An annotated value is optional;
  • @Nonnull - the opposite, respectively.

Naturally, both annotations are applicable to fields of objects and classes, arguments and return values ​​of methods, local variables. Thus, these annotations supplement the type information regarding the mandatory presence of a value.

But annotate everything for a long time and the readability of the code is sharply reduced. Therefore, as a rule, the project team accepts the agreement that everything that is not marked @Nullableis mandatory. Those who used Guava, Guice are well acquainted with this practice.

Here is an example of the possible code for such an abstract project:

import javax.annotation.Nullable;
public abstract class CodeSample {
    public void correctCode() {
        @Nullable User foundUser = findUserByName("vasya");
        if(foundUser == null) {
            System.out.println("User not found");
            return;
        }
        String fullName = Asserts.notNull(foundUser.getFullName());
        System.out.println(fullName.length());
    }
    public abstract @Nullable User findUserByName(String userName);
    private static class User {
        private String name;
        private @Nullable String fullName;
        public User(String name, @Nullable String fullName) {
            this.name = name;
            this.fullName = fullName;
        }
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        @Nullable public String getFullName() { return fullName; }
        public void setFullName(@Nullable String fullName) { this.fullName = fullName; }
    }
}

As you can see everywhere it’s clear whether it is possible to get null with reference reference.

The only caveat is that situations arise when, in the current context (for example, at a certain stage of the business process), we know for sure that something in the general case must be present. In our case, this is the full name of Vasily, which, in principle, may not be available to the user, but we know that here and now it is impossible according to the rules of business logic. For such situations, I use a simple assert utility:

import javax.annotation.Nullable;
public class Asserts {
    /**
     * For situations, when we definitely know that optional value cannot be null in current context.
     */
    public static  T notNull(@Nullable T obj) {
        if(obj == null) {
            throw new IllegalStateException();
        }
        return obj;
    }
}

Real java asserts can also be used, but they didn’t get accustomed to me because of the need for explicit inclusion in runtime and less convenient syntax.

A few words about inheritance and covariance / contravariance:

  • if the return type of the ancestor method is NotNull, then the overridden successor method must also be NotNull. The rest is valid;
  • if the ancestor method argument is Nullable, then the overridden descendant method must also be Nullable. The rest is valid.

In fact, this is already quite enough and static analysis (in the IDE or CI) is not particularly needed. But let the IDE work, it’s not for nothing that they bought it. I prefer to use Intellij Idea, so all further examples will be on it.

Intellij Idea makes life better


I must say right away that, by default, Idea offers its annotations with similar semantics, although it understands everyone else. You can change this in Settings -> Inspections -> Probable bugs -> {Constant conditions & exceptions; @NotNull/@Nullableproblems}. In both inspections, select the pair of annotations to use.

Here's how Idea looks like highlighting errors found by inspections in an incorrect implementation of the previous code:


It’s become quite wonderful, the IDE not only finds two NPEs, but also forces us to do something with them.

Everything seemed to be fine, but the Idea built-in static analyzer does not understand the default binding agreement that we have accepted. From her point of view (as well as any other stat. Analyzer) three options appear here:
  • Nullable - the value is required;
  • NotNull - the value is optional;
  • Unknown - nothing is known about the binding meaning.

And all that we did not begin to mark is now considered Unknown. Is this a problem? To answer this question, you need to understand what Idea inspections for Nullable and NotNull can find:
  • dereference of a potentially null variable when accessing an object field or method;
  • passing in Nullable the argument of the Nullable variable;
  • excessive check for the absence of a value for the NotNull variable;
  • inconsistency of the parameters of the binding when assigning a value;
  • returning NotNull using the Nullable variable in one of the branches.

It is logical that any value returned from a library method that is not marked up with these annotations is Unknown. To combat this, simply mark with annotation a local variable or field that assigns.

If we continue to adhere to our practice, then in our code everything will be marked as Nullable optional. Thus, the first test continues to work, protecting us from many NPEs. Unfortunately, all other checks fell off. The second check, which is extremely useful against comrades who are very fond of writing methods that actively accept null as arguments, and passing null to other people's methods that are not designed for this, also does not work.

There are two ways to restore the behavior of the second check:
  • in the settings of the “Constant conditions & exceptions” inspection, activate the option “Suggest @Nullable annotation for methods that may possibly return null and report nullable values ​​passed to non-annotated parameters". This will cause all unannotated method arguments throughout the project to be considered NotNull. For a project that is just starting, this solution is perfect, but for obvious reasons, it is not appropriate when introducing practice into a project with a significant existing code base;
  • use annotation @ParametersAreNonnullByDefaultto set the appropriate behavior in a specific scope, which may be a method, class, package. This solution is already great for a legacy project. A fly in the ointment is that when setting behavior for a package, recursion is not supported and this annotation should not be hung on the entire module at a time.

In both cases, only unannotated method arguments become NotNull by default. Fields, local variables and return values ​​are not affected.

Near future


The upcoming support @TypeQualifierDefault, which is already working in Intellij Idea 14 EAP, is called to improve the situation . Using them, you can define your annotation@NonNullByDefault , which will determine the default binding for everything, supporting the same scopes. There is no recursion now, but debate is ongoing .

The following demonstrates how inspections look for three cases of working from legacy code with code in a new style with annotations.

We annotate explicitly:



By default, only arguments:



By default, everything:



the end


Now everything has become almost wonderful, it remains to wait for the release of Intellij Idea 14. The only thing that is still missing until complete happiness is the ability to annotate a type in Generic before Java 8 and its support for Type annotations. What is very lacking for ListenableFutures and collections in some cases.

Since the volume of the article turned out to be quite significant, most of the examples remained overboard, but is available here .

upd. It is still possible to add meta-information about optional values ​​for external libraries .

Used sources


  1. stackoverflow.com/questions/16938241/is-there-a-nonnullbydefault-annotation-in-idea
  2. www.jetbrains.com/idea/webhelp/annotating-source-code.html
  3. youtrack.jetbrains.com/issue/IDEA-65566
  4. youtrack.jetbrains.com/issue/IDEA-125281

Also popular now: