
The Case Against OOP Is Wildly Overstated - mpweiher
https://medium.com/young-coder/the-case-against-oop-is-wildly-overstated-572eae5ab495
======
lgessler
This doesn't speak to what I consider some of the most dangerous parts of OOP,
which include the assumptions that tightly binding data and code is helpful,
and that statefulness is fine to freely sprinkle throughout your program.

~~~
tgflynn
So what is your preferred solution for programs that intrinsically need to
deal with a lot of state ? Global variables ?

I used to see that type of approach in some complex Fortran simulation
programs where every function referenced a huge common block. I really don't
think that leads to easier to manage solutions.

~~~
lgessler
Essentially, yes, but in a way that sounds more careful than what you saw in
that Fortran program. If it absolutely has to be stateful, like with a db
connection for example, then there's no getting around that, but what you
_can_ do is (1) not bury it in a bunch of stateful objects that didn't have to
be stateful, which can cause cascading errors when either one of them
misbehaves and also makes it difficult to isolate any one stateful component
for testing or re-use, and (2) use special wrappers around it that will make
the statefulness easier to deal with. (For examples of (2), see Clojure atoms
and the Clojure library mount:
[https://github.com/tolitius/mount](https://github.com/tolitius/mount) )

~~~
nostrademons
More concretely, for every bit of state in the program I'm interested in two
big questions, each with two subquestions:

1.) Who can _access_ this state, and which of those accesses allow writes vs.
which are read-only?

2.) What is the _lifetime_ of that state, and what portion of that lifetime is
mutable vs. read-only?

Java-style OOP can control #1, but makes no distinction between reads &
writes. C++ const correctness and the val/var distinction in
Kotlin/Ocaml/ES6/etc. can make that distinction, but only C++ forces you to
think in terms of lifetimes. Rust borrowing probably comes closest to
answering all 4.

I'm still waiting for a language feature that lets you say "This field of this
struct is only mutable while this module is running, and once it completes it
will never be changed", though. A lot of data structures are initialized in
passes and then passed along as constant data: you construct the basic object
structure with nothing but a few IDs, then you compute extra fields as
necessary, then you're done and the object is never written to again. If you
could keep the fields mutable during initialization (which may be a longer
process than just constructor calls) and then seal them off once that's
complete, you eliminate a whole class of bugs where a read-only client decides
to mutate a field that should only be mutated from designated initializer.

~~~
bpolverini
That would be really cool, but I think enforcing that is undecidable. My gut
tells me that having that language feature at compile time is the same as the
Halting Problem.

~~~
nostrademons
In its full generality it's undecidable, but there are probably restrictions
you can build into the type system to make it decidable. I was thinking
something along these lines:

    
    
      class Symbol {
        @init(constructor) originalToken: Token
        @init(constructor) position: SourceLocation
        @init(Parser.parse) scope: SymbolTable
        @init(Parser.parse) declaredType: Type
        @init(TypeChecker.typecheck) inferredType: Type
        @init(DataFlowAnalyzer.computeLiveness) usages: List<Expr>
      }
    

And then the type system carries an extra bit of information around for
whether a reference is fully-constructed or not, much like const-correctness.
Fields marked with @init can only be written on a reference that's not fully-
constructed. There's no compile-time enforcement for initialization order,
though it'd be pretty easy to do this at runtime (convert them to null-checks
or special not-initialized sentinel values). Newly-created references start
out with the "initializing" bit set, but once they're returned from a function
or passed into a function not in the list of legal accessors, they lose this
bit unless explicitly declared.

It's basically the same way "mutable" works (in languages that support it),
but with a separate state bit in the type system and extra access checks
within the mutating functions to make sure they only touch the fields they're
declared to touch. You can fake this now by passing mutable references into
initialization functions and then only using const, but it's a bit less
specific because many classes are designed to be long-term mutable through 1-2
specific public APIs but also need to be mutated internally for deferred
initialization, and lumping these use-cases together means that deferred
fields can be touched by the public API.

------
jmnicolas
Since I accepted I was no Einstein I embraced the KISS principle.

My code became simpler, easier to understand.

Of course it wouldn't get the approval of any software architect or any design
pattern fanatic, but I can take any code I wrote 3 years ago, understand it on
the fly and edit it with confidence. There are few bugs and they're never a
nightmare to find.

Now if I have to modify the code I was writing before my KISS revelation, when
I was reading too much about OOP and design patterns then the headaches start.
Often I'm left wondering why oh why I had to use so many levels of indirection
to solve what is a simple problem.

~~~
nfw2
Einstein himself would approve this philosophy. As he once said, "Everything
should be made as simple as possible, but no simpler."

Still, finding the simplest-possible solution to complex problems is often
challenging in itself, especially considering what is "simple" or "intuitive"
can be highly subjective. Engineers are often uncomfortable thinking about
these problems because the domain is shifted somewhat away from the realm of
logic machines and towards psychology and UX, in which they have little to no
education or experience.

~~~
Gibbon1
An idea I carried over from mechanical engineering is the idea of degrees of
freedom. If your solution has fewer degrees of freedom than the problem, it
doesn't work. If it has too many it's unconstrained and requires hacks to keep
it from doing something terrible.

------
clktmr
> The solution to the fragile base class problem and other inheritance
> hangovers is surprisingly simple — don’t use it

If OOP provides the footgun to shoot yourself, OOP is at fault. Overall the
article reads as "OOP has no problems if is used correctly". Of course that's
true. I have seen well designed OOP programs, but the majority was not. The
author should ask the question, why OOP is applied the wrong way so often.

~~~
Rickasaurus
The term we like to use is "the pit of success". It shouldn't be hard to do it
the right way.

~~~
beaker52
"The pit of success" is a great guiding principle when it comes to the design
of codebases. Forget about "design patterns" and just focus on _how do I allow
developers to focus on creating things that are adding value, rather than
faffing around with extraneous things_.

I can't tell you how many times I've seen a project go full onion-pattern with
a web service that basically just recieves HTTP requests and makes a few
itself and returns some data. Instead of a HTTP request handler that fetches
data, perhaps does some mutation and returns it, there's a whole "service
layer", a domain model, data fetching abstraction. To change something you
have to navigate the winding dependency graph and fight off all the deamons.
That's not the pit of success, they're the catacombs of failure.

