Getter-Setter Pattern Considered Harmful
wolfgang-ziegler.com
wolfgang-ziegler.com
In OO othodoxy, you aren't supposed to make state public, because it violates encapsulation. If you want to modify your Point object to use polar coordinates instead of cartesian, then if you expose your cartesian state (x and y), then all the code that relies on x and y is now broken.
So what getX() and setX() do is allow your x and y fields to be encapsulated. Problem solved! Except that once you change to polar coordinates you have your choice of problems:
1) Find all the getX() and getY() uses, and rewrite the affected code to use getR() and getTheta(), since you are now using polar coordinates. In other words, you have violated encapsulation by having getX() and getY() methods, and your get/set methodology hasn't achieved anything.
2) Re-implement getX() and getY() in terms of polar coordinates. That's doable. But do you also now have getR() and getTheta()? Why?
Having getFoo() and setFoo() methods for each field foo is this dumb idea that began, I think, with JavaBeans. It has nothing to do with OO as I understand it. The idea behind OO encapsulation is to separate your object's behavior (the methods) from private state. You can change state at will as long as you maintain behavior. Mechanically adding behavior (get/set methods) to track your private state has always been a stupid idea that has nothing to do with OO except to violate encapsulation.
What should be private state is a matter of debate.
If you have a point, it's not too ridiculous to ask "where is it?" Cartesian coordinates may or may not be how the Point object represents where it is, and that internal detail should be hidden from callers.
> But do you also now have getR() and getTheta()? Why?
It seems like a Point class should hold its internally state in the "best" implementation for how it is usually used, and provide its state out to callers in all of the convenient forms that would be used more than occasionally.
Of course, one should avoid purely exposing internal details. And sometimes you might encapsulate more to encourage callers to use the pointA.getDistanceAndBearing(pointB) call. (But I don't think this makes sense for points; knowing their location is too fundamental).
The point of getters and setters is they're the bare minimum of encapsulation. The advice is to ensure even reading a field should be abstracted so implementation details do not cause large reactors of downstream code.
How this is conflated with 'every field should have getters and setters' like it is in this discussion I do not know but that is not the intention.
Unfortunately, it's often taught with toy noun examples that have obvious/actual internal fields and data structures.
It'd be better taught solely using abstract nouns, to better nudge learners into explicitly thinking about external/API design vs internal state.
Or, in other words, getters and setters are subcases where the external/API exposed is 1:1 with internal state.
But it often isn't. The blog post starts out talking about how that field -> getter/setter pattern is very widespread, and tools (JavaBeans tools for example), make that pattern unavoidable sometimes. Yes, it should be an API decision, but for a variety of reasons -- tools, patterns, coding standards, cargo cult programming -- it isn't.
3) implement getX() and getY() alongside getR() and getTheta() and allow user code to decide which to use. Double the functionality without having to expose internal state.
4) implement a PolarPoint class with a way to convert between that and Point. This is more applicable if the concepts are not orthogonal but also not as strongly correlated as coordinate systems.
The two ways of handling you describe are not how I would actually handle any of this in real situations.
Getters and setters meanwhile allow you to do things like recalculate internal state whenever you call setX() or setR() to amortize computational costs or do any other kind of change tracking without having to rely on some more language-specific feature to do so. Plus getters and setters work across network boundaries unlike modifying state directly.
The benefit of having getters is that API consumers don’t privilege public state above any other API component, because they don’t know what’s actually the real state and what’s just under contract.
The immutable version would let you have all four fields. A setter on any of the 4 items (should you desire it) will return a new object whose four fields remain consistent with one another.
The point is that mechanically maintaining get/set methods for each field is completely at odds with encapsulation. To continue the example, sure, go ahead, have any or all of get/setX, get/setY, get/setR, get/setTheta if they make sense for your application. But writing them just because you've created a field, and the coding standards say they should be there is dumb. The coding standards that say this (like JavaBeans) may have their reasons, but OO ain't it.
var y = x with { things I want to change }
https://learn.microsoft.com/en-us/dotnet/csharp/language-ref... y = { ...x, change: 'b' }
And Swift avoids the mutability-problem all together, by providing structs with copy-on-write and self-mutating methods:https://docs.swift.org/swift-book/documentation/the-swift-pr...
The last point, sure you can have a breakpoint on a memory address but if you have a getter/setter method its trivial to put a breakpoint in there, but I bet that if you have to set a memory brekapoint you have to waste time googling how do that (at least with GDB)
Raw data POD type structs are useful but only when the scope is limited.
def Foo:
def __init__(self, foo):
self.foo=foo
Use it in all the ways you like. If then later you discover a need for a getter/setter type setup (because you're now e.g. storing foo in a different format internally) you can switch without any of the code depending on the Foo class needing to change: def Foo:
def __init__(self, foo):
self.foo=foo
@property
def foo(self):
return unmunge(self._foo)
@foo.setter
def foo(self, v):
self._foo = munge(v)
I'll grant that the syntax is slightly wonky, but its really nice to not have to think about getter/setters when initially designing a class. You can add them when and where you need them without breaking other code. def __init__(self, foo):
self.foo=foo
should, in this example, probably be: def __init__(self, foo):
self.foo=munge(foo)There are aspects of a valid point in there, but it's buried and confused.
That builder pattern makes me sick to my stomach.
Also, it starts with claiming state is part of the problem with getters/setters, but does not mention it again. (It seems to be conflating mutability and state, which, while often related in practice, are not necessarily that tightly coupled -- an immutable thing can nevertheless be state, and you can have mutability without introducing state. State is really about things that live beyond the immediate context/scope. So really nothing to do with getter/setter.)
Let me turn it around on you: Give me a simple example in code of something that is mutable and not state.
I'll come back later to check your answer and provide one of my own, if necessary.
int addOne(int value) {
value += 1;
return value;
}
value is clearly mutable, yet not state.State is a value you can refer to later. Here, "value" doesn't outlive the function, so can't be referred to later.
State is a complication because you have to worry about whether it remains up-to-date.
(Mutable state is even more complex because you also have to worry about if/when it may be changed.)
If you are not validating any of the input you might as well just have a public variable.
Maybe you do this by default if you are developing an external API for versioning reasons but that’s such a niche use case.
I find that getters and setters seem like pointless overhead when I have an idea that I want to test empirically as soon as possible, but if I don't, I end up with a lot of regret years later when I have to track down all the references and update them instead of just changing code in the one place.
I'm not a fan of lazy loading, but I know a lot of people are, and using the getter/setter pattern also enables fairly straightforward ways to lazy-load properties from persistent storage (or add that capability down the road if it becomes advantageous).
Accessing a field will not invoke other code.
I was unable to put a synchronized onto the field directly:
error: modifier synchronized not allowed here
public synchronized final Object field;
But when I locked it in code: synchronized (field) {
while(true) {
..
I was still able to access the synchronized field directly without triggering a deadlock - whereas a getter() could be synchronized and trigger a deadlock.In any case, the article already made a strong points for immutability so you don't need to mess around with deadlocking crap.
Although I suppose they could mean the concept and not the keyword. A field might not be thread safe and there's no way for an object to guarantee thread safe access of to a field.
You have to take a lock in order to deadlock in the public version. In the getter version, you can take a lock without realising it and then deadlock.
I mean, Lazy<T> is a great example of use of getters.
> You can add validation for all setters, and abstract it as an interface later if needed.
Sure, but why not just postpone the introduction of setters until you actually need them. 90% of time you don't, in which case we just saved a ton of boilerplate and ceremony. The 10% of time that you do, converting public variables to a setter method is about 2 IDE clicks these days.
The only argument I can think of is if you are exposing a public library API, in which case you need to worry about backwards API compatibility.
Trying to refactor fields that made it into a public API is a nightmare. If you're shipping code people use, you won't get to refactor their projects from your upstream library.
Also, no one is suggesting everything needs a setter. Where is this coming from?
this later literally never comes. if you didn't have an ontology when you wrote the code, it never magically unexpectedly appears.
The author recognizes that mutable state gives you a lot to worry about, which I agree with. But public mutable fields make it even worse. Getters and setters at least minimize the surface area and give you some control over mutability.
There are many reasons why this is not the common practice, people have learned these lessons before.
It seems that there are some people who really just think of everything as a state machine and want state transitions to happen only in very controlled and specific ways. And other people seem to focus more on how to organize frequently evolutions of mutable state in a way that makes it easy to reason about. I’m not sure if there is some sort of commonality to these approaches. Different parts of the industry maybe? Different exposure to Haskell?
The immutability kool-aid was already drunk by the banking industry 5000 years ago. That's why you're appending transactions on a ledger instead of mutating bank balances (behind getters and setters, so it's "encapsulated" /s).
Arguing immutability for the sake of immutability is considered harmful imo. Only make things immutable when they actually are immutable or when concurrency is at play (which it probably isn’t, 99% of all code is single threaded).
In theory it sounds great to say "just prevent construction of bad states," but this is a complex problem in professional software that involves coordination between programmers, designers, and product managers to get the UI features into the right place where preventing the construction of bad states doesn't lead to a frustrating UI experience for users. It's often far simpler, more pragmatic, and less complex in terms of coupling in both software and humans to just accept whatever state the UI gives you and figure things out somewhere else where you aren't bothering your users so much.
This has nothing to do with the frontend/backend split since a server will receive arbitrary requests, not just the ones you hope you programmed the frontend to send.
> In theory it sounds great to say "just prevent construction of bad states," but this is a complex problem in professional software
Defeatism.
If you have a User in your domain, and your domain dictates that Users have a UserId, simply do not allow construction of a User without a UserId. What's a UserId? Is it a String, or is it a UUID? If you think the King James Bible translated into Chinese makes for a good UserId, then a String is a perfectly good representation for that. Otherwise pick something more suitable.
Any problem can be swept away with "you don't know what you're talking about", "you'll come around to my way of thinking when you're older", "X has nothing to do with Y", etc.
So, make things real with an example.
For instance when making a person or user you most likely only have their email when you initially create them and then it’s the users job to complete the rest of the data at a later time. Some might want to fill in their age or some might want to fill in their sex. You may never get this data because it’s optional, but when you set it you’ll want to validate it.
And as always in programming _it depends_. It depends on your workflow, requirements, and domain. Being dogmatic about anything is just going to result in worse code.
That's what I do, anyway.
But that's the entire value proposition of do-not-allow-construction-of-invalid-state!
If a User is actually a User, then you can say "the object is already constructed in a valid state".
If you decide to be 'less dogmatic' and instead construct Users-which-may-or-may-not-be-users-just-throw-an-ex-if-theyre-no-good, then you cannot say that the object is already constructed in a valid state.
In your case I would copy rather than mutate, unless I'm actually getting noticably higher profits from lower memory usage during the time between getting the object and GC. This hasn't happened to me yet.
class User {
Long? id;
String email;
String? name;
Integer? age;
Sex? sex;
Avatar? avatar;
User(String email) {
this.email = email;
}
}
record ProfileVm(String? name, Integer? age, Sex? sex, Avatar? avatar) {}
// some ui controller
var user = findById(id)
if (user == null) {
return notFound();
}
var vm = ProfileVm.fromJson(request.json());
user.setName(vm.name());
user.setAge(vm.age());
user.setSex(vm.sex());
user.setAvatar(vm.avatar());
user.save();
return ok();I think we really need to consider that sometimes you want a place to hold together data, and some other times you are encoding some behavioral trait, and prior to records Java did not have a way of distinguishing between them.
My rule of thumb is to avoid exposing data in behavioral classes, and prefer immutable patterns for data holders. But sometimes it's not possible to not have setters (e.g. for builders and for JPA entities).
Methods and functions do something rather than are something so naming them as the action they complete seems much better form to me. 3 characters is a fair price for clarity.
> Drop the getters and setters. They don't add any value
They add the ability for the underlying object to change without breaking compatibility. This is particularly important on shared libraries where you are not going to be able to refactor every single call sight.
For instance we have an object in our model representing a building, we've gone through *three* different structures of what a building is, but we've managed to make those changes while maintaining compatibility through leaving deprecated methods that translate the calls.
Mutability or immutability is part of the type not the field so you have 4 combinations in total
List<T> list;
final List<T> list;
final ImmutableList<T> list;
ImmutableList<T> list;
And that’s not even considering whether each T in the list is mutable or not.
I avoided Spring for years because of its asinine favoring of setters over correct construction
It's always name.
Also, most of this is useless overkill.
If I have, in this case, a person object, I'm doing two things with it. Displaying it. Editing it.
If displaying, who gives a crap of its mutable? If I'm editing it, why are you wasting memory making a copy?
This just makes life way more difficult in most business apps.
Sure, if you NEED this, go for it. But for everyday bog standard business apps? Get the hell away from me.
Have you never hit a bug because an object mutated unexpectedly? Please do keep this in mind when you do, and spend hours of your life trying to hunt it down.
When you've done this 8 or 9 times after wasting days of your life debugging, you become enlightened to the joy of immutability. It's worth the tiny memory overhead.
Beyond that, get* is everywhere in of corporate OO especially in more classical OO languages. I'd argue the right way to do things as well, naming methods as actions, but that's a separate issue.
Sure, you can have naked attributes, but you'll be sorry once they aren't enough and you now have two parallel ideologies seeping through your software, increasing the risk of bugs. Concurrency and collection attributes are the common reasons you'll want getters, possibly also setters.