-
Notifications
You must be signed in to change notification settings - Fork 5
First run
Maven likes to talk, so expect a fair bit of output produced by mvn -Pchecker compile. Here comes the first batch of diagnostics.
Scroll upwards over the verbose output for a bit, and there is this:
...
[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java:
[30,8] [initialization.fields.uninitialized] the constructor does not initialize fields: name
[ERROR] src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java:
[30,8] [initialization.fields.uninitialized] the constructor does not initialize fields: id
[ERROR] src/main/java/org/springframework/samples/petclinic/model/Pet.java:
[49,8] [initialization.fields.uninitialized] the constructor does not initialize fields: birthDate, type, owner, visits
[ERROR] src/main/java/org/springframework/samples/petclinic/model/Owner.java:
[46,8] [initialization.fields.uninitialized] the constructor does not initialize fields: address, city, telephone, pets
[ERROR] src/main/java/org/springframework/samples/petclinic/model/Owner.java:
[137,16] [return.type.incompatible] incompatible types in return.
found : null
required: @Initialized @NonNull Pet
...
...
[INFO] 18 errors
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
The checker found 18 errors.
The ones at the top seem quite similar. ‘Named entity’, ‘pet’, ‘owner’ … these are the entities from the domain model of the Pet Clinic.
Open the class in the first error message,
NamedEntity.
The message says that the constructor doesn’t initialise the field name. True,
but what’s wrong with that?
@MappedSuperclass
public class NamedEntity extends BaseEntity {
@Column(name = "name")
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}Recall that the Checker Framework assumes that unannotated reference types are
non-null by default. So, the ‘name’ field is really of type @NonNull String
– but a field can only be @NonNull if it is initialised to some non-null
value, and that doesn’t happen here. A new NamedEntity() will always have its
name set to null. There’s no denying that name is @Nullable, and that is
what we must declare:
@Column(name = "name")
@Nullable
private String name;Mutable fields like this one usually come with getters and setters. If the field may be null, the getter may return null, and the setter may accept null (run the checker again to see if this reasoning is right). Thus:
@Nullable public String getName() {
return name;
}
public void setName(@Nullable String name) {
this.name = name;
}Notice in these examples how I’m putting the annotations on the field and on the method. This is equivalent to putting the annotation on the type directly, as in the following snippet. Since I chose to go with the JSR 305 annotations though, type use annotation is not an option.
private @Nullable String name;
public @Nullable String getName() { /* ... */ }Annotating type use is a new capability of the Java language that arrived with release 1.8, and is definitely the right way of using nullability annotations from here on out.
By the way, for very short one-line methods like these I like putting the method annotations inline, but that’s just a matter of taste.
Even for a small project like this one running the checker takes time. When there are many similar errors, addressing them in a batch speeds things up.
Try annotating all the entities in
org.springframework.samples.petclinic.model
in the same way. This amounts to annotating
- fields
- getters
- setters
as @Nullable.
I have committed my changes in commit
33aaa27.
This commit is quite big. The annotation work we had to do on the entities was
considerable: about 45 @Nullable annotations. The good news is that this was
already the bulk of @Nullable annotations in this tutorial. Still, let’s take
a moment to understand what required so much work.
The annotations we introduced all had to be placed on the attributes of database entities. The entities of the Pet Clinic follow the conventions of the Java Persistence API (JPA). JPA is a venerable old spec heavily dependent on mutable objects whose fields are null by default.
Pet pet = new Pet();
pet.setName("Fip");
pet.setOwner(owner);From a modern programming perspective this is unfortunate. Nowadays we favour
immutable value classes and non-null defaults. A modern design for a
Pet entity might include a builder or static factory to ensure that fields
required to be non-null by the business domain are enforced to be non-null on
the language level.
Pet pet = Pet.builder()
.withName("Fip")
.withOwner(owner)
.build();The lesson here is that whenever a programming model runs squarely in the face of the null-safe programming practices we talked about earlier, introducing nullness annotations can be an awful lot of work.
Let’s move on. Run the checker again on the improved code – mvn -Pchecker compile –, we’re about to uncover a first bug.