NHacker Next
login
▲Public/protected/private is an unnecessary featurecatern.com
68 points by PaulHoule 3 days ago | 91 comments
Loading comments...
jonathanlydall 15 hours ago [-]
I primarily work in C# and access modifiers allow me to constrain which types or their members on it are accessible to other types or assemblies.

This is particularly useful when authoring libraries as I know that for anything not public, I can refactor to my hearts content (provided the externally observed behaviour is otherwise unchanged).

It’s a bit of a pity that it was only relatively recently that Visual Studio changed the template for new classes to use internal instead of public.

There are lots of public classes in codebases I work on which are probably only public due to the old default and not used externally so could be refactored, but it’s hard to be sure.

Whereas with internal classes I can be sure (unless someone has used reflection, but then that’s their problem).

4 hours ago [-]
6 hours ago [-]
nick_ 12 hours ago [-]
Same here. I recently came across a real use for the newer `private protected` (`FamANDAssembly`) modifier combo. Only derived types that are internal to my assembly can access this member. Beautiful. It re-confirmed my long standing belief that OOP is an incredibly powerful and valid pattern.
kmeisthax 8 hours ago [-]
I'm assuming this is akin to Rust's pub(crate) keyword?
dustbunny 5 hours ago [-]
It's telling that you worded this as a writer of an API, not as a consumer.

Note that only a consumer of your apis can evaluate how well designed they are.

rk06 4 hours ago [-]
Yeah, I am sure that some fo the users may have higher skill set than authors. But majority of users suck at designing. And would add more fuel to the problems. And any refactoring efforts
kevingadd 15 hours ago [-]
I also wish 'sealed' was the default for classes in templates. Usually you're not thinking about what will happen if someone shows up 5 years later to derive from a random class you created, and it has performance benefits to seal a type. You can always remove the sealed qualifier later on purpose.
bdangubic 5 hours ago [-]
make everything immutable and then it doesn’t matter :)
dwelch2344 5 hours ago [-]
Tell me you haven't written a large library for a wide array of users (many of which you wouldn't trust with a potato peeler) without telling me
cherryteastain 14 hours ago [-]
I agree that it's unnecessary, and Python's success despite not having private members/fields is testament to that. But public/private fields/methods, like pretty much everything in OOP, is a 'social' rather than engineering innovation. Just as a class tells you certain operations and data are inter-related, private methods or fields signal to a class's users that you probably will break things if you interfere with certain parts.

Otherwise, at least in C++, you can often bypass the private specifier without much difficulty. Perhaps the laziest and easiest way to do so is

    #define private public
    #include "foo.hpp"
    #undef private
jjmarr 12 hours ago [-]
You should safely shoot yourself in the foot with `friend`, which is a core language feature for accessing private members/functions/etc.

https://www.cppreference.com/w/cpp/language/friend.html

Redefining public as private can violate the ODR rule and triggers undefined behaviour because private members are ordered in memory in an undefined way, while public members are ordered in a standard way.

WalterBright 9 hours ago [-]
#define duck_season wabbit_season

#include "shoot.hpp"

n2d4 13 hours ago [-]
Python absolutely does have private members. If a member starts with two underscores, its class name (and an underscore) is prefixed to the member name, essentially resulting in the same behavior as a private modifier. (Although there are ways to circumvent this in Python, there are also ways to access private fields in most other languages.)

But even if that were true, that wouldn't mean that accessibility modifiers are unnecessary in other languages. Python is a certain language with certain idioms, and other languages are more or less OOP and hence some constructs that might not make sense in Python definitely make sense elsewhere.

_ZeD_ 5 hours ago [-]
No, it doesn't: double underscore field names are mangled basically to avoid easy overriding in su classes; keep in mind that it is a feature that predates super() in almost any form
masklinn 4 hours ago [-]
Not only are those not technically private, they were never intended for information hiding in the first place. Name mangling was introduced to avoid namespace conflicts in inheritance scenarios.
NeutralForest 13 hours ago [-]
I see what you mean what I wouldn't really qualify name mangling as private members, you're discouraged to access them but you can access them all the same.
jjice 12 hours ago [-]
I'd consider it the same, personally. It's a social contract that you can absolutely break if you want, just with a bit of work.