We should design codebases so that future developers fall into the pit of
success, even if it means we haven't been able to show off our knowledge of
software architecture etc. The best pattern you can ever apply, is the one
that makes change easiest. If you make change hard, the code becomes
artificially stale -- I call it 'calcified'. Coupling makes code hard to
change.

Thinking about problems through objects, in my experience, encourages people
to make 'one model to rule them all', for example in a retail app, you might
have a "Product" which ends up having product information, media assets,
pricing, promotions, retail and stock keeping information etc which in a peice
of software undergoing 'rapid development', multiplies the likelihood of
coupling. Which is where Domain Driven Design steps in and tries to preach.
Thinking about your problem in terms of data, or events can often help you
find the right concepts to create your abstractions around. Another good
heuristic is to think about caching, can you cache pricing for the same length
of time you can cache product information, for example?

Bit of a ramble, but explains why I tend to shy away from objects, except for
where I have a bonafide business problem that needs to be modelled -- with
this, the models are only used to make the code make sense to developers and
help arrive at the result they produce, rather than the models being the
result themselves.

~~~
dclusin
For large interwoven software systems this complexity seen in the form of
service layers, directory services, distributed quorums, etc. is often
necessary to achieve availability guarantees. I think that the problem is that
engineers are over eager to design these sorts of things and implement them
before they're actually necessary. And they do it badly and make life shitty
in the process because they still don't fully understand the requirements for
such a system. OOP just happens to be the language feature they use to make a
mess. It would still be no fun even if they used a straight procedural
language like C.

~~~
kqr
Adding on to this, there's an important distinction described by Parnas back
in the 1970s as

> _Software can be considered "general" if it can be used, without change, in
> a variety of situations. Software can be considered "flexible", if it is
> easily changed to be used in a variety of situations._

We frequently forget that these are both valid paths to the same end. We tend
to laser-focus in on generality, at the expense of flexibility.

And for small problems like the one described, it is usually much easier to go
for flexibility at the expense of generality. _And I think we should._

------
skrtskrt
The author is right about ORMs. They are ridiculously over engineered
solutions. (My experience is largely with Django and SQLAlchemy ORMs)

As soon as you want to do something that's not already perfectly built in,
everything becomes a mess of impenetrable hacks. Autogenerated Django
migrations are unstable and often need to be edited to to be correct or make
any damn sense.

It also encourages to people to blur or just straight up stomp on the line
between ORM objects - tightly coupled to your database - and business-logic
objects (entities, usecases, whatever). It's the Django recommended approach,
called "fat models" and it leads to ridiculous levels of coupling and
impenetrable ball-of-mud code.

Just write SQL and marshal it into business objects. I'm actually quite fond
of doing this with the SQLAlchemy Table & Query APIs, they do most of the
marshaling for you.

In Go I just write the raw SQL.

More generally regarding OOP: I have never seen any good come from more than
one layer of inheritance.

~~~
dnautics
Always prescribing raw sql is dangerous: You typically want _something_
managing your sql queries if there's user input because scrubbing out SQL
injections is hard and it's not something you should be 'rolling your own'

~~~
winstonewert
I presume that when somebody says raw sql they mean are using parameters and
not just building strings.

~~~
C1sc0cat
and SPROCS

------
bussierem
The problem I've always had with these back-and-forths between various
programming philosophies is that the very nature of the argument exposes the
reason the arguments don't work.

The most common (but not only) way each side argues its point is to take some
contrived example and show how philosophy X does it well, and how philosophy Y
does it terribly. These examples are often ranted about by the "Y" proponents
because "it's a bad problem choice designed to make Y look bad".

Yes. It is. Because X and Y were not designed to solve the same problems. If
you pick an example Y is good at, most of the time X will be bad at it. So if
you're doing something like that, use Y. But if you're doing something X is
good at, then pick X

Why would you intentionally shoot yourself in the foot to use a framework,
language, philosophy, etc. that was never intended to be used for that thing
when there's something that was? Stop arguing about who's better at what. X
and Y are good at different things. That's why they're _different
philosophies/languages/technologies_.

~~~
PaulDavisThe1st
So let's say I've decided to sit down and write a digital audio workstation.
I've got to pick at least one programming language and at least one general
programming style. Let's say I pick C++ and (somewhat by implication) a
generally OOP style.

Someone comes along and says "oh, you could do this much better with Haskell
and functional programming".

How can you argue that the comparison involves an "X and Y" that are not
designed to solve the same problem? Advocates for languages and programming
styles are generally doing so on the basis that there is a broad class of
software development problems/projects that will benefit from the use of their
preferred "X and Y".

Sure, there are some DSLs that are clearly not intended to be compared with
(say) C++. And there are some overall programming styles that clearly suite
certain kinds of software much more than others (e.g. the absence of an event
loop somewhat changes everything, as does high level distributed parallelism).

But OOP isn't an example of such a thing.

~~~
jancsika
> How can you argue that the comparison involves an "X and Y" that are not
> designed to solve the same problem?

Because Y is Haskell and your project has soft-realtime scheduling
constraints.

But if Y were Rust, point taken.

------
identity0
The author flatly says "do not use inheritance." Without inheritance and
polymorphism, what is left in OOP? If you look at the author's 4 pillars, the
only thing left is encapsulation (and "Oversimplified Hot Takes") It seems
like the author himself has proven why OOP is bad. You can have encapsulation
without OOP. People were doing it in C 40 years ago. And people are doing it
in Go right now. No one would call Go an OO language, yet it has what the
author admits is OOP's only good feature.

~~~
AnimalMuppet
Yes, you could do encapsulation in C. You'd have a struct. But the problem is,
any function in the whole program could modify the data in the struct,
possibly placing it in an invalid state. When the struct wound up in an
invalid state, you had to look at the entire program to find out who did it.

(C++ style) OOP encapsulation is different. Only member functions can modify
the "struct" (object) data (unless you did something crazy like make your data
members public). If the data is in an invalid state one of the member
functions did it. Nobody else had the ability to do so. Your debugging got
easier, because there's a much smaller set of places where the problem could
be.

Now, true, you could do that in C, with the structure declared only in one C
file (and no header file), and all functions declared file static except the
"public interface". But C++ make that the normal way to work, not something
that you had to go out of your way to do.

