70 comments

[ 0.24 ms ] story [ 117 ms ] thread
This is strange advice. There's no real drawback from using getter/setter and there are obvious benefits.

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.

Getters, yes.

I would argue that everything that can be immutable should be immutable though unless you have good reason for it to be mutable.

Thats the right answer. Have immutable classes with constructor-injected fields, and getters. In Java Lombok makes this scalable to many members using the builder pattern, and in Kotlin its a non-issue anyway.
Agreed but immutability can lead to builders and builders lead to lots of code bloat. Plus, from what I've experienced lots of frameworks depend on java bean conventions (i.e. getters/setters for everything).

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.

Instead of attributes that lead to creation of getters/setters, how about the simple notion of private or public access to members? I'm not sure which should be the default, but it sounds like Java programmers would like them private unless otherwise specified. Why add code when all you want is to restrict access?
You can use setters that return a reference to a new object with the updated says, using some copy and delete mechanism for immutability.
If one is commanding an object to set a private data field which one somehow knows the name of, then it's not an object that is being dealt with, just a procedural programming data bag.

Setter methods are 95% the same as direct mutation of encapsulated state. It degrades the concept of objects being smart independent entities.

Getters/setters vs public properties has nothing to do with encapsulation. Obviously not all properties are public/have getters and setters.
I didn't mention getters at all. I agree that encapsulation status remains constant in the presence of either setter methods or direct mutation, i.e. broken.
> It degrades the concept of objects being smart independent entities.

"Smart independent entities"? That's a new one on me. What does that mean?

An object graph operating more like a network of tiny special-purpose computers with I/O (messages/method-calls). Rather than occupants that are conscious of being not much more than rectangles in a single memoryspace. Alan Kay also made analogies with cell biology.
I disagree with the author, getters and setters are a good tool to have and used in the right place.

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.

The author admits you indeed cannot avoid using them sometimes and mentions exactly the validation case as one of the times you end up writing something similar to a setter anyway.

You agree with the author but haven't realized it, I think.

No admission is necessary. There is an entire class of immutable languages (typically functional), which don’t allow mutation (and therefore don’t need setters).
I try to avoid setters whenever possible in favor of constructors taking the dependency as parameter. That way, reasoning about the lifecycle of dependencies is much easier.
I agree, if you can make the object imutable by setting all it's fields at construction time then it is great, if you have to update the object then is good to use setters or functions that can perform validation, it makes it easy to catch errors earlier.
The argument for getters and setters is much stronger when you're writing libraries that are dependencies for projects you don't control.

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.

From TFA:

> * 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 think it is kind of insane not to recompile when your downstream dependencies change and the recompilation fixes this problem. Like presumably it makes sense to run your tests again when you change downstream dependencies and it is extra work to run tests independently of recompilation so I think most people's process would be update dependency -> recompile -> run tests. I just remember Java having lots of gotchas with binary compatibility and I assume C# has similar issues. It's just easier to not depend on binary compatibility and always recompile.

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 understanding is that C#'s properties were designed pretty much exactly for this situation.
Python's properties are great for this. You can start off with public attributes and later switch to properties without changing the API.
tl;dr: “Don’t use getters and setters” shouldn’t be a goal to strive for. Good OO design should be the goal, and the reduction of getters and setters (and of public data members) is a consequence of that.

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.

[1] http://www.idinews.com/quasiClass.pdf

IMHO getters & setters can be a bad smell, symptomatic of classes that are just dumb aggregations of data that have no significant behaviour. I've seen them most often in Java - a single paradigm lang that forces every method or data item to be a class member. And then, because our data item is a class member, it must be encapsulated. Multi paradigm langs offer better approaches to data aggregations. Python, for instance, gives us dictionaries and named tuples as well as classes.
Generic data types aren't necessarily a better approach. My primary language for many years has been PHP, where the default approach used to be just to pass generic maps around for all data, and it was hell.

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.

Why use actual objects and rely on static anlysis when you can write tens of unit tests that ensure the maps/tuples passed around always contain the correct data structures? And you get the added benefit of 100% test coverage.
now you have to add (manual) type checks in your tests instead of your compiler doing it for you
You probably didn't get the sarcasm :).
> dumb aggregations of data that have no significant behaviour.

The horror... /s

I think the point is that dumb data structures should look like dumb data structures, with a language construct like `struct`, rather than trying to defensively put everything as a getter/setter because “maybe this won’t be a dumb struct in the future” or something.

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.

The problem with a struct-like construct is that it doesn't have any way of guaranteeing some data is present and valid or not. I'm thinking about C++-like structs or python maps.

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!

I appreciate what you're saying, and I agree that it's a valid approach, but I would be more interested in making any of the methods which are interested in the object, capable of handling any of the values expressible by the object's type.

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.

I mostly agree with you except:

> 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.

The first point:

> (...) 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.

> Mutable classes are a performance hack.

Could you please clarify this point? I have no idea what you're trying to say.

It's often much faster to modify something in place than to create a modified copy.

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.