Less work in Python than it is Java, but there are ways to get to them in Java, or hell, just modify the source and change it directly. By doing that, you're doing something the developer didn't intend and you shouldn't be surprised if it doesn't work at some point in the future. Same as I'd expect in the Python case.

NeutralForest 12 hours ago [-]
Sure, I think the bar to modify is higher in Java for example but I can't really find fault in what you're saying because I'd never unknowingly modify a member with double underscores in Python.
throwaway2037 5 hours ago [-]

    > but you can access them all the same
Can't you do the same in C++ using struct offsets and reinterpret_cast<>? I'm not sure what you are saying here.
NeutralForest 2 hours ago [-]
I'm not familiar with C++ enough to know about circumventing `private` but in Python you can access "private" members just like any other "public" ones, with the dot notation. There's no specific enforcement at the language level, no error will be thrown.
TylerE 9 hours ago [-]
That is absolutely a social construct and not a language feature. You can override “private” members just fine with a simple assignment.
jmmv 4 hours ago [-]
I’ve seen a ton of this “#define private public” in a previous team that was forced to write unit tests in a rush due to the whims of leadership. It hurt to see, and the resulting white box tests were useless.
ethan_smith 7 hours ago [-]
The #define private public hack doesn't work reliably in modern C++ compilers and violates the One Definition Rule, potentially causing undefined behavior and hard-to-debug errors.
CodeHz 5 hours ago [-]
Actually, it won't compile in msvc (the linking phase, to be precise), MSVC uses different names for methods with different visibilities
leni536 12 hours ago [-]
Don't do that in C++. Maybe unless if you are debugging. This can blow up in your face in unexpected ways.
ctxc 15 hours ago [-]
Do you really want to create an interface for every possible place where you'd use `private` though?
WorldMaker 15 hours ago [-]
Right, even if it is just syntactic sugar for a "pure" dual of `interface` as the article posits, syntactic sugar still has advantages/merit in many scenarios.
ctxc 15 hours ago [-]
Exactly. Ime most classes are not designed to be inherited but want to expose a clean public API.

While true that all classes can be inherited, treating _all_ classes with the assumption that they will be would just lead to a labyrinth of code.

coderjames 13 hours ago [-]
> most classes are not designed to be inherited but want to expose a clean public API.

> While true that all classes can be inherited

That's what the 'final' keyword in C++ is for: https://en.cppreference.com/w/cpp/language/final.html

"Specifies that a virtual function cannot be overridden in a derived class, or that a class cannot be derived from."

WorldMaker 13 hours ago [-]
C# and Java both have `sealed` for that, too.
throwaway2037 4 hours ago [-]
Nitpick: It is called "final" in Java, but your point stands. As I understand, C++ borrowed their "final" keyword from Java/C#. I'm not trolling when I write that last sentence. That is something that has really changed about C+ since the C++-11 committee was started -- and continues. When people write language / library proposals, they now regularly survey the landscape and talk about their experiences with other languages / libraries.
orwin 2 hours ago [-]
Ideally, yes in my opinion. In the real world, I don't have time to design my code that well.
kazinator 5 hours ago [-]
In Common Lisp, we sort of get this by the separation of exported versus unexported package symbols.

Unexported package symbols do two things:

- when visibility "windows" are created between packages via the "use" mechanism, unexported symbols are excluded.

- unexported symbols require tokens with two colons: you have to write foo::bar, not just foo:bar, to refer to bar if it is unexported from package foo; moreover you must still write foo::bar even if you are using package foo, whereas if it were exported, you would just write bar.

CLOS uses symbols for naming entities like generic functions, classes and slots. It has no additional protection mechanisms beyond what the symbols provide, and symbols are agnostic of classes (and everything/anything else which they name).

rerdavies 12 hours ago [-]
Pretty much all language features save for a shockingly small set are "unnecessary". The proper question is "are they useful?"