Inheritance: If you have a problem where it adds value, use it. It's a tool,
not a religious dogma (either for or against). You could argue that most
people, when they think they have a problem where inheritance adds value, are
mistaken. You could even be right. But _never_ use it? It's _always_ the wrong
choice? Get outta here. I'll use it when it helps - when the shape of the
problem calls for it.

~~~
identity0
I never said encapsulation was easy or obvious in C, just that it's nothing
new or inherently OOP-based. I also never said that you should never use
inheritance, I was just quoting the author, who, in his "defence" of OOP
admits that OOP's only unique feature should not be used at all.

------
tempodox
IMO, OOP just puts too many footguns at your disposal, the most dangerous one
being mutability (how normal is it that our methods mutate instance
variables?). The larger a system grows the more mutability will make it even
harder to understand and control. It may be tolerable in small-scale embedded
software, but outside of that we should make use of the better alternatives
that modern hardware affords us.

~~~
PaulHoule
I knew a professor who worked on Density Functional Theory which can be used
to derive many of the properties of materials from first principles. (e.g.
"Does Iron Conduct Electricity?")

This involved running FORTRAN programs on what was then considered a large
supercomputer (256 nodes.) Asked what he thought about any programming
technique that cost a factor of 2 in performance and he told me it was a non-
starter when he ran jobs that took a month.

Back when Hadoop was popular, the "cutting edge" of the thing was the system
that deserialized and serialized data and some of the major ways to improve
performance are: eliminating copies, eliminating memory allocations, etc.

I told Rich Hickey the same thing I was told, that even though the performance
difference isn't much in the grand scheme of things, people who have high
performance needs are going to dismiss whatever he brings to the table if it
costs them a factor of two.

Interestingly, Hickey responded the same way the professor did: just as the
professor dismissed the value of the 2x slower but more maintainable system,
Hickey just seemed to dismiss the value of "2x faster".

I see this a lot where people just don't communicate, even when they pretend
they are.

~~~
eternalban
HPC obviously prefers higher performing approaches, but how is this anecdote
relevant to general programming community?

The position that x2 "isn't much in the grand scheme of things" is a
reasonable position. Note the "grand scheme" qualifier. Also the implicit here
is "we trade performance for ---". The professor was making a sensible
statement about tradeoffs when considering 'general programming approaches'.

Your last line also reads like a potshot at Rich Hickey.

I think the _actual_ failure of OOP is that it did -not- result in the desired
outcome of addressing the "software crisis" [1] and the significant imbalance
between the scale of required (new) software and available human resources to
write these systems. A subset of programmers can use OOP to build very
effective systems; the rest shoot themselves in the foot. That is the failure
of OOP.

But I am curious if any other software methodology prevents underskilled
developers from shooting their own feet. AFAIK the answer is a definitive
'No'.

[1]:
[https://en.wikipedia.org/wiki/Software_crisis](https://en.wikipedia.org/wiki/Software_crisis)

~~~
PaulHoule
It's a potshot at everybody.

Years ago I thought "people are fundamentally good."

Now I listen to the fire and brimstone preachers on the radio and every so
often it gets to me.

I wouldn't quite say "people are born in sin" but it does seem that people
can't really handle computers or carbon-containing fuels or other
technologies. So often I meet somebody who is said to have 'good social
skills' or who 'cares about people'. Somebody will have a talk with them and
tell you that they felt like they were 'listened to' but when I talk with the
'good social skills' guy an hour later about the conversation he had that left
somebody impressed it is clear the 'good listener' dubbed over what he heard
in his mind with what he wants.

He never gets challenged over this, but this is the problem I see. People
talking past each other, not listening. I know one person who can listen to
people argue for an hour and repeat back what they said in great detail, but a
lot of "high performers" seem to make it through life because their bluff
never gets called.

\---

The model where software development starts by "determining the requirements"
may itself be a "bad smell".

On some level it is all about 'satisfying the requirements', but there is
something to say for putting the integrity of the system first.

For instance, say you want to build an airplane that carries a huge radar,
such as an AWACS plane. If you were going to build a plane from scratch you
would be overwhelmed with "non-functional requirements" such as being able to
take off and land, not crash because of ice, etc. You would be driven batty by
the people who want to argue with reality about those requirements (e.g.
Boeing executives that couldn't reconcile the asked-for-by-customer not
retraining the pilot with the 'not crash' non-functional requirement.)

If you have any sense you buy an off-the-shelf plane and stick an antenna on
top and you skip the psychoanalysis. You get this

[https://en.wikipedia.org/wiki/Embraer_R-99](https://en.wikipedia.org/wiki/Embraer_R-99)

By "design reuse" you reuse all sorts of validation, testing and experience.
Many software projects go at it entirely wrong, getting into the "let's design
a whole new airframe" approach.

------
lmm
Article doesn't even make an argument. If you're not modelling things with
objects, and you're not using inheritance, in what sense are you doing OOP at
all?

If you want to treat OOP as a tool to use when it's appropriate to the
problem, good - but as you gain experience you'll find that that actually just
means "never".

------
wrsh07
Pretty good takes here in the comments (shout out to references to "pit of
success")

There are two things worth mentioning, though.

1) the best pragmatic programming recommendation is "semantic compression" by
Muratori [1]. Incidentally, I find it to be an effective "takedown" of the
sort of dogmatic oop that anyone criticizing oop is criticizing

2) Muratori might disagree with this, but for me the whole point of a new
class (type) is new invariants [2]. If you need a new invariant, you make a
new type. Sticking to this principle has helped me keep classes small and
focused without really thinking about other things like "single
responsibility". There are a few exceptions (eg you might need a new type to
work with a particularly clunky API, like some Map Reduce ones), but it's
really that simple.

As a bit of an aside, the worst code I have written hasn't been purely OO nor
purely functional. It's been when I've mistakenly tried to add new ideas to
existing code that break the paradigm.

