To Hell With Setters and Getters

How Accessor Methods Are a Waste of Time and Often Hinder Encapsulation

Recently I had the audacity to ask colleagues, why exactly do we bother with setters and getters for our C++ classes? Fortunately I was not put up against the wall and shot for heresy. But nor could anyone actually give a good reason for why we use setters and getters in our code.

I could of course have asked this question about Java classes, Python classes or almost any other object oriented language. Within software development there are certain beliefs which assume religious conviction.

People will criticize you for not using setters and getters with the argument that, now your poor class has no encapsulation. You made it naked and vulnerable. The argument goes something like this:

If you don’t have setters and getters for the properties of your object, the you can’t change the internal representation without breaking code.

When I point out that today we got refactoring tools which let you replace all direct variable access with a setter and getter, then what is the point of prematurely adding setters and getters? Actually even before refactoring tools, this was not a problem, as you could simply make a variable private, and the compiler will tell you immediately which places you need to change. If you use a statically typed language such as C++ or Java that is.

Why Does It Matter?

Of course you might ask why should I care about writing accessors. After all we often have IDE’s which will do this for us. However consider a simple type. We could write it like this and be done with it.

struct Rocket {
int stages;
float fuel;
RocketEngine engine;
bool reusable;
};

Instead to be good OOP citizens we force ourselves to write stuff like this:

class Rocket {
public:
Rocket();
virtual ~Rocket();

int GetStages() const;
void SetStages(int stages);
float GetFuel() const;
void SetFuel(float fuel);
RocketEngine& GetEngine();
const RocketEngine& GetEngine() const;
void SetEngine(const RocketEngine& engine);
bool GetReusable() const;
void SetReusable(bool reusable);

private:
int stages_ = 0;
float fuel_ = 0.0;
RocketEngine engine_;
bool reusable_ = false;
};

Together with a corresponding implementation file.

Rocket::Rocket() {

}

Rocket::~Rocket() {
}

int
Rocket::GetStages() const {
return stages_;
}

void
Rocket::SetStages(int stages) {
stages_ = stages;
}

float
Rocket::GetFuel() const {
return fuel_;
}

void
Rocket::SetFuel(float fuel) {
fuel_ = fuel;
}

RocketEngine&
Rocket::GetEngine() {
return engine_;
}

const RocketEngine&
Rocket::GetEngine() const {
return engine_;
}

void
Rocket::SetEngine(const RocketEngine& engine) {
engine_ = engine;
}

bool
Rocket::GetReusable() const {
return reusable_;
}

void
Rocket::SetReusable(bool reusable) {
reusable_ = reusable;
}

So instead of 6 lines of code we end up with around 60 lines of code. 10x as much code to satisfy the OOP gods. That is not free, even if the IDE writes it all for you. It is 10x as much code you have to relate to, possibly read, reason about and maintain.

This is madness and fortunately the software development community has started to slowly realize this. Look at source code in Google’s Go programming language. They seldom use accessors. If you define a simple point type like this:

struct Point {
float x;
float y;
};

There is no need for accessors. You are not suddenly going to need to change this into being defined as polar coordinates instead. Although that is the sort of excuses people invent. Anyway a refactoring tool can add those accessors should you need them in the future. Never in my career have I changed a basic type like this after tons of code has been written.

Other languages such as Ruby, C# and Swift have done away with the need by simply making the syntax for accessing a property look the same as accessing a variable directly. Then you can add an accessor later without having to do any actual code change.

Accessors Are Not Encapsulation

At the heart of the problem with accessors is that a majority of OOP developers seem to think that encapsulation is simply about making sure internal data is not accessed directly.

However that is mixing up a language feature aimed at building encapsulation with the concept of encapsulation. A common way to illustrate this is with e.g. a stack.

struct Stack {
void push(int value) {
values[++top] = value;
}

int pop() {
return values[top--];
}
private:
int top = -1;
int values[CAPACITY];
};

Notice how there are no setters and getters for the variables top and values. If we had added getTop() and setTop(), that would not have promoted encapsulation. Quite the contrary we would have exposed a detail about how our stack type is implemented. We would have allowed users of our stack to change the top of the stack to a value which does not match the number of push() and pop() being called. Hence we would have broken invariants.

Had we added setValues() and getValues() accessors we would not have maintained encapsulation but exposed how values are stored in the stack.

The bottom line is that encapsulation is not about accessing internal variables through method calls, but hiding the internal structure and creating an interface which maintains the correct relationship between the internal variables.

Should You Ever Write Accessors?

By now, you might think that I am completely against ever using accessors. But that is not the point I want to make. My point is that OOP design should not be taught as commandments which must be observed to satisfy the OOP gods.

No rule should be set in stone. You should be able to make the case for the choices you make. I will here make the case for when I would use accessors.

Libraries Used by Third Parties

If you’re code is a library used by others, then you are more likely to need encapsulation because you can’t do a simple refactoring of customer code. However in this case, simple encapsulation through accessors might not be enough, since you often want to maintain binary backwards compatibility.

In C++, if you change the internal state of your objects by adding or removing variables, then you risk changing the size of you’re objects. Say my Point class contains two integers for x and y coordinates. Imagine a customer has an array of these point objects, and I update my library by adding a z coordinate. If my library is dynamically linked at runtime and the customer has not recompiled his code, then x coordinate of the second point will be a the position of the z coordinate of the first point.

Seen from that perspective, OOP encapsulation in C++ is rather weak. In e.g. Objective-C this is not a problem, as all objects are heap allocated, and you communicate with them through message passing. I suspect Java would not have this problem either, since anything which is not a primitive type is accessed through references (essentially pointers).

You Want to Limit Access

To start off with setter and getters which do nothing, is rather pointless. However it is quite sensible to make a variable private and only provide a getter. E.g. a collection usually has a size property. You don’t want the user of your collection to be able to arbitrarily change the size property unless the number of elements have actually changed. Hence you want in private and provide a getter method.

One frequently wants to make immutable objects. It can be achieved by letting the user set the values of all its properties through the constructor. Later those properties can only be accessed by setters (no getters).

Your Type is Part of an Abstraction

In my programming language of choice, Julia, you tend to specify functions as taking abstract strings as arguments, because Julia supports many types of strings. Then you can’t access say a length variable directly, because the length of the string could potentially be calculated as needed, as with C style string, or it could be a stored variable as with Pascal. The algorithms the user writes should not have to care about this. Hence you need to provide a length() getter to abstract away this difference.

The same goes for collections. Their implementation could vary considerably but you still want to provide a standardized interface to determine the number of elements in the collection, iterating over them etc.

Conclusion

If you find yourself wondering whether you should write setters or getters, you got to ask yourself some questions about the code your are writing and the type (class) you are implementing. Is this for internal use only, or will customers see this type?

Is it a read-only property? If you don’t decide this up front there is no point in writing accessors as it won’t help you later.

Is my type a specialization of an abstract concept? E.g a file on Unix is quite abstract as it could represent files on a hardisk, a device, a process, in memory file etc.

Only when you have a sensible reason, should you actually write the setters and getters.