The usefulness of public/protected/private is that it provides a way to narrow the surface area through which derived classes can interact with their superclasses. Which is enormously useful from a maintenance perspective.

The place where inheritance is better than composition is where base classes provide partial implementations that are used by derived classes.

baobun 15 hours ago [-]
In case one is not familiar with the author, they are commonly arguing that inheritance is an antipattern that should not be used in OOP languages. The OP is in that context: private and protected are not useful if you don't have inheritence.
orwin 2 hours ago [-]
Ideally, at least in python, 'true' inheritance should be used in very specific cases, and most of the time, you should only 'inherit' mixins and interfaces.

In practice? abstract classes all the way down to technical debt hell.

IshKebab 14 hours ago [-]
I don't think that's really true either.
simoncion 6 hours ago [-]
It's not true at all. Even Erlang (very much not an OOP language) has public and private methods, with the default being private.

If you don't have a mechanism to indicate which parts of your software's API are internal implementation details and which parts are intended for use by users of that software, then you're going to have to rely on convention. The problem with convention is that no ten people do it the same way... which increases cognitive load... which is to be avoided in a field that has more than enough little things to keep track of already.

deredede 4 hours ago [-]
> The problem with convention is that no ten people do it the same way

"Stuff that starts with one underscore is an internal implementation detail, use at your own risk" in Python is as close to a universal convention as you can get.

PaulDavisThe1st 14 hours ago [-]
I'd like them extended slightly, in fact. I'd like a friend declaration after an access specifier to apply only until the next access specifier. Eg.

     class Foo {
       public:
          void method_A();
 
       protected:
          friend class Bar;
          void method_B();

       protected:
          friend class Baz;
          void method_C();

       private:
          void method_D();
     };
anyone can call method_A(), only Bar can call method_B(), only Baz can call method_C() and nobody can call method_D().

You can do this by inheriting from 2 different pure abstract classes, but that feels much kludgier to me.

hhhAndrew 6 hours ago [-]
This is how Eiffel works. Instead of private, protected, public, you specify the set of classes a method can be seen by: `feature {ANY}` for `public:`, feature {NONE} for private, feature {Self} (IIRC) for protected, feature {Foo, Bar} for descendants of Foo and those of Bar, etc.
leni536 12 hours ago [-]
Look up the passkey idiom.
PaulDavisThe1st 11 hours ago [-]
Nice, but cumbersome with > 1 friend.
Dwedit 16 hours ago [-]
Locking away backing fields from a property is not an unnecessary feature.
scotty79 15 hours ago [-]
If somebody needs access to the backing field it's usually for a good reason. And if you restrict it, they'll just fork your code and overwrite whatever they need.

It should at most be a soft annotation to prevent it from getting auto-completed, not a restriction.

derefr 14 hours ago [-]
You're assuming a very simplistic model of API coupling, where there are only "applications" and "libraries that the application compiles into itself." But:

• There are also runtimes, installed separately from the application. You might be able to fork the JDK, but people want to run your Java application on their JRE, not yours.

• There are proprietary applications that nevertheless expose APIs to code against. A Photoshop plugin author can't fork Photoshop to expose fields of the plugin host.

• There is the OS itself. You can fork the Linux kernel (and gcc/clang) to expose some internal data your application needs as a system call — but unless you get all that code upstreamed, your application is now entirely non-portable, running only on your own servers at best.

• Most commonly of all, there are system libraries. You can certainly fork (and co-distribute / bake in) a library like libicu or libxml — but no major OS will be willing to ship your app or lib as a system package of its own, if it does that. They'll want your app/lib to depend on the existing packaged version of those libs.

None of this matters, of course, if you develop proprietary-service backend software for a living. You can maintain a menagerie of weird lib forks, shove all the build artifacts into a docker image, and it'll work for you. But much (I'd say the majority) of the world's programming is not in proprietary-service backends, and so needs to actually depend on code it doesn't control.

scotty79 11 hours ago [-]
All very good arguments about not having private and protected.
demosthanos 7 hours ago [-]
> And if you restrict it, they'll just fork your code and overwrite whatever they need.

