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