Getters/Setters. Evil. Period.

The people who say this are right about the conclusion, in spite of being wrong about the reasons (in my not-so-humble opinion). All this OOP purity stuff that the article goes on and on about is BS.

But enough of that. Let me make an observation and a practical recommendation.

The observation is that lots of Java programmers just put getters and setters into their classes by default. They don't stop and ask whether the particular class should have them—they just boilerplate them in. Somebody has told them that this is "good practice," and they've just adopted it. The IDEs abet them in this—they all provide some facility to generate this stuff, even putting "helpful" documentation comments on the code:

private Foo foo;

/**
 * Gets the {@link Foo}.
 *
 * @returns the {@link Foo}.
 */
public Foo getFoo() {
    return foo;
}

/**
 * Sets the {@link Foo}.
 *
 * @param foo the {@link Foo}.
 */
public void setFoo(Foo foo) {
    this.foo = foo;
}

Recommendation: if that description applies to you, give the following a shot instead:

  1. Make class fields private final by default. Don't make a field non-final unless the field's value will concretely need to be modified multiple times during the object's lifetime. If it would be set only once and never used again, really try your best to make it final.
  2. Remember that the way final instance fields work in Java is that they must be initialized in the class's constructors—the class won't compile otherwise. So go ahead and do that.
  3. Do not add any getters to a class unless you have a concrete need for them.
  4. Since final fields can't be modified once the object is constructed, it doesn't make sense to have setters for these fields.
  5. If you truly do need non-final fields, see if you can isolate them to short-lived, "throwaway" objects that are constructed by more permanent, immutable ones that contain all the other necessary values in final fields.

This has lots, lots of upsides. I will name two:

  • It avoids the all-too-common problem of incompletely initialized objects, where a class allows you to instantiate an invalid object that needs further initialization through setters before it can be used. Instead, callers can only succeed in creating an object if the object would be usable.
  • The more of the object's fields are final, the more that the object's future behaviors are determined when it's constructed. This means that the object's behavior remains much more predictable even in a very complex codebase. If you construct an object, pass it to some other code, and then use the object after that, the number of ways the stuff "in between" can modify the object are limited, so you can much more successfully predict how the object will behave on the later call.

This sort of problem bit me just last week when I was modifying a third-party open source library—I change a class's constructor to modify the way that a certain field was initialized, and then when I test the class it behaves the same way as before. Turns out that a couple of methods in one of the classes that consumed the object was calling the field's setter and putting it in the state that the original constructor did, because there was a code path where the field in question was not initialized.

The downsides are:

  • Java has no keyword or optional arguments, so when your constructors or static factory methods get complex they become unfriendly to use. You end up using the Builder Pattern, which is easy to read but requires a lot of boilerplate. Ideally you should let your IDE take care of generating these.
  • There are lots of old Java frameworks that demand that you provide setters or make your life difficult, and you can't always avoid them.
/r/programming Thread Link - yegor256.com