More power to them. They can take responsibility for that code and maintain it and I don't have to worry about breaking them when I release a new version. Everyone's happy.

magicalhippo 15 hours ago [-]
I like Boost's approach[1] of using a separate namespace for these implementation-specific things, rather than visibility modifiers.

Then the internals are accessible if you need it for a temporary workaround or such, but it's also very obvious when you are accessing stuff you really shouldn't. And implementers are still free to rearrange the internals as they see fit.

[1]: Not saying Boost invented it, just where I saw it first.

rerdavies 12 hours ago [-]
Boost uses both. Implementation-private namespaces are a slightly different feature. They provide a way to share internal methods between classes within boost that must not be used by client code. That's a scoping feature that c++ doesn't have, but c# (for example) does.
NBJack 3 hours ago [-]
And now they've introduced a tight coupling and some serious potential tech debt should the original ever change its internal workings.

Also, you seem to assume we're talking about open source here.

tenebrisalietum 15 hours ago [-]
and then they also own future responsibility for that change.
13 hours ago [-]
scotty79 11 hours ago [-]
It's always app dev responsibility. They can't excuse a bug or lack of functionality with "that's what's in the lib".

I can't imagine somebody monkey patching exposing internals of a lib and blaming lib author for poor effects.

simoncion 6 hours ago [-]
How are they to distinguish between what are internals and what is the public interface if the internals aren't locked behind something like 'private'? Naming convention? [0] Code comments, or other documentation? A thorough reading of the codebase?

[0] Good luck with enforcing the One True Naming Convention.

1 hours ago [-]
ReptileMan 17 minutes ago [-]
https://steve-yegge.blogspot.com/2010/07/wikileaks-to-leak-5...

Brilliant steve yegge article on the topic.

Wikileaks To Leak 5000 Open Source Java Projects With All That Private/Final Bullshit Removed

On a more serious note. While I can see the reason for private members, the case for private methods has always been much weaker.

aurumque 13 hours ago [-]
Access modifiers don't give you unlimited protection, they help you reason about the code and how it is used. If I change a public method and break something downstream, that's on me and my team. If I change a private method and break someone else's code which was bypassing using reflection or other mechanisms, that's on them.

Languages like Python don't have keywords but enforce using conventions like underscore and 'dunder', the latter of which is actually enforced using obfuscation. It is extremely helpful to signal to users that they should not be using certain methods or fields. When there are no access modifiers available, we still see teams writing methods like "xyx_UNSAFE" or "abc_DO_NOT_USE". It's ridiculous to me to encode this kind of information like this instead of having first class support in the language.

Access modifiers also tell the compiler or JIT what is safe or unsafe to manipulate. It has implications beyond users, and can be a determining factor when inlining or making other optimizations.

This article is so opinionated (edgy?) that it ends with "Inheritance was a hack in the first place." I think, like all things, we need to consider the broader landscape for object oriented programming instead of just saying these things are irrelevant and absurd and useless.

NBJack 3 hours ago [-]
Please don't fall for the clickbait here. Take a look at the history of posts from this site.
julik 13 hours ago [-]
For most modern languages, something like an @internal annotation would work better. With compiled languages that need to conform to an ABI or export RPC definitions, there is merit to specifying what exactly must be exported (or must never be inlined), but even that could be tackled by an @internal or @nodoc or @private annotation - not a language construct.

See also https://steve-yegge.blogspot.com/2010/07/wikileaks-to-leak-5...

rerdavies 12 hours ago [-]
That would be fine if the annotations were standardized. But if the annotations are standardized, then they might as well be a direct language feature.
fc417fc802 7 hours ago [-]
It's a bit different. You'd expect to be able to have your tooling ignore the annotations and everything should "just work" which eases debugging. Annotations are generally expected to be useful for things like linting, optimization, and documentation (ignoring language extensions that change the semantics of course).
simoncion 6 hours ago [-]
> You'd expect to be able to have your tooling ignore the annotations and everything should "just work" which eases debugging.

I'd expect any language that uses annotations to have a fairly-complete reflection capability. I'd also expect a good debugger to be able to use that to permit me to read, modify, and call methods and members marked as 'private'.

