Familiarity - friend or foe (part 2)

The first part of this post was not really meant to be a first part of, possibly, a longer series. Yet, I seem to have found another case where things that we know and ways how we work might be preventing us from finding other solutions, possibly better.

Some time ago I joined a new project. I was supposed to start working on a new application that would, later on, be taken over by a team I was part of. I thought: cool! I’ll be able to do things my way, at least to some degree. The project is written in Java, I needed some value object, so I’ve written something like this:

public class Foo {
  public final Bar bar;
  public final Baz baz;
  
  public Foo(Bar bar, Baz baz) {
    this.bar = bar;
    this.baz = baz;
  }
}

A perfectly legal Java class, all fields final, so the instances are immutable, I can freely pass such object around and not worry about data being changed or race conditions. Also, no getters and setters which usually just create noise. The only way to set the values is to create a new object and pass those values to the constructor. Reading the values meant just accessing the properties, no getters invocation. The class is short and to the point – it’s just a value object, has no logic.

And then came SonarQube and told me: over my dead body, thou shalt declare your fields private and create getters and setters if you need them.

I had to change the class to look like this:

@Value
public class Foo {
  private final Bar bar;
  private final Baz baz;
}

I used Lombok to shorten the code, it doesn’t look that bad right now, and it is immutable once created. Still, the resulting class has getters and setters I wanted to avoid.

Here’s the point I want to highlight. In my case getters and setters add no value, there’s no logic hidden in there. They’re also not providing any encapsulation or hide the internal structure of the class. My first approach looks very clean and just from the first looks tells me “I’m a value object”, at least to me.

The only reason to add getters and setters there is the Java Beans standard. According to the documentation

A Java Bean is a reusable software component that can be manipulated visually in a builder tool.

The documentation then goes on and specifies, for example, that “Properties are always accessed via method calls on their owning object”, so getters and maybe, for writable properties, setters. Also, your class should provide a no-argument constructor so that the tools can instantiate the beans without having to look for and guess the parameters required by constructors that do take parameters.

I believe the time of Java Beans is gone, it provides little to no value today. The last time I was using a RAD, something like Delphi, was maybe 2003, 18 years ago. I just checked, such IDEs still exist, they do provide visual tools, but mostly for UI components, something that you can actually see. They do not claim to enable you to visually, drag&drop-style put together any kind of application more complex than displaying a “Hello World!”. Yet, here we are, in a world, where a significant percentage of all methods existing in a Java project are getters and setters. We write them almost with our eyes closed, they’re so obvious that they become almost invisible.

Of course, Java Beans-style is not completely bad and useless, there are benefits of using it, sometimes significant ones. One is that the tools we’re using often rely on our classes having getters and setters. If we deviate from it, they might have trouble doing their job. Take Hibernate for instance. Once we create a class without a no-args constructor, Hibernate will complain. They can get around having no getters and setters using reflection, but they do recommend having them. The same goes with other, popular tools like Jackson. They do not require getters or setters, but cannot work without a no-argument constructor. Sometimes they provide a lower-level API where you could write your own code creating objects using whatever constructors you have available, but this also means more work from your side. This is an important aspect pointed to me by a Java Champion, he knows the Java environment and tools far better than me and I have to take his advice into consideration.

The second aspect is non-technical and touches “familiarity”. Once I “break” the convention and start creating classes differently, I might surprise other developers. You might have already heard about a “principle of least astonishment. Indeed, how often do you want to wonder why a specific thing is done differently than usual? Is this solution the right one? Has someone chosen it purposely for some property particularly beneficial in a given context? What are those property and context?

I just made a circle, I ended up exactly where I’ve started. The reason for me to break the convention is to get some benefits in a current context, but it will cause some confusion in others. I can explain why I’m doing things differently, but I’d need to be there to answers all the questions and explain the context every time someone sees my “weird” code for the first time. Also, sometimes those gains do not justify the confusion. Every single time it’s a tough choice and a trade-off.

Of course, getters and setters are just the tip of an iceberg. They might also not be the perfect example of a “standard” to break. I have much more “crazy” ideas in my head and would like to break much more rules and also much more fundamental ones. Still, every now and then I find pieces of code I do not like, although they look “normal” in Java. Every such time I’m asking myself – does it have to be this way? Where is it coming from? What would happen if I break the convention and do something different? Are the old ways still the best ones, or should we move forward?

Those questions are open, I do not have any general answers here. Most likely those questions can be answered only in a given context. Definitely an interesting topic, and I will definitely be revisiting it from time to time.

Note, this post was also published on Tech Coffee Time.