16

I am not looking for an opinion about semantics but simply for a case where having getters sensibly used is an actual impediment. Maybe it throws me into a never-ending spiral of relying on them, maybe the alternative is cleaner and handles getters automatically, etc. Something concrete.

I've heard all the arguments, I've heard that they're bad because they force you into treating objects as data sources, that they violate an object's "pure state" of "don't give out too much but be prepared to accept a lot".

But absolutely no sensible reason for why a getData is a bad thing, in fact, a few people argued that it's a lot about semantics, getters as fine per-se, but just don't name them getX, to me, this is at least funny.

What is one thing, without opinions, that will break if I use getters sensibly and for data that clearly the object's integrity doesn't break if it puts it out?

Of course that allowing a getter for a string that's used to encrypt something is beyond dumb, but I'm talking about data that your system needs to function. Maybe your data is pulled through a Provider from the object, but, still, the object still needs to allow the Provider to do a $provider[$object]->getData, there's no way around it.


Why I'm asking: To me, getters, when used sensibly and on data that is treated as "safe" are god-sent, 99% of my getters are used to identify the object, as in, I ask, through code Object, what is your name? Object, what is your identifier?, anyone working with an object should know these things about an object, because nearly everything about programming is identity and who else knows better what it is than the object itself? So I fail to see any real issues unless you're a purist.

I've looked at all the StackOverflow questions about "why getters / setters" are bad and though I agree that setters are really bad in 99% of the cases, getters don't have to be treated the same just because they rhyme.

A setter will compromise your object's identity and make it very hard to debug who's changing the data, but a getter is doing nothing.

coolpasta
  • 657

6 Answers6

25

You can't write good code without getters.

The reason why isn't because getters don't break encapsulation, they do. It isn't because getters don't tempt people to not bother following OOP which would have them put methods with the data they act on. They do. No, you need getters because of boundaries.

The ideas of encapsulation and keeping methods together with the data they act on simply don't work when you run into a boundary that keeps you from moving a method and so forces you to move data.

It's really that simple. If you use getters when there is no boundary you end up having no real objects. Everything starts to tend to the procedural. Which works as well as it ever did.

True OOP isn't something you can spread everywhere. It only works within those boundaries.

Those boundaries aren't razor thin. They have code in them. That code can't be OOP. It can't be functional either. No this code has our ideals stripped from it so it can deal with harsh reality.

Michael Feathers called this code fascia after that white connective tissue that holds sections of an orange together.

This is a wonderful way to think about it. It explains why it's ok to have both kinds of code in the same code base. Without this perspective many new programmers cling to their ideals hard, then have their hearts broken and give up on these ideals when they hit their first boundary.

The ideals only work in their proper place. Don't give up on them just because they don't work everywhere. Use them where they work. That place is the juicy part that the fascia protects.

A simple example of a boundary is a collection. This holds something and has no idea what it is. How could a collection designer possibly move the behavioral functionality of the held object into the collection when they have no idea what it's going to be holding? You can't. You're up against a boundary. Which is why collections have getters.

Now if you did know, you could move that behavior, and avoid moving state. When you do know, you should. You just don't always know.

Some people just call this being pragmatic. And it is. But it's nice to know why we have to be pragmatic.


You've expressed that you don't want to hear semantic arguments and seem to be advocating putting "sensible getters" everywhere. You're asking for this idea to be challenged. I think I can show the idea has problems with the way you've framed it. But it also think I know where you're coming from because I've been there.

If you want getters everywhere look at Python. There is no private keyword. Yet Python does OOP just fine. How? They use a semantic trick. They name anything meant to be private with a leading underscore. You're even allowed to read from it provided you take responsibility for doing so. "We're all adults here", they often say.

So what's the difference between that and just putting getters on everything in Java or C#? Sorry but it's semantics. Pythons underscore convention clearly signals to you that you're poking around behind the employees only door. Slap getters on everything and you loose that signal. With reflection you could have stripped off the private anyway and still not have lost the semantic signal. There simply isn't a structural argument to be made here.

So what we're left with is the job of deciding where to hang the "employees only" sign. What should be considered private? You call that "sensible getters". As I've said, the best justification for a getter is a boundary that forces us away from our ideals. That shouldn't result in getters on everything. When it does result in a getter you should consider moving the behavior further into the juicy bit where you can protect it.

This separation has given rise to a few terms. A Data Transfer Object or DTO, holds no behavior. The only methods are getters and sometimes setters, sometimes a constructor. This name is unfortunate because it's not a true object at all. The getters and setters are really just debugging code that give you a place to set a breakpoint. If it wasn't for that need they'd just be a pile of public fields. In C++ we used to call them structs. The only difference they had from a C++ class was they defaulted to public.