[1] [https://caseymuratori.com/blog_0015](https://caseymuratori.com/blog_0015)

[2]
[https://en.m.wikipedia.org/wiki/Class_invariant](https://en.m.wikipedia.org/wiki/Class_invariant)

~~~
GordonS
I enjoyed reading Muratori's blog post you linked to - it's interesting to
read the thought processes of a dev who writes so well.

But I don't get how "semantic compression" is any different from the more
commonly termed DRY (Don't Repeat Yourself)?

~~~
wrsh07
I think DRY is a goal for the code and semantic compression (as described in
the post) is a path towards that goal.

In Muratori's world they're following best practices like YAGNI and DRY and
that allows you to refactor code at the right time

------
winstonewert
It seems to me that the definition of OOP is so unclear as to make the term
useless. It seems to me that lots of really bad ideas were spread under the
guise of OOP. But now OOP advocates are shifting the definition so that OOP
either excludes are doesn't explicitly advocate those bad ideas. I have to
wonder: what exactly does OOP still advocate?

------
dragontamer
There's this "bastard OOP" where "ellipse isa circle" going around which
completely doesn't work in practice. But there's "true OOP" that follows SOLID
principles (particularly LSP) that actually makes a lot of sense.

Unfortunately, this "bastard OOP" is the more popular variant, with many
"bastard OOP" examples from the late 90s and early 00s running around
textbooks, polluting the minds of student programmers at the time. This
article does a good job poking fun at it, with "class Dog extends Animal",
which is complete nonsense.

Actually learning SOLID principles, as well as the historical 80s style of
OOP, goes a long way towards fixing problems.

------
freedomben
Might I suggest, when posting Medium premium stories to Hacker News it would
be best to use the "Share Friend Link" link so people can avoid the pay wall
(it doesn't affect author earnings). That seems to be what it is for:
promotion outside of Medium itself. (This only works for the author so doesn't
help others, but I wanted to mention it to raise awareness)

------
flohofwoe
IMHO if a programming philosophy leaves so much room for interpretation and
misunderstanding, rants and counter-rants, needs evangelizing to the "dumb
masses" what it "actually" means, and all of that hasn't been settled after
more than half a century, then it has turned from a philosophy into a religion
(or rather, a cult).

Just let it rest and move on. It's just a waste of time for everyone involved.

~~~
bussierem
I think it's telling that I'm not able to tell whether you're talking about
OOP or FP in this comment.

~~~
xtian
Is there controversy about what FP “actually means”?

~~~
liability
The only real contention seems to be whether "functional programming language"
should describe languages that merely facilitate functional programming (e.g.
lisp), or only to those that enforce it (Haskell.)

Otherwise, whether or not a procedure is a function is about as clear cut as
you can get. It either is or it isn't, it's not subjective.

~~~
mpweiher
> whether or not a procedure is a function is about as clear cut as you can
> get

Definitely.

If it returns a single value then it's a function. If it does not return a
value, it's a procedure. (Pascal)

All procedures are functions (C)

All procedures and methods are functions (Swift)

A Function procedure is a series of Visual Basic statements enclosed by the
Function and End Function statements. (Visual Basic)

...

~~~
liability
I'm not sure if you mean this comment to be a joke, or....

[https://en.m.wikipedia.org/wiki/Pure_function](https://en.m.wikipedia.org/wiki/Pure_function)

------
mopierotti
After working in the Scala, which blurs the line between FP and OOP, daily for
years, I feel like the choice to use OOP or not tends to boil down to how
you'd prefer to solve the Expression Problem [1] for your domain. (if you
follow the paradigm of "Functional Core, Imperative Shell")

If you're following this paradigm, for all your core objects they:

-Will be immutable, meaning calling methods on them will not change their state. (except for potentially some transparent optimizations, eg in-memory caching)

-Should not have methods that have side effects (i.e. purely functional)

If this is the case, then choosing whether your classes are objects or simple
data structs is only a matter of code organization. ie do you want objects to
carry around their methods with them, or have those functions live separately.

If you want to share method implementations among objects, you have the option
of using inherited methods/types for a more OOP solution, or the typeclass
pattern for a more FP solution. (Though the typeclass pattern has significant
syntactical baggage in Scala)

[1]
[https://wiki.c2.com/?ExpressionProblem](https://wiki.c2.com/?ExpressionProblem)

------
Kapura
In summary, "Object Oriented Programming is good when you don't orient your
program around objects."

~~~
api
That sounds snarky. Not sure if that was intended or not, but regardless I
read your comment as:

"OOP is good when you use the object oriented structure as a tool to organize
your code and data to solve a problem, but not when you try to wrap your
solution or your conception of the problem itself around OOP."

It's 2020, and after 50+ years of programming language research I think it's
safe to say that there is no "one language to rule them all" or "one
programming paradigm to rule them all." Different approaches seem to excel in
different areas.

OOP seems to excel when the problem involves modeling systems with discrete
parts and/or where there is a lot of state to manage. In other words I'd
consider OOP for a "state-rich" problem domain. I am not saying this can only
be done with OOP, just that it's a viable choice. There are multiple
approaches to most problems.

I'd also say that bad code tends to develop different forms of badness under
different paradigms. Bad OOP code is massively over-engineered, verbose, and
slow. Bad procedural code is spaghetti. Bad functional code is impenetrable
"write-only code" that can only be understood by its author (maybe). Bad code
in multi-paradigm languages tends to have all these forms of badness.

~~~
Kapura
I program video games; I would consider running a real-time simulation as a
"state-rich" problem domain. And the constraints of running real-time
simulations mean that some implementations are not acceptable. Things are not
generally allowed to be slow if they can be done in a way that is fast.

Because of this, many game engines are built in a way that allows high
throughput for processing the state of hundreds, perhaps thousands of entities
in the world. Our guiding paradigm could be described as an entity-component
system; another word used to describe it has been "data-oriented design."
Here's a talk about it by the guy who was our engine director at the time[0].
It is not object oriented, and it seeks to unshackle itself from many of the
issues with OOP as a guiding principal.

[0]
[https://www.youtube.com/watch?v=rX0ItVEVjHc](https://www.youtube.com/watch?v=rX0ItVEVjHc)

~~~
garethrowlands
Absolutely. And the kinds of data structures you get in an entity-component
system are probably easier and more natural to use in a language such as
Haskell than a typical OOP object-graph-of-mutating-objects would be. Not that
anyone's programming their ECS in Haskell.

------
wwright
If there is anything in CS which should be renamed simply because people have
completely incorrect assumptions from the name, it's OOP. Everyone has someone
different they think about it, and almost none of it has anything to do with
late-binding, encapsulation, or message passing.

------
jdonaldson
I believe OOP was successful largely because of Conway's law. OOP itself is a
hierarchical (inheritance) type system that mirrors hierarchical social
systems in business. It's very "top down" in its orientation, and also allows
teams to tightly control the information they are dealing with.

Also, I didn't even read TFA. However, I think it's only fair since it's a
medium post that requires login.

~~~
goatlover
Also because of UIs.

------
noelwelsh
I find the premise of this kind of article is flawed. Not because some of what
the article says is incorrect, but because there is no such thing as OOP. OOPs
as practised in Java, say, is very different from that in Rust (which the
article mentions as having a "slimmer set of object-oriented features"). As a
result trying to make generic statements about OOP doesn't really give much
value IMO. You need to discuss specific well-defined programming practices to
make progress.

I also don't think trying to define OOP is fruitful. One can resort to
textualism, going back to Alan Kay's definition for example, but this is as
bad in programming (how does this definition relate to current practice?) as
it is in law.

~~~
dnautics
Ruby implements the Kay concepts under the hood, and erlang is arguably a
rather faithful implementation of Kay objects (though the code one writes
doesn't semantically reflect that and it was developed independently for a
completely different set of reasons - fault tolerance)

------
drbojingle
So you fix OOP by taking away the things that make OOP, OOP? That just sounds
like procedural programming with extra steps.

Also, as an aside, I wouldn't call DRY a 'rock solid foundation' of
programming. People do some weird contortions to make code DRY that end up
causing my harm then good. Separations of Concerns is far more important. DRY
is fine when applied within the scope of a feature, but not cross features.
Otherwise you end up playing wack-a-mole when you fix a bug in one place only
to make a new one somewhere else.

~~~
GloriousKoji
I knew a programmer who was all in on DRY and it was the heart of many stupid
debates. Not only did result in a crazy inheritance and dependency tree, there
were tons of many useless functions. Like having
"logger.log(obj.read_stream())" in a few places would be "WET" the API got
cluttered clutter with crap like obj.read_and_log_stream()

------
jdmoreira
I wish I could read this but it’s hosted in Medium and no, I won’t create an
account.

Seriously why does anyone even publishes there?

In fact I'm already biased against the author just because of his choice. If
he can’t even bother not using Medium can I really trust he spent the time to
think through what he wrote?

~~~
dsun179
I think we should publish these articles on github as readme. Then people can
even add stuff and fix typos.

------
phendrenad2
OOP is a crazy academic-esque idea that happened to stick because it works at
scale. When you have hundreds of programmers working on a codebase, it's
useful to constrain people with UML diagrams and rigid hierarchy. Otherwise,
cowboy coders hack up something the other 90% of coders can't understand. This
is why languages like Scala and Haskell haven't taken off outside of a few
small-team-focused niches, like data science.

~~~
wvenable
> OOP is a crazy academic-esque idea that happened to stick because it works
> at scale.

I suppose it's weird to be young enough that you're first exposure to OOP is
it being taught in an academic environment. It must make it seem like it was
dreamed up like some kind of formalism and spewed into your brain.

But before OOP languages and everything that you describe, people using plain
old procedural languages were already coding in an OOP style because it's
obviously beneficial. Languages later came along to formalize patterns that
people had used for years. For a more modern example you just have to look at
the Linux kernel which includes a lot of OOP principles even though it's all
just C.

~~~
phendrenad2
True, a lot of OOP is stuff that you might develop on your own, if you were
writing a lot of code back in the procedural days. But computer science has
been with us since the beginning of computers, and certainly OOP as we have it
today, with all of it's formalism, probably started back then in computer
research institutions like MIT and Stanford.

------
commandlinefan
Usually when I see people rant against OOP, they're not ranting in favor of FP
(or even better, in favor of something like Scala that combines OOP and FP)
but rather in favor of a return to procedural/top-down programming. There are
very good reasons that procedural programming was abandoned a long time ago
and the need for global variables to make it work is one of the big ones.

~~~
Jtsummers
> There are very good reasons that procedural programming was abandoned a long
> time ago and the need for global variables to make it work is one of the big
> ones.

Procedural programming does not, at all, require global variables to make it
work.

I'm not a big fan of procedural programming at scale because it almost always
seems to assume mutability (by default) and shared state when dealing with
concurrency, but let's set those aside.

You use structures to contain what a naive programmer (or a prototype) would
use global state for. Instead of:

    
    
      player_t current_turn; // a global
    

You do:

    
    
      struct game_state {
        player_t current_turn;
      }
    

And pass that _game_state_ value or reference around. The global state can
easily be minimized or eliminated from most procedural programs. I've done
this quite often as part of improving older programs.

~~~
antonvs
That's basically a hand-rolled implementation of a state monad that supports
real mutability. The Haskell equivalent is the ST monad.

While it's possible to do this kind of thing, it's busywork that properly
designed programming languages are perfectly capable of handling well on our
behalf.

~~~
commandlinefan
I had the same thought: he's doing OOP without using the "class" keyword.
Well-written C code is actually very object-oriented this way, it just doesn't
take advantage of the syntactic sugar of C++.

~~~
mypalmike
This is a common misunderstanding. Structural programming like this is clearly
similar to what we think of as OO. But it existed before OO and is still
heavily practiced in non-OO languages such as C.

The main feature that OO added to this was dynamic function dispatch via
inheritance. The specific function that gets called at runtime depends on the
type of the struct.

Inheritance has proven powerful but often results in confusing code. The
development of interface-based (non-inheritance) dynamic dispatch in COM and
Java, and later embraced by Go and Rust, shows that we can get what is
arguably the primary benefit of OO with a flat structural approach.

~~~
commandlinefan
... and this inevitable pedantry is why I thought twice about replying to OP.

~~~
mypalmike
It's not pedantic. If you're not doing dynamic dispatch, you aren't doing OO.

[https://blog.cleancoder.com/uncle-
bob/2018/04/13/FPvsOO.html](https://blog.cleancoder.com/uncle-
bob/2018/04/13/FPvsOO.html)

------
drummer
OOP has served me well. It is a powerful concept. Problem is that people can
misuse and abuse things.

~~~
wvenable
It's the most successful programming paradigm in history. I feel like being
anti-OOP is like being anti-vax. It's so successful and ubiquitous that it's
success becomes invisible and so people only focus on the failures or misuse.

Complaining about OOP requires an entire object-oriented software stack to
post your argument.

~~~
kryth
That doesn't make any sense. Being an anti-vaxxer is simply stupid, proven by
real numbers and repeated experiments. Meanwhile, there are a lot of fair
criticisms to OOP. Of course, a lot of them arise due to the fact that the
skill floor for software development is quite low nowadays, but then again if
we were all that smart, we'd just write C and C++ at the speed of light for
everything. P.S: Rewriting hackernews in a functional stack is trivial

~~~
wvenable
There are plenty of potential side-effects for vaccines as well. The last time
I had one, I had a relatively unpleasant reaction.

So I'm not saying that there aren't fair criticisms of object-oriented
programming. But "the case against OOP" is not proven by real numbers and
repeated experiments. It's the most successful programming paradigm in
history. The evidence for the success of OOP is more overwhelming than for any
vaccine. Yet we're stilling debating boogeyman like mercury in vaccines and
inheritance in OOP.

Re-writing hackernews in a functional stack might be trivial. But what about
the web browser, the GUI environment, the OS kernel? OOP based software stacks
are everywhere. And there is no need to re-write anything because it all works
fine.

~~~
arp242
That it's successful doesn't mean it's also a good idea. To quote Dennis
Ritchy: "C is quirky, flawed, and an enormous success". I feel this could
probably be paraphrased to OOP as well.

OOP isn't super terrible, but it does mix some good ideas with bad ones. Newer
languages tend to not be fully "OOP" but do include some of the better ideas
from it. OOP isn't the end-goal of programming, it's a stepping stone.

Same applies to functional programming by the way; a lot of non-functional
languages include various features pioneered in functional programming
languages.

~~~
wvenable
> That it's successful doesn't mean it's also a good idea.

No, but success brings out nothing but contrarians. You don't get an article
on hacker news saying everything is fine and working well even if that is the
reality.

You can't make money selling alternative medicine by claiming that medicine
works. Newer languages, in my opinion, are going backwards in a lot of ways
out of fear of ideas that shouldn't be feared.

~~~
Eero_m
Obviously being against the common trend is always going to get more
attention, but it makes no sense to assume that those people are wrong
strictly because of that.

The fact that vaccines are widely used doesn't make anti-vax stupid. Anti-vax
is stupid, because a lot of real research has gone into vaccines, and they
really do work. The arguments anti-vaxers use are not based on reality, and
can easily be proven wrong. The same can NOT be said about programming
paradigms.

There is no proof that OOP produces more elegant, simpler, higher quality
software with smaller programming effort than other paradigms. OOP isn't
proven to work; it's proven to be an attractive choise

~~~
wvenable
Isn't there proof? I mean there are _plenty_ of alternative programming
paradigms -- some that have been around since the 60's and still promoted as
alternatives today. Where is that success to compare?

Show me that an alternative to OOP is statically more successful and I'll
switch tomorrow.

It seems like you really want it out to be merely popularity rather than being
simply successful. That's not a rational position.

------
qmmmur
There is no 'case' to be made against any paradigm. Different hats for
different jobs.

~~~
yobi-ponti
It has become popular to hate OOP so now people find every opportunity to
discredit it. But yeah, every tool has a purpose. Don't like it in your
workplace? Make a fact based case against it and do something about it.

------
dreamcompiler
PSA: If you want people to read your tech posts, don't write them on Medium.
Medium requires logging in to view their content now, so like Pinterest,
Quora, etc they are dead to me. And I know I'm not alone.

~~~
gizmo385
Given that the post is ranked relatively highly on Hacker News at the moment,
it would appear that there are people who are still reading their tech post
even though it is on Medium :)

~~~
Noumenon72
I paid for Medium because I was reading so much good tech content there.

------
brundolf
The best thing I can say about OOP is that it allows for private state that
only certain logic is allowed to know about. In some cases, you just really
need that.

But I think the biggest problem with OOP is that it encourages a spirit of
"eager complication". Take getters/setters as a classic example. Occasionally
that pattern can be useful, but standard practice in Java is to _never ever_
create a simple public field. You _always_ create a private field, with a
getter and setter, under the assumption that later you'll need to put some
special logic in there.

We can't just create some plain functions, we need a Helper Class™. We can't
just pass in a function's dependencies as arguments, we need Dependency
Injection™. We can't just use a union type, we need a Visitor Pattern™.

I think some of these habits were built in a world before certain simplifying
language features were widely available (union types in particular are only
recently entering the mainstream). Programmers were traumatized by the
limitations of Java and the ensuing complexity of their projects, causing them
to enter future projects already bracing for the worst. Simplicity is assumed
to be impossible, so you just go ahead and barricade the windows with some up-
front complexity in hopes of flattening the exponential complexity curve down
the line.

But the world has changed since then. There's a reason multi-paradigm
languages are becoming so popular: because implementing real projects without
creating out-of-control complexity requires having a wide range of tools at
your disposal. In this new world, I think it's really important that some of
these patterns get unlearned.

I'll leave you with this Paul Graham post from 2002 about OOP Design Patterns:
[http://www.paulgraham.com/icad.html](http://www.paulgraham.com/icad.html)

> When I see patterns in my programs, I consider it a sign of trouble. The
> shape of a program should reflect only the problem it needs to solve. Any
> other regularity in the code is a sign, to me at least, that I'm using
> abstractions that aren't powerful enough-- often that I'm generating by hand
> the expansions of some macro that I need to write.

~~~
mrkeen
> The best thing I can say about OOP is that it allows for private state that
> only certain logic is allowed to know about.

Local variables in methods are private and encapsulated. Languages had this
before and after OO. Plus modules/namespaces/headers/etc.

OO languages added classes with fields, which are variables _shared among
methods_ (whether they are declared with the private keyword or not.) The
opposite of private/encapsulated.

If the 'private' keyword made a class's state sufficiently private I wouldn't
need to worry about the difference between StringBuilder and StringBuffer, and
I could pass java.util.Date without fear.

~~~
brundolf
> OO languages added classes with fields, which are variables shared among
> methods

That's not true. Structs and other mutable, structured data objects existed
long before OOP. The differentiating factor was that no functions/methods were
able to have varying levels of access to those fields; everything was public
all the time.

> The opposite of private/encapsulated.

I don't think that's fair. OOP added "private variables that survive past the
end of a function", if you want to get pedantic, but I think there's plenty of
added value to having this functionality in your toolbox. And yes, technically
closures can accomplish the same thing, but most mainstream languages did not
have them yet when OOP was on the rise and even then, using them to achieve
"lasting, private, mutable state" is, IMO, one of the few cases where the OOP
way of doing things is actually more ergonomic and expressive of intent than
the functional way. OOP is fundamentally about state, so when you really have
to manage some state, it is often the right way to do things. The problem
comes when you assume preemptively that you need state in the first place.

~~~
mrkeen
I'm saying you can have private mutable state if you stick to method
variables. If you take a method variable and promote it to a field (private or
not), or return it via a public method, you have made it more public - that's
what I meant by "The opposite of private/encapsulated".

------
bob1029
OOP is basically a blank canvas. It is always up to the artist to bring
structure and reason to it. There is never one perfect answer for every case.

That said, there are some general policies that seem to make sense, namely the
alignment of your object models with the shape of the business problem you are
trying to solve. Perhaps your problem domain has the idea of a Customer, but
there are really 3 flavors of customer. Many developers would automatically do
base/derived here, but in some cases it makes sense to have these as entirely
distinct types. Making this determination one way or another is at the very
core of the art of software engineering. These decisions have higher order
impacts that are impossible to anticipate or understand until you personally
go through hell a few times.

Proper management of state is another major concern. In context of OOP, I find
it best to centralize state along independent verticals of business
functionality. Models like CustomerCheckoutState, UserSignupState, etc. When
you centralize all of the state you wish to mutate as part of one business
activity into one object instance, it becomes really easy to manage. E.g.
serialize your state to database and back every time the user takes an action.
You can also leverage the scoped injection functionality available in many DI
frameworks to pass a per-user-action state instance into all relevant business
services and UI components.

If you can keep your business fact modeling & stateful domains under control,
the rest will likely fall into place.

------
adrianmsmith
People love to hate on inheritance and suggest that there are no cases where
its use is warranted. But inheritance absolutely is used, and successfully, in
many frameworks. For example GUI frameworks [1] [2] or server-side web
frameworks [3]. People have been writing code in frameworks like this for ages
and as far as I can see they worked well.

Including key use cases such as being able to use the provided components "out
of the box" and also being able to customize them, i.e. "like this component,
but with these differences".

[1]
[https://docs.oracle.com/javase/8/javafx/api/javafx/scene/con...](https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Button.html)

[2]
[https://developer.apple.com/documentation/appkit/nsbutton](https://developer.apple.com/documentation/appkit/nsbutton)

[3]
[https://ci.apache.org/projects/wicket/apidocs/org/apache/wic...](https://ci.apache.org/projects/wicket/apidocs/org/apache/wicket/markup/html/form/Button.html)

~~~
arcticbull
Just because there's examples of successful usages doesn't necessarily mean
that it's "good" or even a model for how we do things moving forward. Picking
that specific example [NS|UI]Button, they're dreadful to work with. Honestly.
Customizing them is difficult to impossible. It's often easier to just
_compose_ them into a different container because the extension points
couldn't possibly be designed in a way that gives you all the flexibility you
need.

Yes, it works, and yes, there are great systems built with OOP patterns. The
same can be said of C++. That doesn't mean they're good, or we should choose
it as our tool moving forward.

------
Animats
One point I've made occasionally is that few languages support backpointers
well. When B is a member of A, B often needs a back reference to A. Rust does
that badly, because it doesn't fit well with the ownership semantics. C++ with
move semantics does that badly, for much the same reason The same thing
applies when A allocates and owns B. It's hard to get a clean, safe, reference
back to A that cannot result in a dangling pointer, especially during
deallocation.

Object inheritance does that well. Finding the parent is easy and is done in a
consistent way. The deletion process is done in a consistent, if not ideal,
way. That's useful.

If we had proper syntax and semantics for getting a reference back to the
owning object, one of the use cases for inheritance would go away. Languages
have "this" or "self", for accessing the current object, but lack "owner", for
accessing the owning object. If a language offers single ownership, you should
be able to find the owner easily.

(Multiple inheritance is just a mess. Most, if not all, of the use cases for
that are better done in other ways.)

------
mcqueenjordan
There is a lot that is problematic with OO, especially if it is adhered to
with dogma. The worst idea and the one I've experienced most issue with, is
the `extends` keyword in Java, and more generally, the concept of inheritance.

Inheritance is terribly overused, and tends to lead to code spaghetti and
testing nightmares. Hierarchy is a mental trick we pull on ourselves to make
sense of the world, but it rarely works out nicely in systems.

------
spanhandler
I think React's decision, when faced with a desire to track state alongside
code, to partially reimplement objects inside of functions rather than just
saying "know what, this new feature requires class syntax, deal with it" (and
in fact excluding class-style syntax from the new features) is the perfect
sign of where the functions-vs-OO pendulum is.

------
kumarvvr
It's been forever that I have used Inheritance in OOP. Perhaps Python has
spoilt me, but 99% of my thought process automatically pivots to composition
when thinking in terms of OOP.

And more towards how to name libraries, what functionality to put functions
into and how to name my files.

OOP is just a tool to organize code. People give it too much thought
sometimes. I did too.

------
cjvirtucio
I have difficulty maintaining a stance in the OOP vs. FP debate. Like, all I
see in my day-to-day is OOP, so I know what good, production-ready OOP looks
like, but I've never an equivalent for FP. Would be nice to see a before/after
article, i.e. an OOP app refactored into FP, talking about why the latter is
better.

------
zaphar
I respectfully disagree that frameworks and stdlibs for languages like Java
and .Net are better due to use of Inheritance. In fact I struggle to come up
with a single occasion where use of Inheritance was not an Antipattern. The
exception I suppose is when you have no other way to do polymorphic
interfaces.

~~~
wvenable
Lots of things in software development have an is-a relationship. The only
time inheritance is an antipattern is when it's used for code-reuse and it
isn't modeling an is-a relationship.

Once you get to application code there aren't a lot of is-a relationships but
inside operating systems, libraries, and frameworks it does come up
legitimately.

~~~
zaphar
Is-a relationships are best handled by interfaces. If you don't have those and
only have inheritance then that is the exception I mentioned above. In those
cases I would argue that the base class should be composed entirely of
abstract methods and the depth of the inheritance hierarchy should be no
greater than 1.

~~~
wvenable
If you have something that _is_ something else in almost every way except for
one or twos details inheritance is far superior than interfaces.

The other benefit of inheritance, that often goes unmentioned, is the ability
to fix bugs in other products. I've had to inherit from some library/framework
class to fix a bug in that technology -- it might be a rare situation but it's
absolutely invaluable to have that option.

~~~
zaphar
I'll have to take your word for it that such things exist but experience so
far has taught me that:

1\. When I have something that is something else with minor modifications that
my and everyone else's lives will almost always be better when if I solve the
reuse issues with some composition and the is-a problem with an interface.

2\. That when I inherit from a class I don't control to fix a bug in that
class the fix is both _very_ fragile and usually _very_ short lived. Either
way I created a problem for myself later on down the line.

I don't disagree that sometimes the framework or library you are using give
you no other choice than inheritance. In those cases using inheritance is your
only and therefore best option. However I don't consider the framework to be
better for it. I consider the framework to worse off for it.

~~~
wvenable
> if I solve the reuse issues with some composition and the is-a problem with
> an interface.

I'm not sure how that's better -- you're just implementing inheritance with
more steps.

> is both very fragile and usually very short lived.

It is and I know you'd make that point but it's better to be fragile and short
lived than completely impossible. I have a least one of these hacks that was
completely necessary that has been in place for years.

------
peter303
OOP is perhaps not the best abstraction for distributed computing where you
want maximize data flow and minimize distributed state. The slowdown of Moores
Law for a single core/node means performance improvements will distribute
computing among a large number of such units.

------
grawprog
I've never really gotten the hate towards OOP. It's always logically made
sense to me. It follows along the path of abstraction and encapsulation
computers had been moving steadily towards. It just takes the concept of a
function, packages it together with some kind of data, keeps the internal
workings separate from the rest of the program and allows one to reuse or
extend those small pieces.

It may not be the best way of abstracting above procedural programming, it's
not suitable for everything, it's easy to use poorly, but it's there and it
does have benefits and can exist peacefully along side other paradigms.

------
InfiniteRand
A little off-topic, but when thinking about the reasoning behind OOP, in
particular how it shows up in C++ and the C++-influences in other languages, I
think it is worth looking at how large-scale C projects (OpenSSL, Gnome, etc.)
organize their code: long prefixes in front of function names complex structs
with lots of function pointers complicated macros for code generation

These directly relate to some of the core features of C++ like namespaces,
virtual functions, and templates.

I think this perspective makes the differences between C++ style object
orientation and Smalltalk style object orientation understandable.

------
AtlasBarfed
I still think procedural and OOP code requires 10-15 IQ points (for some
theoretical "good" measurement of IQ) than FP.

Vocal proponents of languages will be on the more intelligent end of the
spectrum to begin with, so this economic / structural "advantage" of OOP isn't
as apparent, especially since such advocacies revolve around idealism and the
hidden biases of it paying their bills.

~~~
amznthrowaway5
IQ points less or more? I assume you mean OOP requires 10-15 IQ points less,
but it is very hard to do OOP correctly so I don't think that's true.

~~~
AtlasBarfed
Most OOP programmers aren't doing complex hierarchies or other modelling /
design.

They're doing singleton service patterns in Spring Boot.

------
Ericson2314
OOP is so nebulous that there's no reason to critique it.

------
cutler
Apart from Rich Hickey's "Simple Made Easy" I think Joe Armstrong summed-up
best the inherent problems with OOP as it exists in the most popular
languages: "The problem with object-oriented languages is they've got all this
implicit environment they carry around with them. You wanted a banana but what
you got was a gorilla holding the banana and the entire jungle."

------
GordonS
Something I'd really love to see is a GitHub repo containing different
projects written in both an OOP style (e.g. with C# or Java) and an FP style
(e.g. with F#, Scala or Python).

F# and Scala also have both OOP and FP features - something else nice to see
would be the same project written in both OOP and FP styles in the same
language.

Anyone have any links to repos or blog posts along these lines?

------
cannabis_sam
If this article is to be taken at face value, the case against OOP is
understated...

The author is in desperate need of an actual understanding of OOP, an
understand of what OOP looks like in practice, and the most basic
understanding of other programming paradigms (or at least a rudimentary
understanding of what’s meant by procedural, structural and functional
programming.)

------
Waterluvian
FWIW I’ve been a python and javascript dev for a long time. I’ve spent this
year learning Rust and I’m kind of in love with the whole “it’s just a struct
and here are some associated functions” thing. It feels so simple and clear.

I also really liked C#’s “single inheritance plus interfaces”. That was also
an eye opening moment.

------
francobatta
I recently wrote an article bashing OOP, seems like I have to rethink it now
:P [https://collectednotes.com/francobatta/a-case-against-
java-o...](https://collectednotes.com/francobatta/a-case-against-java-or-bad-
oop)

------
platz
Object-Oriented Programming is Bad (2016)

[https://news.ycombinator.com/item?id=19407599](https://news.ycombinator.com/item?id=19407599)
(2019)

~~~
grugagag
Just to mention, Brian Will also has a video called OO programming is good*
[0]. I don't think we should take these literally, he presents what he things
are some bad traits and good traits of OOP.

[0]
[https://www.youtube.com/watch?v=0iyB0_qPvWk](https://www.youtube.com/watch?v=0iyB0_qPvWk)

------
gcpwnd
As usual. People have criticism. Counter critics say get smarter. The problem
is that we are dealing with largely unproven concepts. We have no facts, not
even meaningful empiric insights. It is a battle of religions but it should be
an academic challenge.

------
DonHopkins
Person, Woman, Man, Camera, TV: The Class Hierarchy

[https://www.youtube.com/watch?v=LhZyHIZpzoM](https://www.youtube.com/watch?v=LhZyHIZpzoM)

