You can check the Null-object pattern.
The general pattern is to ban null completely from your code. This eliminates the ugly null checks. In this point I agree with your code reviewer.
Following the below recommendations will result in:
public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
return someRepository.containsAAndB(someObject.getFieldA(), B);
}
Avoid null checks
Before introducing the Null-object pattern, simply apply the pattern or convention to enforce initialization of all references. This way you can be sure that there are no null references in your entire code.
So when you encounter a NullPointerException, you don't solve the issue by introducing a null check, but by initializing the reference (on construction) e.g., by using default values, empty collections or null objects.
Most modern languages support code analysis via annotations like @NonNull that checks references like arguments and will throw an exception, when a parameter is null/not initialized. javax.annotation for instance provides such annotations.
public void operation(@NonNull Object param) {
param.toString(); // Guaranteed to be not null
}
Using such annotations can guard library code against null arguments.
Null-Object Pattern
Instead of having null references, you initialize each reference with a meaningful value or a dedicated null-object:
Define the Null-object contract (not required):
interface NullObject {
public boolean getIsNull();
}
Define a base type:
abstract class Account {
private double value;
private List<Owner> owners;
// Getters/setters
}
Define the Null-object:
class NullAccount extends Account implements NullObject {
// Initialize ALL attributes with meaningful and *neutral* values
public NullAccount() {
setValue(0); //
setOwners(new ArrayList<Owner>())
@Override
public boolean getIsNull() {
return true;
}
}
Define the default implementation:
class AccountImpl extends Account implements NullObject {
@Override
public boolean getIsNull() {
return true;
}
}
Initialize all Account references using the NullAccount class:
class Employee {
private Account Account;
public Employee() {
setAccount(new NullAccount());
}
}
Or use the NullAccount to return a failed state instance (or default) instead of returning null:
public Account findAccountOf(Owner owner) {
if (notFound) {
return new NullAccount();
}
}
public void testNullAccount() {
Account result = findAccountOf(null); // Returns a NullAccount
// The Null-object is neutral. We can use it without null checking.
// result.getOwners() always returns
// an empty collection (NullAccount) => no iteration => neutral behavior
for (Owner owner : result.getOwners()) {
double total += result.getvalue(); // No side effect.
}
}
Try-Do Pattern
Another pattern you can use is the Try-Do pattern. Instead of testing the result of an operation you simply test the operation itself. The operation is responsible to return whether the operation was successful or not.
When searching a text for a string, it might be more convenient to return a boolean whether the result was found instead of returning an empty string or even worse null:
public boolean tryFindInText(String source, String searchKey, SearchResult result) {
int matchIndex = source.indexOf(searchKey);
result.setMatchIndex(matchIndex);
return matchIndex > 0;
}
public void useTryDo() {
SearchResult result = new Searchresult();
if (tryFindInText("Example text", "ample", result) {
int index = result.getMatchIndex();
}
}
In your special case, you can replace the findByAAndB() with an containsAAndB() : boolean implementation.
Combining the patterns
The final solution implements the Null-Object pattern and refactors the find method. The result of the original findByAAndB() was discarded before, since you wanted to test the existence of A and B. A alternative method public boolean contains() will improve your code.
The refactored implementation looks as followed:
abstract class FieldA {
}
class NullFieldA {
}
class FieldAImpl {
}
class SomeClass {
public SomeClass() {
setFieldA(new NullFieldA());
}
}
The improved validation:
public boolean isValid(SomeClass someObject, ConstraintValidationContext context) {
return someRepository.containsAAndB(someObject.getFieldA(), B);
}