It has been decades (so perhaps my memory is incorrect), but I remember being able to access private parts in C++ debuggers. If this was true way back when for C++, I expect it to be a common feature now in debuggers for languages that have reflection & etc.

fc417fc802 5 hours ago [-]
> in C++ debuggers

An awful lot of my debugging is done without a debugger. I generally expect a language to permit me to do anything that can be expressed in a logically coherent manner even when I would clearly be shooting myself in the foot. I also expect a bright neon sign warning me not to proceed.

I quite like python in that regard. You can monkey patch just about anything even when you really shouldn't.

throwaway2037 4 hours ago [-]

    > An awful lot of my debugging is done without a debugger.
I would like to hear more about this. What are the alternatives? printf? To me, nothing beats a good debugger.
fc417fc802 22 minutes ago [-]
Adjacent comment pretty much explained it. Sometimes it's printf, sometimes it's subtle tweaks to program behavior in strategic places, sometimes it's adding asserts (that might very well call functions not intended for public consumption).

I always start debugging by coming up with a theory about why the observed behavior occurred. The fastest way to validate that is generally one or two tiny changes to the program.

I only break out the debug tooling for the serious stuff that has me scratching my head in confusion. Either because the behavior has me thinking "there's no way that's possible" (spoiler: almost always memory corruption) or because I'm segfaulting in a shared library that I didn't compile or because I'm well into some obvious badlands with concurrency or memory corruption or the like.

bmandale 4 hours ago [-]
Suppose we can compile and execute our code at such a speed that a given line of code will be executed the very instant after we press run. In this case, a choice printf line will execute at the same speed as typing print in our debugger. The benefit of the printf line is that we are in effect scripting our debugger to print some data every time that line is reached, without having to type it over and over. For one thing, this allows us to more easily go backwards in the execution, since we are really rerunning the program from scratch each time. We can also much more easily execute a whole litany of prints, which we can then scan for some expected data. The advantage of the debugger remains in the first place in languages that lack strong compile time reflection, such that only the debugger can reliably print the data we want to see, and in the second place in the myriad instances where we cannot get the compile+execution speed down to an acceptable time. I suppose debuggers are also useful when you have a core dump you want to analyze. Besides that I would consider the printf strictly superior.
simoncion 5 hours ago [-]
My point is that even C++ (a language that somewhat-famously lacks built-in support for reflection) has tooling to access and run non-public parts of the program.

"I don't like making functions, methods, and members nonpublic because how would I access them when debugging?" simply isn't a credible complaint when applied to most mainstream languages.

aryehof 5 hours ago [-]
Of course they are unnecessary, but then strictly speaking control statements can be omitted in favor of goto statements. Are they unnecessary?
cryptonector 4 hours ago [-]
We could write only in the untyped lambda calculus -- it has everything you need...
EPWN3D 7 hours ago [-]
Honestly, subclassing strikes me as mostly unnecessary. Most of those use cases can be addressed with protocols or traits.
default-kramer 6 hours ago [-]
I strongly prefer FP over OOP, but... Is there any desktop GUI library that isn't built on subclassing? Winforms, WPF, Swing, Qt, Godot all do. This doesn't prove that subclassing is necessary, but it sure seems like the best approach we've found so far.
dragonwriter 6 hours ago [-]
> Is there any desktop GUI library that isn't built on subclassing?

There are desktop GUI libraries implemented in C, Go, and other languages that do not have subclassing, so, the answer kind of has to be yes.

throwaway2037 4 hours ago [-]
Gtk+ is pure C, but the library essentially implements subclassing manually. It is so painful to use, and so much worse that the C++ equivalent in gtkmm. Again, Win32 API does similar to allow subclassing of widgets. Again, painful.
Spivak 7 hours ago [-]
Welcome to the Python community's "subclassing is only for sharing code, user-beware if you try to use it for anything else."
14 hours ago [-]
cosmicgadget 14 hours ago [-]
"Because you can limit visibility with interfaces, limiting visibility without using an interface is unnecessary."

No thanks. And I'll continue using inheritance and composition correctly too.

