Why getters and setters are terrible
This is an episode of Thoughts on Functional Programming, a podcast by Eric Normand.
Getters and setters kick the domain modeling can down the road. They leave the real design work to some other part of the code. They don't do enough to protect the semantic integrity of the object. They're terrible.
Eric Normand: People, do you want to hear a rant on why getters and setters are terrible? Well, stay tuned. My name is Eric Normand and I help people thrive with functional programming.
Getters and setters are a terrible practice. I believe that this is an objective fact that I will demonstrate with a watertight argument. I'll make a case and there will be no possible way you can refute.
Now, I'm not talking about when you need getters and setters because you're using some API that requires a class that has getters and setters on it. There's pragmatic reasons, practical reasons, to use getters and setters.
I'm just not talking about that. I'm talking about writing some greenfield software that has no such constraints and using getters and setters as a way of modeling your class.
Here's the thing about it. Getters and setters turn each class into a glorified data structure. The data structure has its own bespoke API. It has a get X and a set X and a get Y and a set Y. It's bespoke.
You have to learn that. Consumers of this class have to learn that. They have to write code just for your API.
API design is hard. You can't just throw it together like that. It's a new thing to learn. There's no reuse. No other class has that set of methods on it. It's totally unique, totally bespoke. You have to learn how to use it.
It's worse than that because, in terms of data modeling, all you're doing is kicking the can down the road. You are saying, for instance, "Oh well, I need a set X because X might change. It might need to change after I've instantiated this object."
I'll grant you that X might need to change. I'm granting that even though I don't like mutable state. I'm not fighting that fight right now.
Let's say X does need to change. The problem is you're kicking the can down the road. You're putting a set X on there, but you're not modeling or putting any thought into what that change means.
You're just saying, "I'm going to let some other part of the code decide when X changes and under what conditions and why it might want to change."
Let me give a more concrete example. Let's say you have an employee class and the employee has a salary. You need to say, "Well, the salary can change, right? I'm going to put a set salary on the employee class."
What does it mean to change the salary of the employee? Did you make a mistake when you entered in the salary and now you need to correct that mistake? You left off a zero or something? Oops. That's a certain action you're taking on this employee. You're correcting a mistake in salary.
What if they get a raise? That's a different thing that could happen to this employee. They could have their salary decreased as some punishment or maybe the company's going through trouble and they say, "Well, we have to pay everybody 10 percent less." I've heard of that happening.
That's not really a correction to a mistake. It is a change. Someone decided that you need to be paid more or less. What you're doing by saying, "Set salary" is you're saying, "You can set it to anything you want."
You can set it to three dollars with no notion of how the business runs, what changes you might actually expect. You're just kicking the can down the road.
What that turns this employee into is simply a data structure that stores whatever information is in there including the salary and makes it very hard to do any logic with this employee except change stuff on it and read it back.
The same thing happens with getters. You're just saying, "I need to get the salary out. At some unknown point in the future, some other system needs to read the salary and so I'm going to return it. I need to have a way to get the salary out."
You're totally kicking the can down the road. Someone else is going to have to come along and some other piece of code. It's totally not controlled by this employee class, which is the whole point of the employee class is to maintain some abstraction barrier, some conceptual encapsulation of what it means to operate on an employee.
You're totally kicking it to some other part of the code to decide, "When should I read this? What can I do with this salary?" You're actually not doing data modeling at all. You're just passing the buck.
If you look at the original object-oriented systems, Smalltalk, they did not do this. They were trying to get away from this. They weren't trying to recreate data structures but with methods on them. They wanted a higher-level notion where each thing added meaning, each method added meaning to the data.
There's no meaning when you just read or write out directly or set it to whatever you want. You got to add meaning. The meaning would be, "This person got a raise." That means something, or "We made a mistake. We had to correct the salary."
This is another piece of part of the meaning that you're adding. It means totally different things to the system.
The same with getters. If you can just read or write out, you can do whatever you want with it. Any logic you want. This is why they're terrible. It's a terrible practice.
It encourages the use of classes and the instances of those classes as just data structures to just store stuff for you, for your other parts of the code, the other parts of the code as algorithms. They totally break the notion of encapsulation. They totally break the notion of a coherent cohesive abstraction.
They're just places in memory. Scratch paper that you can write all over and read from. You're relying on these other parts of the code to maintain whatever invariance you're trying to maintain because that's what it's for.
That's what the class is for. You have a few pieces of data in there. You need them to maintain a certain relationship. As one changes, the others need to change.
By just having these getters and setters, you're just throwing that out the window and while you're left with is this little code organization units. It's the same code. It's just in different files. You haven't actually made anything new and useful. You haven't constrained the problem to the domain. You've just opened it up.
All right. That's my rant. Getters and setters.
I don't know why we do them. I've read a lot about them. I read somewhere that it was used as an example. This is the origin of getters and setters. It was used as an example in the JavaBean spec. Never intended as a recommendation.
It was simply, "Here's a simple way you could create this little Bean that stores and gives you back data." It was never meant as a recommendation. The people who wrote this back say that. They say, "This was not our intention. We wanted something with more semantics to it, more meaning to it."
People said, "Oh, let's do that. That looks easy." All the IDEs make it really easy to just you click a couple buttons and you get all the getters and setters made for you. That's where we're at.
That's what most people think object-oriented programming is. The same old imperative programming, but with things stored in objects instead of invariables and structs.
It goes much deeper than that. Unfortunately, object-oriented programming now is too broad and diffuse to have any real power to it. Sorry to say. There are people doing really good things with it, with object-oriented design and thinking about how the code should be.
Unfortunately, they are small voices in a loud cacophony of getters and setters. It sucks.
My next rant will be about how Clojure maps give you the getters and setters for free and people overuse them.
Thank you very much for listening to my rant. If you want to hear other rants or other non-rants, sometimes more informative than this one, you can get all the future and past episodes at lispcast.com/podcast where you find text audio and video of all of this links to subscribe and links to get in touch with me on social media including email, Twitter, and LinkedIn.
All right. May all your higher-order functions be pure. I'm Eric Normand. Rock on.