📜 ⬆️ ⬇️

Win NPE hell in Java 6 and 7 using Intellij Idea

Disclaimer




JSR-305 to the rescue


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


Naturally, both annotations are applicable to the fields of objects and classes, arguments and return values ​​of methods, local variables. Thus, these annotations supplement the type information regarding the necessity of the 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 @Nullable is mandatory. Those who used Guava, Guice are well acquainted with this practice.
')
Here is an example of a 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 is clear whether it is possible to get null with reference dereference.

The only caveat is that there are situations where, in the current context (eg, at a certain stage of the business process), we know for sure that something in general need not be present. In our case, this is the full name of Basil, which may in principle be absent by 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> T notNull(@Nullable T obj) { if(obj == null) { throw new IllegalStateException(); } return obj; } } 

Real java asserts can also be used, but I haven’t got accustomed because of the need to explicitly include in the runtime and less convenient syntax.

A few words about inheritance and covariance / contravariance:


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

Intellij Idea makes life better


I’ll say right away that by default Idea offers its annotations with similar semantics, although it understands all the others. This can be changed in Settings -> Inspections -> Probable bugs -> {Constant conditions & exceptions; @NotNull/@Nullable problems}. In both inspections, you must select the pair of annotations used.

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


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

It would seem that everything is fine, but the Idea integrated static analyzer does not understand the default agreement we have adopted. From her point of view (as well as any other stat. Analyzer) three options appear here:

And all that we did not 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:

Logically, any value returned from a library method that is not marked up by these annotations is Unknown. To combat this, it is enough to annotate a local variable or field that is used for assignment.

If we continue to adhere to our practice, then our code will remain marked as Nullable all optional. Thus, the first check continues to work, protecting us from many NPEs. Unfortunately, all other checks have fallen off. The second test, which is extremely useful against comrades who are very fond of how to write methods that actively accept null as arguments, and pass null to other methods that are not designed for this, does not work either.

You can restore the behavior of the second check in two ways:

In both cases, the default NotNull are only non-annotated method arguments. Fields, local variables and return values ​​are not affected.

Near future


The upcoming support for @TypeQualifierDefault , which already works in Intellij Idea 14 EAP, is intended to improve the situation. Using them, you can define your @NonNullByDefault annotation , which will determine the default binding for everything, maintaining the same scopes. There is no recursiveness now either, but the debate is on .

Below is shown how the inspections look for three cases of working from legacy code with a code in a new style with annotations.

We annotate explicitly:



By default only arguments:



By default, all:



the end


Now everything has become almost wonderful, it remains to wait for Intellij Idea 14 to be released. The only thing that is still missing is complete ability to annotate a type in Generic before Java 8 and its support for Type annotations. That is not enough for ListenableFutures and collections in some cases.

Since the volume of the article turned out to be quite significant, most of the examples were left out but available here .

upd. It’s 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

Source: https://habr.com/ru/post/237843/


All Articles