DTO's are nice because you can throw them over a boundary wall and keep your other methods safely in a nice juicy behavior object. A true object. With no getters to violate it's encapsulation. My behavior objects may eat DTO's by using them as Parameter Objects. Sometimes I have to make a defensive copy of it to prevent shared mutable state. I don't spread mutable DTO's around inside the juicy part within the boundary. I encapsulate them. I hide them. And when I finally run into a new boundary I spin up a new DTO and throw it over the wall thus making it someone else's problem.

But you want to provide getters that express identity. Well congrats you've found a boundry. Entities have an identity that goes beyond their reference. That is, beyond their memory address. So it has to be stored somewhere. And something has to be able to refer to this thing by it's identity. A getter that expresses identity is perfectly reasonable. A pile of code that uses that getter to make decisions that the Entity could have made itself is not.

In the end it's not the existence of getters that is wrong. They are far better than public fields. What's bad is when they are used to pretend you're being Object Oriented when you're not. Getters are good. Being Object Oriented is good. Getters are not Object Oriented. Use getters to carve out a safe place to be Object Oriented.

candied_orange
  • 119,268
13

Getters violate the Hollywood Principle ("Don't call us, we'll call you")

The Hollywood Principle (aka Inversion of Control) states that you don't call into library code to get things done; rather, the framework calls your code. Because the framework controls things, broadcasting its internal state to its clients is not necessary. You don't need to know.

In its most insidious form, violating the Hollywood Principle means that you're using a getter to obtain information about the state of a class, and then making decisions about which methods to call on that class based on the value that you obtain. it's violation of encapsulation at its finest.

Using a getter implies that you need that value, when you actually don't.

You might actually need that performance improvement

In extreme cases of lightweight objects that must have the maximum possible performance, it's possible (though extremely unlikely) that you can't pay the very small performance penalty that a getter imposes. This won't happen 99.9 percent of the time.

Robert Harvey
  • 200,592
2

Getters Leak Implementation Details and Break Abstraction

Consider public int millisecondsSince1970()

Your clients will think "oh, this is an int" and there will be a gazillion calls assuming that it is an int, doing integer math comparing dates, etc... When you realize that you need a long, there will be a lot of legacy code with problems. In a Java world, you'd add @deprecated to the API, everybody will ignore it, and you are stuck maintaining obsolescent, buggy code. :-( (I assume other languages are similar!)

In this particular case, I'm not sure what a better option might be, and the @candiedorange answer is completely on point, there are many times when you need getters, but this example illustrates the downsides. Every public getter tends to "lock" you into a certain implementation. Use them if you truly need to "cross boundaries", but use as little as possible and with care and forethought.

user949300
  • 9,009
1

I think the first key is to remember that absolutes are always wrong.

Consider an address book program, which simply stores people's contact information. But, let's do it right and break it up into controller/service/repository layers.

There's going to be a Person class that holds actual data: name, address, phone number, etc.. Address and Phone are classes, too, so we can use polymorphism later on to support non-US schemes. All three of those classes are Data Transfer Objects, so they're going to be nothing but getters and setters. And, that makes sense: they're not going to have any logic in them beyond overriding equals and getHashcode; asking them to give you a representation of a full person's information doesn't make sense: is it for HTML presentation, a custom GUI app, a console app, an HTTP endpoint, etc.?

That's where the controller, service, and repository come in. All of those classes are going to be logic-heavy and getter-light, thanks to dependency injection.

The controller is going to get a service injected, which will expose methods like get and save, both of which will take some reasonable arguments (eg., get might have overrides to search by name, search by zip code, or just get everything; save will probably take a single Person and save it). What getters would the controller have? Being able to get the concrete class's name might be useful for logging, but what state will it hold other than the state that's been injected into it?