Immutable classes are far easier to reason about than mutable classes. You run the constructor and know "this instance won't ever change again" - but it means if you want a change you need to create a copy with the changed data, which can be a performance problem if you have to do it very often.

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.

Overlooked is how they became so ubiquitous: JavaBeans. It was well-meant, but it's left a lot of unintended pain in its wake (I'm looking at you, JPA constructors). Without that standard, Java code might feel quite differently today.

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.

> 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.

But you can fix them. One line at a time. So many years of atonement still to do ...

For Java exists the Project Lombok, it works pretty well for this purpose, you can use getters and setters without polluting the code with auto-generated stuff. Have some other cool features like fluent accessors, builders, auto cleanups for close() and plugins for IDEs. IMHO every Java programmer should check it out.
Or AutoValue (https://github.com/google/auto) for something a bit less involved - Eff. Java 3 recommends it as an alternative to writing equals/hashcode et. all by hand.
Nowadays its quite nice. There used to be some pain with regards to IDE integration, but the IntelliJ Plugin works very well.

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.

My favorite is immutables (https://immutables.github.io/). It's really frictionless and at this point it feels like an extension of the language to me. It also lets you fall back to mutable types when you need them, despite the name.
I have sometimes wondered why none of the mainstream OO languages implemented an access class of publicly readable but only privately modifiable.
Does Swift count?

private(set) is a thing

Sure, Swift counts - thanks for the correction. Maybe those with experience of it could comment on whether they found this feature useful in practice.
Some do -- for example in C# you can do:

    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.
I prefer getters setters over Car1 example especially for large classes. I am quite used to use prefix to filter for methods in IDE - get<thing>, add<thing>, set<thing>, create<thing>, is<thing>, to<thing>, etc. Often you know which one of those subgroups you are interested in and can do ctrl-o or ctrl-space and filter irrelevant options out. The worst option of all is when there are both getters/setters and public fields.

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.

I prefer actions over getter and setters. The object should take care of itself (ddd style).

But xml and db to object mapper often need them :(

Some Java frameworks let class and interface authors use decorators on getters / setters to control how particular class/interface fields are used in some contexts ... e.g., one might have a field (due to an algorithmic consideration) in an entity that's useful when constructing POJOs (plain-old Java objects) that eventually are written to a database, but that particular field should should not be persisted to the database -- one can use decorators to control whether field goes to database, or the data type used to persist the field in the database, etc. It's sometimes useful to treat the getter and setter asymmetrically.
I have trouble articulating this point, but I never understood the argument why you’d want getters or setters at all.

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.

>> mixing of data and logic try lisp;)

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.

> someday you want to be smart about the 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.

I’m inclined to this position as well but consider cases where you need values to be restricted or linked. A setter would allow that to happen reliably every time without changing your API to require validation calls before doing anything else.

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.

>> consider cases where you need values to be restricted or linked...

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.

I guess I don’t think that way.

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?

A somewhat contrived example but say you had a user address with state and zip code values and you wanted to prevent setting a zip code outside of that state. Or with any kind of state change - e.g. certain statuses are only valid for paid accounts, you can only archive verified files, etc.

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 love this response because it's helping me formulate my objections a little better.

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.

> 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.

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.

”A somewhat contrived example but say you had a user address with state and zip code values and you wanted to prevent setting a zip code outside of that state.”

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”)
Note that it was described as “a somewhat contrived example”. I was just trying to get the idea of a relationship where some people would want to prevent you from setting incompatible values and others might have a separate step.

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.

You have a good point, but there are some cases where I have a hard time seeing how you would avoid getters and setters completely.

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.

In this case I would separate the data (probably the model the text field is wrapping) from the UI widgets that expose it... the model being a dumb struct and the text field being a smart object with methods that look (if you squint) more like getters/setters.

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.

Thanks for clarifying! I really don't think you're shifting the goalpost. What you're saying is basically that the getText/setText methods are part of the API of the visual TextField, like a moveLeft method would be part of the API of a robot controller. A dumb struct with mutable members, such as a Point with public x, y, z members, is on the other hand both the API and the data in the same object.

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.

Why wouldn't you want to keep the constraints as close to the data as possible? That way you avoid the issue that the Rectangle struct is reused somewhere else, but without the correct constraints.
IMO the constraints should be as close to the thing that needs the data, as possible. Your object is an expression of data that may be valid in some contexts and invalid in others, and trying to pick any one of them is difficult (and leads to things like complex inheritance trees to express all the different flavors of constraints.)

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.

These constraints should ideality be in the type system, or other statically checked constraints.

Most OO languages have type systems not fitting for such checks, though. Thus runtime checks, often a part of a setter.

You can use the setter to make a property observable.
The problem is that the typical OOP data declaration is a record, and its fields are not completely orthogonal. Its value space may be much larger than the space of valid combinations of field values.

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.

To me, objects is a data abstraction technique which is about abstracting the said data under a set of operations known as the interface; You are no more thinking about the data but about the operations which then allows all kind of interface abstraction techniques known as design patterns. Once you break that abstraction, you break everything which is relying on.

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.