Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.



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.


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


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.


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 can use the setter to make a property observable.




Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: