Favour empirical evidence over extended arguments

Often, we get into extended arguments when we have to pick an optimal solution from multiple available options. Especially in the case of science and technology, gathering empirical evidence is a highly effective solution. In this post, I share my experience of a code review.

This was the first piece of code I had written as part of my first project in my first job. One of the senior developers reviewed my code. He commented that this.<field> would perform poorly compared to <field>. Hence, he wanted me to avoid using this in my code.

My first reaction was - "the developers of Java language are much smarter to leave such inefficiencies in the system". Also, it is very close to saying - "shorter variables make the program run faster than having long variable names!"

To understand this better, let us take a piece of code.

public class Person{
    private String name;

    public Person(String name){
        this.name = name; 
    }
}

Since the parameter name and field name are the same, I had to use this.name for the code to be correct. I can name the constructor argument differently to avoid using' this'. The alternate code would look as follows.

public class Person{
    private String name;

    public Person(String nameOfThePerson){
        name = nameOfThePerson;
    }
}

One can debate whether using this leads to more readable code in the given context. I will discuss it another time.

Since the question we are trying to answer is which version of the code is faster, the first thought would be to run a micro-benchmark and determine the time required to execute the code. Since the code difference is very small here, it is hard to notice any performance differences through a micro-benchmark. Instead, a great option would be to compare the generated byte code to find out any differences, if at all, exist.

Here is the bytecode generated from the first version (using this).

➜  temp javac Person.java
➜  temp javap -c Person.class
Compiled from "Person.java"
public class Person {
  public Person(java.lang.String);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: putfield      #7                  // Field name:Ljava/lang/String;
       9: return
}

Here is the bytecode generated from the second option (without using this).

➜  temp javac Person.java
➜  temp javap -c Person.class
Compiled from "Person.java"
public class Person {
  public Person(java.lang.String);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: aload_1
       6: putfield      #7                  // Field name:Ljava/lang/String;
       9: return
}

As you would have noticed, there is no difference in the bytecodes. Hence, there are no performance differences. The use of this is only for the Java compiler to differentiate between the method parameter and the instance variable.

In both cases, setting the field name happens in the instruction starting at 6. The instruction putfield will store the value popped from the stack into the instance variable name, whose reference is already stored in the constant pool.

Takeaways:

  • Gathering empirical evidence over long debates would be advantageous for better outcomes.
  • There could be multiple ways of gathering evidence. It would be worthwhile to give it some thought as some approaches could be simpler and more effective than others.
  • Knowing one layer below the layer you work with improves your troubleshooting ability and design quality.