Similarly, the service layer will get a repository injected, so it can actually get and persist Persons, and it might have some "business logic" (eg., maybe we're going to require that all Person objects have at least one address or phone number). But, again: what state does that object have other than what's injected? Again, none.

The repository layer is where things get a little more interesting: it's going to establish a connection to a storage location, eg. a file, an SQL database, or an in-memory store. It's tempting, here, to add a getter to get the filename or the SQL connection string, but that's state that's been injected into the class. Should the repository have a getter to tell whether or not it's successfully connected to its data store? Well, maybe, but what use is that information outside of the repository class itself? The repository class itself should be able to attempt to reconnect to its data store if need be, which suggests that the isConnected property is of dubious value already. Further, an isConnected property is probably going to be wrong just when it would most need to be right: checking isConnected before attempting to get/store data doesn't guarantee that the repository will still be connected when the "real" call is made, so it doesn't remove the need for exception handling somewhere (there are arguments for where that should go, beyond the scope of this question).

Consider also unit tests (you are writing unit tests, right?): the service isn't going to expect a specific class be injected, rather it's going to expect that a concrete implementation of an interface is injected. That lets the application as a whole change where the data is stored without having to do anything but swap out the repository being injected into the service. It also allows the service to be unit tested by injecting a mock repository. This means thinking in terms of interfaces rather than classes. Would the repository interface expose any getters? That is, is there any internal state that would be: common to all repositories, useful outside of the repository, and not injected into the repository? I'd be hard-pressed to think of any myself.

TL;DR

In conclusion: aside from the classes whose sole purpose is to carry data around, nothing else in the stack has a place to put a getter: state is either injected or irrelevant outside of the working class.

minnmass
  • 317
1

What is one thing, without opinions, that will break if I use getters sensibly and for data that clearly the object's integrity doesn't break if it puts it out?

Mutable members.

If you have a collection of things in an object that you expose via a getter, you are potentially exposing yourself to bugs associated with the wrong things being added into the collection.

If you have a mutable object that you are exposing via a getter, you're potentially opening yourself up to the internal state of your object being changed in a way that you do not expect.

A really bad example would be an object that is counting from 1 to 100 exposing its Current value as a mutable reference. This allows a third party object to change the value of Current in such a way that the value could lie outside the expected bounds.

This is mostly an issue with exposing mutable objects. Exposing structs or immutable objects isn't a problem at all, since any changes to those will change a copy instead (usually either by an implicit copy in the case of a struct or by an explicit copy in the case of an immutable object).

To use a metaphor that might make it easier to see the difference.

A flower has a number of things which are unique about it. It has a Color and it has a number of petals (NumPetals). If I'm observing that flower, in the real world, I can clearly see its color. I can then make decisions based on that color. For example, if color is black, do not give to girlfriend but if color is red, give to girlfriend. In our object model, the color of the flower would be exposed as a getter on the flower object. My observation of that color is important for actions that I will perform, but it does not impact at all on the flower object. I should not be able to change the color of that flower. Equally, it does not make sense to hide the color property. A flower generally cannot prevent people from observing its color.

If I dye the flower, I should call the ReactToDye(Color dyeColor) method on the flower, which will change the flower according to its internal rules. I can then query the Color property again and react to any change in it after the ReactToDye method has been called. It would be wrong for me to directly modify the Color of the flower and if I could then the abstraction has broken down.

Sometimes (less frequently, but still often enough that it's worth mentioning) setters are quite valid O-O design. If I have a Customer object it is quite valid to expose a setter for their address. Whether you call that setAddress(string address) or ChangeMyAddressBecauseIMoved(string newAddress) or simply string Address { get; set; } is a matter of semantics. The internal state of that object needs to change and the appropriate way to do that is to set the internal state of that object. Even if I require a historical record of addresses that the Customer has lived in, I can use the setter to change my internal state appropriately to do so. In this case it does not make sense for the Customer to be immutable and there is no better way to change an address than to provide a setter to do so.

I am not sure who is suggesting that Getters and setters are bad or break object oriented paradigms, but if they do, they're probably doing so in response to a specific language feature of the language that they happen to use. I get the feeling that this grew out of the Java "everything is an object" culture (and possibly extended itself to some other languages). The .NET world doesn't have this discussion at all. Getters and Setters are a first class language feature that are used not only by people writing applications in the language, but also by the language API itself.

You should be careful when using getters. You do not want to expose the wrong pieces of data and nor do you want to expose data in the wrong way (i.e. mutable objects, references to structs that can allow a consumer to modify internal state). But you do want to model your objects so that the data is encapsulated in such a way that your objects can be used by external consumers and protected from misuse by those same consumers. Often that will require getters.

To summarize: Getters and Setters are valid object-oriented design tools. They're not to be used so liberally that every implementation detail is exposed but nor do you want to use them so sparingly that an object's consumers cannot use the object efficiently.

Stephen
  • 8,868
  • 3
  • 31
  • 43
-1

What is one thing, without opinions, that will break if I use getters...

Maintainability will break.

Any time (I should say almost always) you offer a getter you basically give away more than was asked of you. This also means that you have to maintain more than was asked, ergo you lower the maintainability for no real reason.

Let's go with @candied_orange's Collection example. Contrary to what he writes a Collection shouldn't have a getter. Collections exist for very specific reasons, most notably to iterate over all items. This functionality is not a surprise to anyone, so it should be implemented in the Collection, instead of pushing this obvious use-case on the user, using for-cycles and getters or whatnot.

To be fair, some boundaries do need getters. This happens if you really don't know what they will be used for. For example you can programmatically get the stacktrace from Java's Exception class nowadays, because people wanted to use it for all sorts of curious reasons the writers didn't or couldn't envision.