alganet 14 hours ago [-]
> Use composition instead. Inheritance was a hack in the first place.

Composition is great. It's the preferred way.

However, if you take it to the extreme (composition only, never inheritance) you sometimes end up with really weird meaningless objects that have only a single method and a one-to-one relationship with another object.

Private methods are ideal for making those abstractions easier to understand. A private method, therefore, is equivalent to an object that has a one-to-one relationship with another object (it's only composed there) and a single method.

A protected method is similar, but allows whoever subclasses it to replace that single one-to-one "mini object" inside with another one-to-one "mini object".

When used within this mindset, it greatly simplifies code. It lets you simplify a branch that has only a single leaf into something easier to read.

Together with classical refactorings (extract method, move method, extract class), they're a precious tool.

Of course, typing is a consideration as well, but I'm talking exclusively about their role in granularity control.

It is unecessary, but convenient. It can be misused. Composition can also be misused.

tadfisher 13 hours ago [-]
> However, if you take it to the extreme (composition only, never inheritance) you sometimes end up with really weird meaningless objects that have only a single method and a one-to-one relationship with another object.

We can take a cue from the functional languages and make this less code-smelly by using anonymous function types (e.g. lambdas). This removes the one issue with single-purpose/single-method objects, which is naming them.

alganet 12 hours ago [-]
It's an extra tool, for sure.

There is no silver bullet. No one likes huge inheritance chains, no one likes only tiny lego pieces, no one likes callback hell.

khalic 13 hours ago [-]
Ah the good old “xxx considered harmful”
m3kw9 5 hours ago [-]
Is so the IDE doesn’t show up stuff when private
cyberax 13 hours ago [-]
IMO, the Go model of package-private and public entities is the best compromise. Simply because it reduces the global namespace pollution.
devjab 4 hours ago [-]
I've worked with a lot of languages over the years, Java, C#, Go, Typescript, C and Python have been the main ones. These days everything is becoming Python around here... well with some C/Zig when needed... and the one thing I miss from any language I've worked with before is the Go model of package-private and public entities.
rerdavies 12 hours ago [-]
Package-private is a nice feature.
xyst 15 hours ago [-]
They are also optional in some languages :)

A long time ago in Java corp world, teams would create their own integration libraries with suspicious/horrible contracts. Ended up just using Java’s reflection api to update access modifiers to get their shitty library to work until they fix it (months later…).

deepsun 15 hours ago [-]
Or introduce a corporate rule to make everything public, if restricting didn't work anyways.
dboreham 9 hours ago [-]
Quick note that inheritance existed in assembly language programming (and programs written using the front panel switches if you want to take things to the limit) and were not "invented" in Simula or C++. Those languages invented syntactic sugar for techniques that were quite mature at the time both in assembler and older HLLs such as BCPL and C.
lowbloodsugar 13 hours ago [-]
Also the static members of a “class” are another interface: a distinct interface that also enforces a singleton.
curtisszmania 6 hours ago [-]
[dead]
ysofunny 14 hours ago [-]
now say it in a sociology or economics conference
jimbob45 15 hours ago [-]
I wish private was a suggestion and not strictly enforced in the same way that types aren't strictly enforced in C. Microsoft loves to make public variables private years during upgrades and it can be impossible to rewrite your application to avoid using that now-inaccessible variable. Looking at you, ASP.NET to .NET Core upgrade...
kevingadd 14 hours ago [-]
Have you heard of Unsafe Accessors? https://learn.microsoft.com/en-us/dotnet/api/system.runtime.... They help solve this problem.
ziml77 14 hours ago [-]
Oh that's awesome that they added that. Ending up needing to access private members already sucks, but reflection makes it even worse due to the performance hit you take.
wewewedxfgdf 13 hours ago [-]
All private does is make it hard for developers further down the track to make changes that they want to make.

And if you don't need private then why have public?

rerdavies 12 hours ago [-]
What private does is make it easier for developers further down the track to fix bugs, and prevent consumers of a class from making horrible mistakes by introducing dependencies on implementation details of a class that are not guaranteed to exist in future versions.