Ask HN: Why do you make class members private?
I know this is something that a lot of people do without thinking about it. So let's think about it.
I want to know the main reasons for making variables and functions in a class private. How is it better? What can happen if you don't do it?
Here are a few possible reasons that I can think of that someone could have:
* You have been taught to do it so you just do it without thinking.
* It reduces the number of files you have to search in if you want to find all uses of a member.
* The member is hard to understand so you want to discourage people from using it.
To clarify, I'm only talking about code in the same project that everyone has access to. I'm not talking about defining an API for other people to use that don't have access to the code, like when you make a library.
116 comments
[ 5.1 ms ] story [ 170 ms ] threadit does not matter what you promise in the contract:
all observable behaviors of your system
will be depended on by somebody."
If your programming language supports reflection, people will depend on private properties and methods regardless of what you do.
Then, you will probably divide up the code into a number of separate methods for better structured code. All the methods that are purely internal and not part of the public API should be private if only to enforce the public API, this is also the encapsulation principle.
I know what _underscoring does in Python and what PEP8 recommends but doing it all the time for everything is so ugly and unnecessary.
Making variables and methods private means making them inaccessible outside the class and I gave the typical reasons/best practices for that.
The point they are making is in python there's no private method, everything is public. There's only a convention of "avoid using anything starting with "_"
From the linked page I read: "Encapsulation is used to hide the values or state of a structured data object inside a class, preventing direct access to them by clients in a way that could expose hidden implementation details or violate state invariance maintained by the methods."
So there are two possible reasons here:
1. The access could "expose hidden implementation details". This is not really a reason because it's just saying that you hide something to prevent access in a way that can expose what you hid.
2. The access could "violate state invariance maintained by the methods". Let's say the class has two arrays, `vertices` and `edges`. The state invariance is that they need to have the same number of items (like you have in a polygon). Then why would you make them private? A possible reason could be that it's easy to forget to add an edge if you add a vertex. But why would that be easier to remember if you do it in a member function?
Re 2: Supposing that the two collections have to be the same size, this implies that they get extended at the same time. If they are public, then anyone can add either a vertex or an edge at will. But they are not required (by the interface, only by a verbal contract of sorts) to extend both at the same time. With private members, you present a public interface that ensures both collections are updated simultaneously (or "simultaneously", as a transaction both happen before the method/function/subroutine/procedure/whatever the fuck ends). This makes the class/module author responsible for controlling this invariant, and not the users. Which is the only sane option unless you really like broken contracts and duplicated code scattered throughout a code base.
2. First, remember that I am only interested in code that everyone has write access to so even if something is private, everyone can still change it. "This makes the class/module author responsible for controlling this invariant" - does that mean that private is mostly useful together with a rule that only one person (or a few) should change code that belongs to their class, so that that person can keep the invariants in their head and that way avoid breaking them?
2. Forget about people, it's about place. I don't care how many people alter a particular class or module. What I care about is how many places some information has to exist within the code and has to be maintained and synchronized as a result.
As a mostly-useless-after-CS101 example, consider a bounded stack. An implementation might have a number (perhaps variable) which describes its limit, an array backing it, and another number indicating where the current "top" of the stack is (in languages where arrays carry their size you don't necessarily need that limit number as a separate thing).
If you leave everything public then every user of this bounded stack could directly alter the backing array and change the "top" of the stack, artificially indicating that something had been popped off or incorrectly incrementing beyond the limit. The limit itself could be altered without actually changing the backing array. The backing array could be made smaller or larger without correspondingly changing the limit. All of that would make this data structure useless, because it would be in an arbitrary, likely invalid, state.
In order to preserve the invariants of the system (limit == array size, 0 <= top <= limit; as just two of them), you write routines internal to this module (class, whatever) that make sure that when one is changed the others are changed, or that changes are only valid (top can never be larger than limit or less than 0). If users are able to bypass these routines by directly accessing these three member fields of the data structure then to correctly use it they have to preserve all these invariants everywhere they use the data structure. The code is now scattered and contains many duplications. Again, this isn't about people, it's about places and the number of them.
If you want to change the internal structure of this bounded stack, you have to change every place that currently accesses the public fields. Or you can be a sane developer, use private fields and public routines that manipulate the state so that it's always in a valid state. Now when you change the internal structure you only have to change those public routines and the private fields, no other place has to be altered. Every use of this bounded stack will look exactly the same as before, just push and pop and some error handling for when the stack is full or empty.
So it's not to make it easier to change things? Otherwise what's the difference, in difficulty to change things, between using a member in 20 places outside of the class and 20 places inside the class? It's the same amount of code to change if you change the member.
> But no one outside the class/module can directly access it
Everyone can access all the code.
> those outside users will not be impacted
What do you mean that they will not be impacted? If I change the internal array and all the places where it's used, nobody might even notice it. Or is the assumption that one team can only change inside the class and other teams can only change outside of the class? Remember that we assume that everyone has access to the all the code.
2.
> it's about places and the number of them.
So again, is private a way to tell programmers to reduce the number of places they use the member in so that it will be easier to change it?
> Everyone can access all the code.
I don't care what everyone can access (in terms of altering on a filesystem and code repository). It's about what the code can access.
Yes. Everyone who can see this code can alter this code, who cares? What matters is that users of this class cannot, in a separate module/class, directly access those three fields. You cannot, after marking these as private, do this inside some other class or module: That's invalid code, and that's a good thing. top is only modifiable by the BoundedStack class's own methods, which ensures that the state is always valid (so long as the code itself is correct, this is a trivial example to test so that's easy to verify one way or the other).Using private, here, is moving a convention (like _ in Python or just a verbal agreement or a note in the documentation) to a compiler checked thing. It makes it guaranteed that users cannot (modulo reflection facilities in some languages, but that's jumping through hoops, but that's their problem when it breaks, not mine) access these without going through your intended interfaces.
>> those outside users will not be impacted
> What do you mean that they will not be impacted? If I change the internal array and all the places where it's used, nobody might even notice it. Or is the assumption that one team can only change inside the class and other teams can only change outside of the class? Remember that we assume that everyone has access to the all the code.
What I mean is that in the above, you can change the manner in which BoundedStack (or whatever the module is) is implemented and the interface remains the same. Users still have access to peek, pop, push, and whatever other methods you provided. What they cannot do is assume (you know what assuming does, right?) that the underlying representation is stable and bypass your intended interfaces. BoundedStack could switch to using a LinkedList for all the user needs to care, but if they assume it's an array backing it and have access to that array they might write something like this (even assuming they do everything as safely as possible so that this is a valid piece of data they're accessing, but in an unintended way):
Now let's suppose you used public fields and that currently works. Later on you decide to switch to a linked list. Hypothetical rationale: Users often create large bounds for the stack for exceptional circumstances, but have small stacks 99% of the time; a linked list ends up using a lot less memory because it doesn't allocate a massive, and mostly unused, array; access is sufficiently infrequent that the allocations are a non-issue. For some reason you left the name array in place, so the user's code still works as is, or even if you did change it they changed the name (or maybe you did when you applied the name change to the whole codebase, oops). Except that array[n] is now a linear operation and not constant time. Suddenly there's a massive performance regression because the users:1. Did something unexpected with the code (why the fuck are they using a stack if they want to access anything but the top?).
2. Assumed the internal implementation deta...
Yes that's what private does, it makes sure that you can't access something from any place in the code. The questions is why you should make this constraint.
> That's invalid code
Is private used to tell programmers that if you change this variable or call this function, the object can be in an invalid state?
> For some reason you left the name array in place
Is the reason here that private makes it easier to find all uses of something because it reduces the number of places you have to look in? Example, you change the meaning of a variable but forget to change the name so the compiler will not tell you about all the places to change. You can still change all the uses of that member in the class because those uses are easy to find and know about.
Making the list or the dictionary public opens you up to situations where consumers of the code modify either datastore independently and corrupt the consistency of the class.
Then one day you decide you want to aggressively refactor that class. Now you can because the interface can stay the same even though you could completely re-do the guts. Better still: all your tests will still work, as will your class documentation.
Admittedly, my opinion is mainly based around working mostly in C++, but every codebase I work in of significant age is a knitted castle of interlocking classes with poorly thought out accessors and private variables. While the vision of carefully encapsulating implementation details is nice, in practice the discipline required to do so seems unachievable.
Instead of using private members, if I want an interface I explicitly make an abstract base class interface and inherit from it.
This has two advantages: (1) unit tests are simpler when I can poke/prod the concrete internals because they're public. (2) I can make an "include" directory with the interfaces in it when I want to distribute a shared library and those interfaces will be more concise and less likely to leak all sorts of implementation details in the headers.
Speaking as a C# developer, I think the concept of classes as tiny programs with tiny APIs works very well.
Everything looked like this: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
Maybe it's better now but the Java/C# practice of shoveling largely empty classes around with an IDE isn't something I'd point to as a good example.
Now the Fortran code I've encountered, that's the stuff of nightmares (to be fair to Fortran I've also encountered good code, but there seems to be a generation of embedded developers who have no formal programming training that all adopted a style of Fortran inspired by Cthulhu).
I'm sad that I've worked with plenty of codebases that look exactly like that and I twitched a little bit browsing around. :)
Well the last time I regularly used C++ was Borland C++ 3.1 from 1992. I'm not sure you'd be interested in my opinions on the language from 30 years ago.
I think that compared to the giant hairballs of absolutely unmanageable spaghetti classes when done right have made larger scale projects tractable to mere mortals and have significantly improved software maintenance.
Classes done wrong (hundreds or even thousands of classes that all do almost nothing) are a terrible anti-pattern and should be avoided.
The exact same thing goes for services. And in fact for any tool used in a really bad way.
While not much difference in C++ specifically, but having a distinction between a class that encapsulates state and a record/struct that is nothing but plain old data is essential in my opinion. Both are useful for different problems.
I lived the past 10 years in a ruby codebase where since effectively everything was public, we didn't mark enough stuff private and you can always YOLO monkeypatch or call things with `send(:method)` there was zero effective encapsulation.
And that pretty much crippled the product because now everything could be a public API so you can't change some shitty little helper to clean the code up somewhere because someone might have written code against that, in their private codebase that you have no visibility at all into. Every change becomes a potentially breaking change, because you have no idea how someone else's codebase might or might have fucked you by digging into your codebase.
Rejecting encapsulation is literally the dumbest post-OO take, and I have the scars to prove it.
Good fences make good neighbors and all that.
That's optimistic. I suspect there are some people who would be confused by your statement if they've been exposed mostly to tests that verify the steps taken by the implementation.
In any case, it's not good. Also not uncommon.
Fair enough.
Typically this is a case of people not really getting what unit tests are for, so they end up testing the implementation rather than the behavior.
Yes, that's what private does. My question was more why you should do that.
> the interface can stay the same even though you could completely re-do the guts.
So is the reason to use private that it's a way to tell programmers to not use something in too many places because that will make it hard to change in all those places if you need to change the private member?
Think of the interface to your class as a contract: this is how this class works, from now until eternity (or until the next breaking change ;) ). Relying on implementation details breaks that contract, the contract is on the interface not on the guts.
It won't be 'hard to change' it may be impossible to change. Because someone that relies on your class may not have access to the code of the other class!
Do you mean that if someone changes a class member, they would not change in all places that use that class member? Why not?
> someone that relies on your class may not have access to the code
As I wrote in the clarification, I'm only interested in cases where everyone has access to all the code.
You really think that in the 50+ years of OOP and modular programming no one has considered why? That they've really just thrown shit at the wall and seen what stuck and never considered what to throw at the wall or why it stuck?
We do it because it’s largely automated by IDEs now. I strongly suspect that if IDEs didn’t make it so easy to do, we basically wouldn’t bother with it. Python does OOP just fine without real visibility control.
Let's say I have a Motorcycle class with 2 Wheel members. What if in the code I assume 2 Wheels when writing the serializer? And then someone comes along, instantiates a Motorcycle and nukes a Wheel? The code will break.
You can make an argument that OOP is not what you want, and instead create something akin to namespaces+data, and that's fine. But OOP's thing is to create a public API, and controlled state behind it.
I used to believe in information hiding, but not anymore.
Within reason, you should treat every class as a tiny library that defines an API for other people to use.
That allows you freedom to change things within the class without breaking other code. It also makes it easier to prevent accidental misuse of the class that can leave it in weird states that aren't supposed to happen. It allows you to reason about the intended use cases of the class and write tests for them all.
Even within a single module of code, your public interfaces are "APIs". If I write a class that displays a paged interface in a popup modal, there is no relevant difference between a member of my team utilizing that class (from within the same module) and other people using it (as a library I have published). They both read the documenting comments and access the public members to achieve their goals, and both will suffer from the same confusion if there is no way to tell which members are the interface and which members are implementation details.
The difference between a team member and a library user is that the team member works in the same project while library users have their own projects. So if you change something inside the class, you can change all the uses of that class in your project but you can't change other people's projects. That's why I added the clarification, because there is a very obvious reason to be strict about the API of a library that other people use: you can't change those uses.
If the field is mutable then obviously you may want to keep it private to ensure some invariant, or to cache/invalidate/log something when accessed. Accessing the private data directly will circumvent that.
If the exposed data isn’t the same as the stored data (e.g a DateTime struct might have a field with a 32 bit Unix timestamp but only exposes year/month/day/hour…). The reason for not exposing it because you don’t want consumers to depend on it. If you changed to a 64 bit internal representation you will break any consumer who is using the private field. But if you only expose year/month/day/hour/… then you can solve your year-2038-problem by simply changing the data type. Anything exposed is depended on. Always. And here is the kicker: it doesn’t matter if your consumers are other classes in the same program, or library users for a shipped api, they are users and they will (mis)use your class in any way possible. In the library api case you break others’ code. In the internal code case you might get a missed cache, or a harder refactor or similar.
If you can see the use in access modifiers for libraries then it's not so hard to make the leap to its general usefulness. Defining objects with clear and simple APIs is extremely valuable when working on complex software projects. As the code base inevitably grows to a point where no one knows every part of it, you want developers to be able to contribute without having intimate knowledge of everything. The value proposition is the same as for third-party libraries.
The only time I break this rule is for data-transport type classes. For example if I serialize some object into some class X before sending it to some API. Since all those objects do is hold data, I see no point in making the members private. Many people do, I suppose out of habit; or in anticipation that maybe one day those objects will do more than hold data. I'd like to hear a good argument for making members private in data-transport type classes.
Changing private things and nobody might ever know.
It's about the need to coordinate and communicate changes and avoid people relying on unspecified behavior.