How to write bug free code - State
A lot of bugs are in some way related to state. So that is what we will be talking about today. We will start off with a quote from Einstein: “Insanity: doing the same thing over and over again and expecting different results. ” Code should be consistent, calling the same function with the same input should return the same result during the whole life cycle of the object. Insanity and bugs will follow if this rule is violated. This sounds logical, but in practise it is quite easy to violate this principle. The main cause of this, is object state. This state is used in a lot of functions, and can thus affect the behaviour of our program at runtime. This principle can be even be seen in the most basic of examples (imagine the impact on a more complicated class...). Given the following class:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Lets see how this state affects the behaviour of our object:
public static void main(String[] args) {
Person person = new Person();
person.setName("Bob");
System.out.println(person.getName()); //Calling getName() will now result in "Bob"
person.setName("John");
System.out.println(person.getName()); //Suddenly, calling the same method getName() for a second time, now returns "John"
person.setName(null);
System.out.println(person.getName()); //Even worse, suddenly we get NULL from the same function call!
}
All it takes is a simple setter, and calling the same function over and over again with the same parameter (void in this case) does not have to result in getting the same output. This means the object is not safe, and the state should be checked every time we call getName()
, since we have no way of knowing if the function might return null
. (bonus points if you notice that you also get null if you do not call the setter at all!) As developers we usually work on projects in teams, and even if you always do a null
check on getName()
, one of your colleagues might forget at some point, and that code might result in a null pointer at some point in time. There are two ways of avoiding the consistency problem. The first is creating constant state (immutability), or passing the state as an argument, and thus make the method stateless. Lets see what an immutable version of Person
would look like:
public class Person {
private final String name;
private Person(final String name) {
if(name == null) {
throw new IllegalStateException("name should not be null");
}
this.name = name;
}
public String getName() {
return name;
}
}
There are a couple of things that have changed. First of all, we now use the final
keyword for our variable name. This makes sure the variable can be assigned only once (in the constructor). In the constructor we also throw an exception if the parameter is null
. Thus, the object cannot be instantiated in an illegal state, and since we lack a setter, the state cannot be altered during the lifecycle of the object. (for the sake of argument, we will ignore reflection for this discussion) This means we now have a safe object. Every time we call getName()
, the same result will be returned, and Einstein is again a happy man. Also, we can now remove all null checks on getName()
, since it is now impossible for it to be null. The downside of this, is that in order to change the name, you need to create a new object with a different name, but the consistency of immutability is well worth the trade-off. A second downside is that since you do not have any setters, arguments can only be passed as constructor arguments, and this can lead to very ugly constructors. If this is the case, I suggest taking a look at the builder pattern, which deals with this problem quite well. For the second solution Java can help us as well, with the static
keyword. Let's take the following example:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String sayHello1(){
return "Hello! my name is " + name + "!";
}
public static String sayHello2(String name){
return "Hello! my name is " + name + "!";
}
}
As you can see, sayHello1()
uses the state of Person
, and as we just learned, this would be safe if name
was immutable, but we can avoid the issue entirely if we instead pass the variable as an argument. No matter the state of Person
, sayHello2()
will always return the same value when called with the same argument, and thus Einstein will again, be a happy man. So, does this mean you should make all your classes immutable? No, sometimes things are just optional, or you have no way of knowing the value for certain properties at object creation. However, it can still be a good practice to transform the mutable object to an immutable object once this is possible, MutablePerson -> Person
and only use the immutable object in your buisnesslogic My rule of thumb in this case is: you should make everything immutable, and only make them mutable with good argumentation. In conclusion. We learned that state can cause our methods and thus our application to behave differently. This is a mayor cause of bugs (especially null pointers). The solution is to be aware of state, avoid it if possible by using static methods, or if you cannot avoid it, make your object as immutable as possible.