Avoid getters and setters whenever possible
dev.to
dev.to
From my point of view, your data structures are your API. Attempting to hide them behind methods so that you can do something clever doesn’t work when it’s not always you that constructs the data (I’m thinking of things like json deserialization and accepting data from outside your control.).
All the arguments behind getters and setters seem rooted in this fear that maybe you’ll change your mind later about your data, but somehow you won’t have to change the function signatures of your getters/setters when that happens... this seems so contrived to me it’s insane to design entire frameworks and paradigms with this as a central idea.
Maybe I’ve been using Go for a bit too long now but I really think the mixing of data and logic is a fundamental mistake.
Hence the need to have setters that would check for invariants and prevent invalid mutations.
Here's the next thing: objects are mutable by default. This is why a setter can be useful: it notices the event of state change and runs other code based on the new state.
When you have e.g. algebraic data types, you already have easier time limiting the space of possible combinations, making (most of) the invalid states unrepresentable.
When your data are immutable, any changes in them are explicit events (see react / redux), and running code on them is also explicit and straightforward.
also in a language like java, where you can't upgrade a field to a property(function), these were placeholders, IF someday you want to be smart about that field.
That’s the part I don’t get. Why would you suddenly decide one day that a field needs to be smart? What’s the use case for an API that promised something is just dumb data and then suddenly isn’t any more?
If you need encapsulation, you shouldn’t be pretending to the downstream consumers that you have “fields” at all. A good rule of thumb to me seems that if all you’re writing is getFoo/setFoo, you’re just a struct in disguise, and when you want to start making these things “smart” you need to rethink your API because the thing has fundamentally changed from data to logic, and pretending otherwise seems a really bad idea.
I guess what I’m saying is there’s a real place for dumb data in programming. And complicated behavior happening behind the scenes of a dumb-data facade sounds like an anti-pattern to me.
Say you are creating a UI library which has a TextField class. What should the TextField API have in place of the usual getText/setText and isEnabled/setEnabled methods? Say, if the library should support developers who want to create a text field that is automatically filled with data on a button press and can be disabled when a checkbox is ticked.
Note that I'm not arguing against you - I'm honestly curious about what the TextField should be replaced with or what its API should look like.
But that’s just the thing, the text field in this case isn’t something you’d ever confuse with a dumb struct... it has methods that accept key input, or disable/enable it, or calculate its clipping area, etc. I don’t look at these as “getters” or “setters”, but instead just methods on an object like any other.
It may seem like I’m shifting the goalpost here and I’m sorry, I really do have trouble articulating this, but getters/setters seem like an anti pattern to me precisely because they let the consumer think they’re just dumb properties (along with invariants like the data you get out matching the data you put in, which you can never actually assume) when they’re anything but.
It seems to be important to separate data from logic, and API from implementation in discussions like these. Sure, I can make a nice API for a visual Rectangle object with dynamic width and height, and add a check to my setWidth method to make sure the width is never negative and the changes trigger a repaint. But if I need a Rectangle struct to store data in the implementation of my library, it can be way simpler to just make the Rectangle have a public final/const width member, and set and check the width in the constructor.
Sometimes you need the smart Rectangle, and sometimes you need a dumb struct with width and height.
I think this is the essence of the "composition over inheritance" idea that seems to be the most violated piece of sound OO advice out there.
Most OO languages have type systems not fitting for such checks, though. Thus runtime checks, often a part of a setter.
Getters definitely seem less likely to have cases like that unless there’s some sort of ordering contract you need to enforce.
How valuable this is will come down to audience: if it’s a small team which is very familiar with the app, there’s considerably less value than a large app with a huge team or a library intended for other people, especially non-experts.
In other words when the values are part of a larger data structure that will break consistency if not updated via the proper functions. a.k.a. there are supposed to be side effects (of a sort) when the value is changed. This has always been my understanding of where the concept came from. It's encapsulation/protection of structure, not of a specific variable.
There are times when you want to change things only via an API and there are times when you don't care. You will usually know in advance but not always. As an example, try making a diagram editor and later decide you'd like to implement undo/redo after the fact.
Having classes where values are “restricted” doesn’t make many sense in my mind. It’s a flaw in your type system if invalid values are possible... proper algebraic typing with pattern matching would do an infinitely better job at this.
And linked values are more smell to me... it’s a sign you have a bad abstraction if two properties have to be set in tandem or your object becomes invalid. Can you think of a good example for purposes of discussion?
That’s not saying you couldn’t have an API which is set, set, set, validate() but there’s an aesthetic argument for early enforcement so invalid values can never be set.
Type systems can’t solve everything but I’d agree that it’s not surprising that you see these design patterns most in languages like Java which didn’t have a better way.
I don't think a User class should be directly validating the zip lives inside the state in some sort of setter method... which one do I set first to make it work? It looks like once I change one I can't change the other any more... I'd need to have a method that changes both at once. And this is a perfect example of your data not matching your encapsulation: your encapsulation would want to express that you need to provide a zip/state pair via some setLocation(zip, state) method, which does not correspond to any private property you actually have.
But I definitely would prefer the set,set,set,validate approach more, and I would even say the validate() belongs in a different class altogether, ideally as a side-effect-free standalone method. (Basically, I should be able to set an invalid state and zip property in a vacuum, it's only when I go to validate it that it returns invalid.)
Going further I would implement any form submission system as taking some Validatable interface, where the Validatable object in this case would store your Address object via composition, and would represent the specific logic for validation for these purposes... but it would all live separately from a User struct, which would just be a dumb struct.
The idea I had in mind for that admittedly contrived example was roughly that you'd have an address class and some logic along the lines of setting a field automatically clears the lower-level fields, but that's not really a practical design as much as an illustration of the pattern of having complex logic in a setter.
I should note that this isn't my preference, either – either set,set,set,validate or updateLotsOfFields(dataStructure) – but I've seen people who felt otherwise and had reasonable arguments for this kind of behaviour.
So, you’re thinking of an address class that says “I know what zip codes go with what states” _and_ “if you don’t know what zip codes are valid with what states, I’ll throw an exception”? That’s cruel. Changing a user’s address would be quite the challenge, if (s)he moved to a different state (you have to change the state and the zip code in one go)
A better design has some kind of oracle that, given possibly incomplete address info, returns an ‘address’ that you then can atomically set on a user:
user.address = streetMap.find(“123 Main Street, USA”)I tend to prefer the latter approach but I've worked with people who felt differently for reasons which I'm unwilling to entirely reject. Both can be made to work[1] and there's enough variation between languages, projects, and environments that I think the choice is at least understandable.
1. To answer your last point: something along the lines of obj.setZip() raising an exception but obj.setState() automatically cleared the related value on a change.
And even if you mostly use public properties, there will still be cases where you'll need getter/setter so you'll end up with a messy mix of getter/setter and public properties. Better be consistent and always use getter/setter.
I would argue that everything that can be immutable should be immutable though unless you have good reason for it to be mutable.
There's really two missing features of java that could solve 90+% of the use cases for getters/setters.
- Attributes: annotation or keyword based directives for the compiler to generate getters/setters for a given instance variables
- Default + named parameters which would reduce a lot of the pain of multiple constructors and constructors with a tremendous number of parameters.
Setter methods are 95% the same as direct mutation of encapsulated state. It degrades the concept of objects being smart independent entities.
"Smart independent entities"? That's a new one on me. What does that mean?
Just be aware that Lombok bleeds into other areas as well, e.g. you'll need a recent version of Sonar to properly calculate coverage.
Setters are great when you want to do some validation and add an entry in a log when the setter is used.
There are rules what not to do in getters or in setters so if you use does practices you are ok.
You agree with the author but haven't realized it, I think.
While you can avoid making it worse with idioms like only using constructor injection, you can't unwrite the billions of lines written to date.
Kotlin makes a great effort to atone for the sins of the past.
But you can fix them. One line at a time. So many years of atonement still to do ...
Even for small classes where there is no design pattern involved, it is important to think about the interface first then the data. take for example, a rectangle, you may think (g/s)etters would be pointless and be tempted to do
class Rectangle:
public x, y, w, h;
public Rectangle(x, y, w, h):
this.x = x;
...
Then you need the area class Rectangle:
...
public area():
return w * h;
Then you need to cache the area class Rectangle:
public x, y, w, h, cached_area;
public Rectangle(w, h):
this.w = w;
this.h = h;
cached_area = w * h;
public area():
return cached_area;
Problem here is that w and h being public, user can corrupt the area, you now need
to rely on (s/g)etters. class rectangle
...
private w;
public setWidth(w):
this.w = w;
updateArea();
private updateArea():
cached_area = w * h;
If you had start with an interface only having operations, you would never had that problem. interface Rectangle:
getX();
setX(x);
getWidth();
setWidth(w);
area();
...
// implementations are irrelevant to the user.
class RectangleCached implements Rectangle:
...
class RectangleLazy implements Rectangle:
...
class RectangleWhatever implements Rectangle:
...
One of the great leaps in OO is to be able to answer the question "How does this work?" with
"I don't care" (Alan Knight)Note that another solution in this case would be immutability but this is another discussion.
If you own all consumers of your code, and it's all part of the same project, then you can safely refactor from field access to getters and setters if and when you need to add extra semantics. If you own the consumers but they're not all part of the same project, you can refactor but it will take longer and be a multi-step exercise.
If you don't own the consumers, then you need to break compatibility, which means major version bumps, which will leave some downstream consumers on older versions, and will probably increase version divergence in your userbase, fragmenting the ecosystem and increasing your support load.
> * I would consider creating both a getter and a setter for the data use case in a public API, like let's say I am writing a library meant to be used as a dependency in a lot of other applications. But I would only consider it after exhausting all of these list items.
I'm guessing this is a result of 'insane' processes where you have people upgrading dependencies in deployed applications without properly going through a test process.
(EDIT: oops i thought this was a c# post. but this change in java breaks source compatibility as well as binary compatibility)
> (...) My question is how often has the working programmer ever had to do that? I don't remember ever doing this in all my years of software.
has that hollow ring of "I write my software the way it doesn't allow easy changes, so I don't do them, so there's no reason to do it." ... there probably would've been reason to do it more than once, the author just didn't want to do it because it would've been painful.
Anyway: If you can make your class work without exposing anything then go ahead. That's great. But if you have to allow external access a getter will always be better than direct access.
p.s.: Setter means mutable class. Mutable classes are a performance hack. Sometimes you have to do it, most of the time it's premature optimization.
Could you please clarify this point? I have no idea what you're trying to say.
Suppose I have:
Point point = new Point(50, 50);
int x = 100;
int y = 200;
Point translatedPoint = point.plus(x, y); // new Point(this.x + x, this.y + y)
This code suggests that Point is immutable. Once defined, a point never changes. This makes a lot of sense -- if you change the values of x and y, you have a different point, in a mathematical sense.But newing up an object is tremendously slower than integer arithmetic. Suppose I can do this:
Point point = new Point(50, 50);
point.translateX(100); // this.x += x
point.translateY(200); // this.y += y
Point translatedPoint = point;
This is going to lead to translatedPoint much more quickly than the first code. But it's riddled with troublesome outcomes. Who else was relying on point? What happens if I change it again? Why am I able to mutate something that, mathematically, isn't mutable?The basic reason mutability improves performance is that you can change things in-place without making a new copy. Most of the stuff being done during the copy construction is overhead compared to the thing you're changing. The price might be too high in a performance-sensitive situation.
There are workarounds based on using copy-on-write datastructures with clever shared bits, but they're not native idioms in Java-land or in most languages without a strong functional heritage.
See the difference between GregorianCalendar (or Date) and the more current java date APIs. The new ones are immutable and every "mutable" operation returns a new object instead of changing the old one.
But most importantly, the real practical differences between these styles are minimal for all practical purposes. It is all mostly matter of habit. You might as well argue about which shade of blue is best for syntax highlighting.
You cannot tell if a map passed to you has any of the members you expect, hasn't had additional unexpected members added, or anything else about it without parsing it, it's a mystery bag. There are times when a mystery bag is fine and you're going to be inspecting it anyway, but most of the time your code has at least some expectations about the structure of your data, and for that you need real specific types to maintain any kind of sanity. Otherwise, you're re-validating data over and over or writing code that relies on hope and faith instead of correctness.
The horror... /s
Such a thing does reek of design smell to me, and is a sign that you’re blending your data and your logic too much.
An object representing a dumb data structure can be written in order to have many useful guarantees upon construction, for example making it impossible for the object to be created without fully initializing it, or guaranteeing the presence of all "keys" in the structure. This is easily guaranteed statically and it's so damn useful!
That is, you need useful zero values (which means no null pointers), and any method that takes the object should be able to do sensible (if not useful) things even if things are their zero-values.
As for validation, you don't always know what different consumers of your type consider to be valid or not. For a rectangle any non-negative size should be valid, for instance, but when you try to pass a zero-sized x side to a class that (for some reason) needs non-zero x length, that class should be doing the validation, not the Rectangle class itself.
I think I'm coming at this after doing nothing but golang for a few years, and then having to switch to Java recently, and I really hate the language so far, mostly because of the style of programming it encourages.
> that class should be doing the validation, not the Rectangle class itself.
I partially disagree. You should have a layer after which all (request) parameters are surely and clearly validated. This avoids repeated validations. This layer should be separated from any logic layer. Additionally a class like Rectangle should have a sanity check in the constructor, and throw a fatal exception if the passed in values are invalid.
> you don't always know what different consumers of your type consider to be valid or not
I try to make things in such a way that an object of a type is either valid or not. If two consumer have different notions of validity about the ~~same~~ similar (!) type of data, I'd rather create a new type.
EDIT: For the exact reason that this allows to use static analysis and avoid silly checks for 0-sized x sides. What does a 0-sized side mean? This type of data can only come from bad user-input, and it should stop at the first line of the controller, and not a step further.
public int member { get; private set; }
That said, C# is very much my kryptonite and I hate it, so don't ask me any further questions.private(set) is a thing
But xml and db to object mapper often need them :(
I fundamentally agree with the author: Getters and setters often break Tell, don’t ask, increase the public interface of a class (and expose implementation details), and add mutable state, which makes reasoning about the program’s state harder — for getters, this is often mitigated by defensive copying, which has its own set of issues (it can introduce glaring inefficiencies in the code base, which, in many cases, could have been entirely avoided).
The extreme case is when getters and setters lead to quasi-classes [1]. All this is uncontroversial when you think about it (and is backed by a vast consensus in the literature and by what little evidence we have in evidence-based software engineering). On the other hand, I agree with the author that accessors are sometimes a practical solution and shouldn’t be banned entirely.
But I don’t think the author states the case well at all. There’s too much talk about feelings, which is a poor substitute for arguments. This starts with the first example that’s provided: Contrary to what the article claims, there’s of course a difference between `Car1` and `Car2`. Namely, `Car2` can add logic to its accessors without creating introducing API changes. `Car1` cannot do this: If I for instance need to ensure that `engine` is never set to `null` by the user, or I need to add logging, or I need to add deferred loading, etc, I need to make `engine` `private` and add public accessors.
Future-proofing isn’t always relevant but it unfortunately tends to become relevant unexpectedly. Anyway, neither example is probably very good code and, conversely, either piece of code can be appropriate. But they’re emphatically not the same.
I also don’t think the “list of options” at the end is particularly useful because it seems to be a substitute for properly thinking about the architectural design of the code (which doesn’t need to happen up-front, it can happen iteratively): Many accessors are simply never necessary and were instead added “just in case”, or because the programmer thinks about objects as “bags of data” instead of, more appropriately, “[black] boxes with a specific observable behaviour”. This simple change of perspective will drastically reduce the number of public accessors in a code base; but this happens as a side-effect of better OO design, rather than a goal in itself.