Unnecessary IF statements
Making decisions is important. We cannot run from them. What we can is to don't make decisions that have already been made by us or others.
In this very short article I'd like to share with you three ways, how can we avoid writing unnecessary IF statements. Those hints are so simple that it's even hard to believe how helpful they may be.
But why actually is it a good idea to avoid IF statements? Like many other things, they introduce some complexity and increase the risk of making a mistake. Even though it's very easy to write and understand them, when done hundreds of times, may be done incorrectly. Believe me or not, I've seen a security issue caused by an unnecessary IF statement that could be totally removed without any further changes in the code!
(boolean? true : false) == boolean
Let's assume we need a method that tells us whether something weights more than the given value. The craziest example I can think of looks like that:
public boolean isHeavierThan(int kilos)
{
boolean result = false;
if (weightInKilos > kilos) {
result = true;
}
if (weightInKilos <= kilos) {
result = false;
}
return result;
}
Of course most of the time in a real code it doesn't look that bad. What may be seen definitely more often is:
public boolean isHeavierThan(int kilos)
{
if (weightInKilos > kilos) {
return true;
} else {
return false;
}
}
or it's shorter version:
public boolean isHeavierThan(int kilos)
{
return (weightInKilos > kilos) ? true : false;
}
Now this IF is much shorter, but still present. It means that it may still be written incorrectly, like that:
public boolean isHeavierThan(int kilos)
{
return (weightInKilos > kilos) ? false : false;
}
Fortunately the way to get rid of it completely is dead simple, as all what we do here is returning the exact result of a comparison:
public boolean isHeavierThan(int kilos)
{
return weightInKilos > kilos;
}
It still does exactly what the first version does, but without a variable with 3 assignments and 2 conditional statements.
Null is not an empty collection
A name displaying screen sounds like a great thing!
Collection<String> names = new ArrayList<>();
names.add("Katie");
names.add("John");
screen.display(names);
Perfect! We've just displayed two names. But how do we tell the screen that we don't want to display anything without modifying the code that actually makes the call to the "display" method? Can it be a null value?
Collection<String> names = null;
screen.display(names);
It may work, but what's the cost? Now in the "display" method, right before actually printing our names, we must check if the given parameter is not a null value.
public void display(Collection<String> names)
{
if (null != names) {
for (String singleName: names) {
System.out.println(singleName);
}
}
}
If we try to remove this null checking...
public void display(Collection<String> names)
{
for (String singleName: names) {
System.out.println(singleName);
}
}
... it looks much nicer, but then: NullPointerException! Isn't there a way to escape that hell? Of course there is and it's actually quite simple. Before we write the final solution, let's think about something. If we want to display 3 names, we pass a collection of 3 names as the parameter. If we want to display 2 names, we pass a collection of 2 names. How does that differ, then, from displaying 1 or 0 names? Well, it doesn't.
Collection<String> names = Collections.emptyList();
screen.display(names);
Passing an empty list when we expect no names to be printed saves the screen from checking if the given collection is actually a collection and not a null.
Unfortunately Java doesn't stop us from passing there a null, but the good thing is that there're great tools like the Checker Framework which are extremely helpful in eliminating null pointers, as they make them appear during the compile time.
Let's meet Mr. Unknown - Null Object
In this example we're going to ask people their name. Our model of a person is very simple:
public interface Person
{
public String getName();
}
We can also look for somebody using the repository of people:
public interface PersonRepository
{
public Person getBy(int id);
}
For presentation purposes, the fake repository may be written this way:
public class FakePersonRepo implements PersonRepository
{
private Map<Integer, Person> people = new HashMap<>();
public FakePersonRepo()
{
people.put(2, new PersonImpl("Katie"));
people.put(7, new PersonImpl("John"));
}
@Override
public Person getBy(int id)
{
return people.containsKey(id)
? people.get(id)
: null;
}
}
It holds two people with id 2 and 7 and returns a null value if nobody is found. Now if we want to use this repository to display somebody's name or some default text in case a person like that doesn't exist, we can write it this way:
Person foundPerson = people.getBy(3);
String foundPersonName = (null != foundPerson)
? foundPerson.getName()
: "Mr. Unknown";
Depending on our application, it may soon turn out that this fragment of code is repeated over and over again. It's a good moment to consider introducing the Special Case/Null Object design pattern. One thing we cannot forget about is that the previous two examples were about removing IF statements that were unnecessary at all, while this one is about moving it to a different place and avoiding repetition.
If every single time, when nobody is found, "Mr. Unknown" becomes the name that is actually used later in our app, it looks like there's a hidden object representing nobody.
public class NullPerson implements Person
{
@Override
public String getName()
{
return "Mr. Unknown";
}
}
What in the case of our very simple model doesn't even need to be a separated class, as we can use an instance built this way:
new PersonImpl("Mr. Unknown");
Then our repository, when nobody is found, returns this special object instead of a null value.
@Override
public Person getBy(int id)
{
return people.containsKey(id)
? people.get(id)
: new PersonImpl("Mr. Unknown");
}
Now when writing some code that uses the repository of people we can write the following fragment and be sure that it's going to work regardless somebody is actually found:
String foundPersonName = people.getBy(3).getName();
By doing that, we moved some logic from the client code into the repository and made the client code unaware of somebody's presence. Sometimes it may be a great idea, but it's definitely nothing that may be applied everywhere without considering pros and cons of using it in some particular application. If there are 20 places in the code where a person is read from the repository and then the same action is performed if nobody is found, it's a job for the Special Case pattern. On the other hand, if sometimes "Mr. Unknown" is displayed, sometimes no table row is rendered and sometimes an error message pops up, this pattern is useless, because the client code needs to know whether somebody is found. However, if there are tens of cases it matches and just one when we really need to know whether somebody is found, an interesting solution is to make the special case object a Singleton and then check if the returned person is exactly that instance. Even though it's probably the only use case of the Singleton pattern that doesn't seem to be a terrible crime, I suggest being very careful with that, especially if the special case object is not very simple or if there's a chance we will need more than one null object.
I hope you liked this article. It makes me very happy if you find some of those hints useful! It's almost a direct transcription of my youtube video "One less IF" so if you're interested in a video version, feel free to check it out!