Know why you don't like OOP
12 points by abhin4v
12 points by abhin4v
The section on encapsulation misses the main point of it: a module or data structure needs to maintain its invariants, and if other code can reach in and mess around it’s hard to ensure that invariants are maintained correctly. Sadly OOP eclipsed the notion of abstract data types as in CLU, so they were long neglected as a tool for designing software (and the acronym collision with algebraic data types is awkward). I suppose they have had a kind of renaissance as interfaces or traits.
Also, methods are a curious one.
The basic machinery is type-based dispatch. In classic OOP it is dynamic dispatch on the receiver of the message, which is kind of awkwardly one-dimensional in the way it privileges the first argument of the method.
There aren’t many languages that tackle this awkwardness head-on by going all the way to multiple dispatch: basically, CLOS and Julia. It’s much more common to support method overloading, so you get dynamic dispatch on the first argument and static dispatch on the rest (the types acting as a sort of hidden extension of the method selector). In practice this seems to be a good compromise.
The syntax of method calls also matches the asymmetry of method dispatch. Identifying the receiver first turns out to be really helpful both for batch compilers and for IDEs and REPLs, by massively reducing the number of possible methods that have to be considered by the compiler or the user.
It’s ironic that object-oriented syntax is linguistically subject.verb().
Yeah, the underlying issue is that OOP models all function calls as message passing, which is natural for some functions and very unnatural for others. Even Erlang (built on message passing) has free functions.
Generally good points. I think one thing about "OOP" is that it is a bag of interrelated ideas rather than a coherent concept, so there is no consensus on what "OOP" even is, and that's why people all have different opinions.
The author's point on "inheritance" is a bit weird. Using inheritance alone doesn't mean separate small allocations. or even dynamic dispatch. I'd probably talk about other shortcomings of inheritance and the alternatives.
There are also other things we can discuss (and I personally think are overused in "OOP" code):
I think the issue with inheritance is mostly seen in languages that tightly couple implementation inheritance and subtyping. Both of these are useful. I've worked on projects that have seen huge performance improvements from having custom string representations, for example. Being able to have a concrete subtype of a string that has a different representation optimised for a different mix of operations is great. But being constrained by that having to inherit the implementation of some existing string is not great. Similarly, if I'm creating a new kind of view object, I want to be able to reuse a load of generic implementation for things like input handling, managing clipping regions, and so on. And that might mean I also want to be a subtype of a generic view, but I might also want to be a subtype of something unrelated.
Most existing OO languages either have performance issues arising from completely decoupling these things (e.g. Smalltalk / Objective-C, which makes inlining, and therefore any cross-method optimisation, very hard for an implementation), or they tightly couple them in ways that introduces fragility (C++, Java).
Go separated these properly, using a structural type system based on interfaces for expressing subtyping relationships and an implementation-inheritance mechanism built on composition that explicitly doesn't introduce subtyping. A language with structural and algebraic type systems can give you the loose coupling of Smalltalk with the performance of C++, but most OO languages predate most of that type-system work existing.
Being able to have a concrete subtype of a string that has a different representation optimised for a different mix of operations is great
What is the benefit of having a subtype? Optimized strings sound great, but what benefit do you get from them being a subtype of some proto-string? In general, despite cutting my teeth on programming with OOP languages decades ago, I have never felt I benefited much from thinking about subtypes—it’s always felt much more natural and productive to think in terms of interfaces and implementation. The taxonomic exercises or “kingdom of nouns” stuff never felt enlightening to me.
Implementations defining a concrete version of an interface is a subtyping relationship.
there is no consensus on what "OOP" even is, and that's why people all have different opinions.
This has been an interesting aspect for me in my decades of talking about programming languages. Proponents of OOP have wildly different and often contradictory ideas about what OOP is, yet they’re all seem to get along well—I can probably count on one hand the number of arguments I’ve witnessed between proponents of OOP, but someone who criticizes OOP for its emphasis on inheritance is the mutual enemy of both the pro- and anti-inheritance OOP proponent. It becomes hard to say what OOP proponents have in common besides an affinity to those three letters.
If I were asked why I'm not a fan of inheritance, I'd not talk about performance, but about this:
Inheritance is used for frameworky code. This can lead to rather complicated contracts: you have to implement some methods, you can override some methods, you shouldn't override other methods, and to understand what exactly gets called when and how to override things, you may need to trace things up some hierarchy. Not all languages enforce what you can't override, and as a result some very brittle hacks become possible.
I wrote some stuff about this in the past.
A contract where you're to implement some function signature or interface is a lot simpler to understand.
Modelling after the real world: Who even does this?
Domain-driven design adherents would like a word. I suspect this part is simply outside the current experience of the OP, and no shame in that, we all have our area of specialization and gaps between them. But there are techniques which emphasize this "modeling of the real world", which can genuinely be recommended in many contextss.
I don't think Domain Driven Design generally means real world modeling in the sense that this article is (rightfully!) mocking.
I am a person. I am also an employee, a programmer, a father, a son, an American, a go player, a resident of Portugal, a man, a person with a specific blood type.
The textbook "Cat is an Animal" modeling would say that we should have a Person class where each of the things I mention above is some kind of subclass. As I understand it, Domain Driven Design advocates would be horrified--they would not want a single Person class that those entirely different entities shared.
I think the author is right, the "Cat extends Animal" stuff is just the subject of bad courses and examples, it's not even how people who love OOP design software.
It's missing a section on message passing which is a big part of what some people mean when they say "OOP" but other than that it's a breath of fresh air to the pointless arguing that usually happens when this gets brought up.
This means that your array items will end up all over the place in memory, since each one is separately allocated.
This is a rather niche argument against inheritance.
The core argument would be (for me) that inheritance only allows you to extend behavior and types in a very rigid way which almost always results in shoehorning. Composition allows flexibility in how things are put together.
It comes up more often than you might think, and unless you take the game programming approach of turning your arrays inside out and having a structure of arrays rather than a structure of arrays composition can make things even worse.
But, if you have a moving GC then this can become less of an issue. Arrays tend to be filled in order so end up with the elements being nicely collocated by the bump allocator, and even if that doesn’t happen the GC can often make the elements collocated when moving an array from new gen to old gen.
That the author just assumes that people that "don't like it" don't know why they don't like it, is part of the problem of why it got so big, when it was just a bad idea that should have been relegates to its niches long ago.
I could say I don't like it, but a more accurate description is: OOP supports and embraces a bunch of constructs and practices that are catalysts to horrible code that interferes with my work.
The whole method concept is just a fancy name for state dangling around with weird lifecycles, which very quickly turn everything into a mess. I also find the whole ideia of 'enforcing' quite silly. A programmer cannot call a private method, yet he had a text editor and can change it to public at will. In python there is nos such silliness yet everyone understands the underscore convention for something that isn't intended to be accessed explicitly. The boilerplate and code noise of languages like java or C++ ie also ridiculously unreasonable. To the point that people don't even consider writing it by hand.
Inheritance, polymorphism... Way to many fancy names for simple and unnecessary problems. Just call whatever code you want whenever you need it's. It's not rocket science.
With a minimum of snark (apologies in advance if I fail at this goal), this series of arguments is not at all compelling. Basically, you started with:
That the author just assumes that people that "don't like it" don't know why they don't like it, is part of the problem of why it got so big, when it was just a bad idea that should have been relegates to its niches long ago.
... and then you proceeded to tell us that you don't like it but don't know why you don't like it.
The whole method concept is just a fancy name for state dangling around with weird lifecycles
No, that is not "the whole method concept". I can't being to fathom how it's related to methods at all, for that matter.
Methods in most C++-family languages are simply a means to resolve implementation identity (i.e. which function to call) coupled with an implicit context (e.g. C++ this) arriving within that function. If there are specific aspects of those two things that you dislike, then you should explain that.
A programmer cannot call a private method
Languages make it slightly more difficult to call private methods, but it's still pretty easy in Java, C++, ... I like to say that in C++, privates are but one void* cast away.
The point about private/protected/public isn't security, despite how most new programmers mistakenly glom onto that idea. The idea is simply organization. I keep some things in my kitchen, some things in my kitchen pantry, and some things in my basement pantry. I'm free to move them from place to place, but from an organizational perspective, the space in my kitchen is at a great premium.
Then you pointed to Python (an OOPL, albeit it a relatively awkwardly designed one) as having a better approach than OO (?), which -- as someone who's managed large Python projects -- is about as terrible an example as I could imagine. And for the record, I like Python ...
At any rate, I realize that I am making a technical argument online, which is a losing proposition to begin with. So I apologize for the seriousness of any arguments that I may have written.
I do not expect to change your mind, and that is not my goal in the first place. But I would encourage you to re-examine your arguments, because they are not the knock-out punch that you imagine them to be, and as a result they reflect poorly on you instead of the target of your ire. I do respect that you dislike OO, and I assume that you do have rational reasons for that dislike, but I am encouraging you to be more precise in your criticisms, and also to do a quick proof-read of your text before you post.
If you are going to castigate the people who don't know why they don't like OOP, i.e. programming oriented around agentic objects, you should probably at some point mention the subject matter itself, agentic objects, at all. But the problem is that most people talking about OOP actually have no idea that agentic objects are a particular strategy which is not always the right one and which you can elect to use or not use, in the way that fish do not know what wetness is. The actual reason I do not like OOP has nothing to do with inheritance, which is a frequently-useful language feature. It is because it is a monoculture that not only has no idea that some things should be dry instead of wet, but frequently assumes that when people say they are anti-OOP that their problem is the salinity or tint of the water. Composing the agent-objects instead of inheriting from them doesn't help. Agentic objects are the natural abstraction model for I/O, are often a good idea for isolatable subsystems, and are rarely a good idea for straightforward data.
For me the "steelman" version of OOP is the pure actor model. I believe this is in line with Alan Kay's remarks that the essence of OOP is message-passing between opaque autonomous entities. Most of the other common detractions from OOP like inheritance are orthogonal to the message-passing model.
I dislike the particular mix of OOP concepts in Python. A lot of my complaints are old and have been solved (e.g. by dataclasses) but the lack of extension methods still irritates me – I've often found myself captive to upstream bugs or just inconsistent interfaces in libraries I depend on. For example, PyTorch, numpy, and scipy all use .std() to mean slightly different things.
Languages I think solve this well: Scala & Kotlin (explicit extension methods), Julia (methods attached to functions making extension open by default)