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