Object-Oriented Programming is Bad (2016) [video]
m.youtube.com
m.youtube.com
- Sometimes more "procedural" programming provides better clarity. At other times, object-orientation is a cleaner approach. Neither should be a replacement for the other at all times.
- The idea that objects should reflect how we think of real objects in the physical world probably needs to die, or at least not be treated as a standard for object orientation, since objects in reality often do not fit into neat categories and hierarchies. Because reality is dirty, as is virtual reality, unanticipated exceptions to the rules end up causing OO zealots to create hack solutions that end up being rigid and less obvious to other programmers. The simplest example I can think of off the top of my head is the Fat Model Skinny Controller paradigm in MVC programming; since a model is often looked at as a representation of real-world objects, a programmer thinking in OO is likely to stick every conceivable property and behavior around the imaginary object into the model code, which sometimes results in files thousands of lines long with code that doesn't need to have anything to do with database abstraction. In such cases, a lot of code related to the concept behind a model would be better handled by helper functions or other classes.
http://www-cs-students.stanford.edu/~dbfaria/quals/summaries...
While at that, “message passing” [aka goto with arguments] is too fine grained of a concept, what people need 99% of the time is a structured function call.
There was even an unpleasant period of time in the 90's when people tried to make some of the Linux OS distro end-user apps run off of CORBA to bring about the interconnected "sea of objects" vision, slowing things down a lot.
No wonder OO style is widely overused.
It's overused in part, because it's easily sold. It's easy to make sound-bites out of the doctrine. At the same time, it's a bit nebulous and it's easy to describe all of reality this way at first glance. (That said, in the right hands, it can make for some dandy, clean software. The tricky part is the "right hands.")
Basically, software is organizational politics. Software methodologies are dogma, with nothing to back them up, except maybe some savvy design and at times some mathematics. However, these aren't sufficient to deal with the complexities brought in by business needs, the real world, and the complications of developer communities.
We're back in the alchemy days of the programming discipline. This is perhaps why minimalism often rules the day. Do as little as possible. Make do with as little as possible. Make apps and modules as small as possible. As much as possible, do no harm.
That was a thing on the GNOME desktop (GNOME being short for GNU Networked Object Model Environment). The CORBA framework was called bonobo, and it was basically a very early version of what's now achieved via dbus. KDE had its own equivalent, known as DCOP. AIUI, both systems were replaced altogether when dbus became a thing.
I'd go further to say that software management is (often, mostly?) organizational politics.
Software itself is about the flow of control and data, trouble often follows where dogma obscures this fundamental.
Trouble always follows where dogma conflicts with first principles and empirical data.
More correctly, a significant part of software development is organizational politics. The "flow of control and data" inevitably gets you embroiled in that.
The thing about Smalltalk, is that it adheres very closely to "everything is an object." This means that even low level mechanisms in Smalltalk have to allow a very nimble use of objects. This means that there can be serious cost/benefit mismatches when translating libraries/patterns/designs from languages like Smalltalk to others. Creating a lambda in Smalltalk is trivial business as usual. Other environments require more thought, have more limitations, have higher costs of their use. Factories are how you do normal operations in Smalltalk. In other languages, they become infrastructure that needs to be maintained.
Not everything needs to be modeled in every language. Basically, you model to the point where the cost/benefit works out, but go no further. In Smalltalk, the cost of modeling things is designed to be so low, almost everything is an object. So of course, the cost/benefit in different environments can be drastically different, and in many, modeling everything can be a waste.
Or the misapplication of it. If an app is really about dataflow, but programmers have tried to shoehorn in a pedantic OO model, one can often end up with lots of nouns with names that talk about How (exposing too much detail) and lots of code that serves to stick data in this cubby, with other code taking that data out, then sticking it into another cubby, ad nauseum. In that case, the relationships between the cubbyholes, the data, and the dataflow are only maintained through naming conventions, which can in turn be misapplied or neglected.
This makes following the dataflow into detective work to circumvent missed naming conventions, where it should really just be following implementers/senders/references in the standard way provided for by the language.
since a model is often looked at as a representation of real-world objects, a programmer thinking in OO is likely to stick every conceivable property and behavior around the imaginary object into the model code
This is where YAGNI comes into play. (You Ain't Gonna Need It) Never implement something unless you have empirical data to back up the need. If you implement according to imagination... well, the imagination of creative tech people is often practically unbounded. So just imagine how badly scaled the result could be.
Object orientation can provide a cute encoding of abstraction layers ala SICP to avoid dealing with a bunch of dangling functions:
Maybe the "impedance mismatch" between pure-fp and I/O is too great, with the IO monad being the "leaky abstraction" of the FP world.
Then OO is not a bad solution to an irrelevant problem, but a pretty-good solution to a bad relevant problem.
EDIT:
To clarify: The "problem" is change.
Imperative programming relates sequential device-change over time, to sequential programming lines. OO programming is a structured imperative programming.
Pure FP langs model sequential change with (roughly,) lists of actions to-be-performed that are snaked through your program.
(Also: maybe OO's failure has more to do with its limited static analysis, type systems, than its model of change?)
Booch: That’s a problem you wrestled with for literally years.
Backus: Yeah, and unsuccessfully.
http://archive.computerhistory.org/resources/access/text/201...
Before I wrote that comment I was actually on the anti-OO side, and in writing it, I convinced myself that maybe the FP solution wasn't great.
Good to see this wasn't a mistake!
Yeah, real existing software has flaws like every other thing in reality but at least it exists unlike a significant piece of fp style software. I like fp and I think it has a lot to teach but I think it's a bit presumptuous to advocate against a major paradigm that has actually proven itself with one that has failed to do so for decades.
And if OOP is so horrible why do so many people write OOP style PHP and javascript where they really don't have to?
Personally, I don't think OOP is bad per se, I just think the type systems of the main OOP languages are too limited and inflexible. Even so, you can accomplish amazing stuff in them. Just look at the Spring Framework and what it helps you do with so little code.
I think they have been successful because in the end a lot of the world we deal with behaves like objects. Devices, processes, remote machines all accept messages and those messages have 'side effects' in that they change the future behaviour of that thing.
Objects and messaging are best at the boundaries of system components. The internals end up being far simpler to state in functional terms, where the side effects can be pushed to the edges with far less inherent complexity.
By assuming that objects are successful everywhere and always, it's really ignoring the regions where objects are more or less useful, painting over the picture with broad strokes.
By learning multiple paradigms, you're learning broad principles that map well across all languages, as well as principles that are stronger or weaker in some paradigms than others. This enables you to be much more versatile when creating systems than devoting yourself to the 'One Way To Code' mentality.
Previous HN discussion: https://news.ycombinator.com/item?id=18043058
I get the sense that many commenters here, YNews in 2019, very much fit the description of a "Blub" programmer.. with the added twist that it is -giant-cloud-thing- platform and/or exciting web trends are my thing, to deepen the "Blub" point of view..
.. can't help but chuckle at the repeated and fairly clever jabs at ASM, considering it took M$FT ten years to make a windowing GUI that performed well enough on a PC, after the Mac OS was written in a lot of ASM.
Ok, try this on for size: OOP in general has one characteristic that makes it fantastic for large-team development, and that's extending over replacing. OOP makes it easy to never remove or change any existing code, and also "easy" to make changes or fixes by only adding more code. I wouldn't call it a good thing, but it does enable enormous numbers of people to all work on a codebase productively at the same time. I think this, more than anything else, is the power of OOP.
> And if OOP is so horrible why do so many people write OOP style PHP and javascript where they really don't have to?
Numbers. OOP supports big teams, so there are lots of OOP programmers in the wild. I genuinely think Javascript is sucking up to the Java developers of the world, and PHP is genuinely better for adopting OOP bits and pieces, but I'm not entirely sure why OOP devs and Java devs in particular seem to be immune to the industry mantra of "stay current and learn new things".
Its especially beneficial when you're doing multithreading work. I've debugged multithreading C++ and multithreaded elixir/erlang and fixing errors and identifying race conditions is night and day.
We wrote a front end in highly disciplined functional JavaScript on react with a single source of truth object in the center. I haven't touched JavaScript in a decade, and I could debug parts and add features with 100% confidence that I wasn't messing anything up (needed the frontend in a pinch so we hadn't done unit testing yet-i know I know, it's fixed now)
If you are curious, I recommend the video "boundaries" by Gary Bernhardt, of "wat" fame.
I could see it working well with a React-style component model-- update the state, and anything derived from it automatically changes to match.
This is largely only true in the post-bjarne soustrop OOP era. The smalltalk or actor paradigms don't necessarily encourage state mutation.
good on you to call BS a bit, but others have mentioned management of state specifically.. so "nouns" are ABCs, "verbs" defined, but after runtime for a while, and getting hammered or other difficult conditions, what is state like ?
good OOP can be really useful, without being a religion.. personally I would build general-purpose objects here and there to save some tedious decomposition..and the resulting seperation of code and concerns was fine .. specific things get specific code, while one or two item constructs somewhere off the beaten path might get lumped into a collection of "util" or similar.. its ok - it takes some practice and thinking ..
This is standard practice on YouTube, for better or for worse - same with the usual appeal "hey, you can help me by liking, commenting and subscribing!" at the end. (I have not clicked on this video, so I have no idea if anything like that is in there too.) Gotta drive those "engagement" numbers!
Except for this: > favor composition over inheritence
Yes! Inheritance is OOP-specific, and I agree that you should avoid it.
one needs not to avoid it completely but need to be aware of the pitfalls
"OOP" (e.g. subtype polymorphism) is useful for some problems, but worthless and kludgy for others; it's a hammer pounding lagbolts. A generation of programmers (mine) missed out on the richness of Lisp/ML flavored languages.
I didn't really come to Lisp/Ocaml until a decade after I'd been programming and after embracing it, I can't imagine going back.
Thankfully were in the multiparadigm era where modern languages are adopting the best of all world's.
This pitfall of open-ended implementation inheritance (a.k.a. "open recursion"), which is precisely what "inheritance" per se provides over the well-known (and often used) combination of "object-based" composition and "interfaces"/traits/type classes, is pretty damning for OOP itself.
I'd argue that those languages don't see as much use in great part because they are not the first languages people are exposed to, and therefore most decide it's not worth the effort to learn them.
I was a die hard OO/UML guy. I even contributed to various open source software projects for a model-driven-architecture framework, a UML editor, and integrations with Rational Rose. I was an evangelist for good OO practices, SOLID, etc... As a development coach I was quite fervent.
Looking back on that I feel like a fool. When I read the code refactoring examples from Uncle Bob and others I see all the problems Brian talks about in his video (and more). OO was a nice idea but we got the granularity and implementation wrong.
Before that it was the search for the One Way. OOP and functional programming were probably the two biggest opposing schools during that era, but now the trend is languages that easily allow both plus plain vanilla procedural programming. It's the programmer's job to pick a paradigm for the problem being solved.
~ Fred Brooks, No Silver Bullet (1986)
It seems we've really only made progress on unit cost and ubiquity.
I think this is roughly why cloud is popular. It's not cheap, but if you're using their standard pieces (RDS, lambda, ECS, ELB, CloudWatch etc), you're working in a defined box with fewer choices.
The main result of Modernism has been the subservience of the latter to the former, and in general the almost wholescale abandonment of aesthetic concerns. This is starting to come apart a little bit as people realize that this separation is unhealthy, and maybe we shouldn't build brutal, ugly flyovers through our cities because they have measurable effects on happiness, and thus property values, etc. (i.e., aesthetic concerns have functional consequences).
Similarly I think many software "engineers" take a long time to realize that one of their main functions is writing code that others can read; this is an aesthetic, artistic discipline that I think many developers reject as outside their domain. This, too, is starting to come apart as we slowly figure out that code's maintainability is a key determinant of its long-term success - that is, aesthetic concerns have functional consequences.
No, OO does not make any demands like that. You are totally free to make objects and classes out of processes and algoithms, and that is a good tool to do the kind of cross cutting concerns the article talks about.
There is not such thing as "bad" or "good" in absolute. There are paradigmes that work better in certain contexts - and I use the word "context" in very general way here - and some that work worse.
If my consulting experience has taught me something, is that "it depends" is a valid comment most of the time. By the way, I also learned that if you stopped at this comment, you're not a good consultant.
I am also becoming increasingly enamored of procedural programming as a default approach. Functional is also good - it's one of my first loves - but I find that it can be similarly prone to encouraging premature abstraction. Like OOP, that problem isn't going to become so apparent until you start using it in a large, long-lived business system with ever-shifting business requirements.
That said, I think that I've got to part ways with the video around about the part where it switches to talking about how the speaker thinks good code should be structured. Especially the bit about not keeping functions small - the problem with that is, it makes it way too easy for spaghetti code to sneak in. In the same way that, in the heat of the moment, a developer in an OO language is going to start tangling together the connections among objects a bit too liberally, a developer working in a 300-line procedural function is going to start fiddling with any variable that happens to be in scope a bit too liberally. Factoring out your functions does mean you have to name all those functions (which, I realize takes effort, but I also can't agree that it's a waste of effort), but it also serves as a way of making sure everyone stays honest about shared mutable state. Maybe it wouldn't be so bad in a language that has that "nested functions that aren't closures" feature, but, like he says, such a language does not currently exist.
In general I totally agree that sequential code should just be sequential, and it should not afford more functions. Because, the each time one looks at those additional functions, in your standard programming language, the first thing one must ask oneself is, "what is the context in which this function must work? What are all the callers of this function?". In simple, sequential code, the answer is often more clear. At least, it's clear that the block of code is really only ever "called" from one place.
The disadvantage is that the block can see unrelated variables that were defined higher up in the same function - or one must add a level of indentation everywhere to protect those.
Clean 300-line functions form an unstable equilibrium point; it requires constant effort by someone who cares about code hygiene to keep them clean. More so than factored code.
Also I think the mention of golang is telling which I think is an implicit bias in his preferred style reccomendations.
The claim that primitive types are better than interfaces (inside modules) reveals that he is comfortable with golang style. But this point misses that you can do _more_ things when you know the full concrete type of something, than when you program generically, only depending on the minimal requirements of what you need, which in turn opens up flexibility for the caller (constraints liberate).
Of course, one doesn't think too hard about this in golang, lacking generics
The reason of the popularity of OOP is way longer than java, Xerox invented the graphical desktop and ALSO OOP.
When Steve Jobs was ousted from Apple he created NEXT because he believed OOP was the next big thing. It was, and it became the foundation of MacOS X. Microsoft copied Steve Jobs in Windows with MFC. Java copied all of them.
In the words of Steve Jobs, developing in OOP is not faster than not using it. The big difference was once it had been created, it can be reused easily. That was essential for complex systems. It became obvious for companies.
There is this religion today of functional programing, people come to me and say, look how great this is, without state parallel programming is so easy, now you can use 32 processors at the same time. Great!!, until we measure performance and the thing goes 50 times slower. So now you can use 32 processors for what used to take one.
Don't get me wrong, we use functional programming when it is the best tool for the job(it removes lots of bugs), but it is not panacea.
Only thanks to researchers like Simon Peyton Jones[1], did functional language compilers improved their code quality.
[1] - https://www.microsoft.com/en-us/research/publication/the-imp...
Something of this level of quality, not random assertions on online forums.
http://kcsrk.info/multicore/gc/2017/07/06/multicore-ocaml-gc...
My point is that writing a GC for Java has in fact proved very difficult. It took Sun and Oracle many man years and different GC designs to get where they are today. So it seems Java also needs a "good" concurrent GC. With both functional and OOP languages generating a lot of garbage and sharing language features, I don't see a big difference in requirements in practice. And sure enough, Clojure/Scala seems to work well using the JVM GC and F# seems to work well with the .NET GC too.
I think the burden of proof rests with you and your statement that the concurrent GC situation is somehow more challenging for an FP language.
> I only accept CS papers about it
Me too
The reason that OO, FP, functional decomposition, entity relation diagrams, methodology de jure sucks is because modeling is hard.
--
These rules apply to all programming paradigms:
Favor composition over inheritance.
Try to not share state.
Favor dumb objects over active objects.
Favor shorter call stacks, less indirection.
Try to bunch like things together.
Try to DRY.
Likewise all the functional programming languages that get used as example, are also not pure FP, rather multi-paradigm, also supporting concepts from OOP.
"FP vs OOP: Choose Two by Brian Goetz"
Prototype inheritance is also OOP.
I'm finding DDD + functional languages + serverless architectures work quite well together, so far.
my rule of thumb with objects is to keep the methods to a minimum, to the extent all classes are either interfaces, implementations of interfaces or pure data classes. obviously this approach will be natural to ML programmers.
This is my biggest problem with OOP: proponents keep shifting the goalposts to avoid criticisms. If someone follows OOP practice, and it doesn't work out perfectly, then they must not have been doing it "properly". Hence "OOP" becomes a nebulous term, encompassing a whole bunch of approaches (encapsulation, inheritance, subtype polymorphism, dynamic dispatch, SOLID, MVC, etc.) but if any of those don't work in some situation then they mysteriously don't count as 'proper OOP' in that case.
I used to be very deep down the OOP rabbit hole (oh the joys meta-object protocols!), but these days I tend to stick to functional programming. I wouldn't claim it's the best way to program, and there are different tools for different jobs, etc. but one thing I've taken to heart is that we should try to make the easy thing be the correct thing.
An example of this is static type systems: it's possible to write correct code without types, but it's much easier to get things wrong. Type checking rules out a lot of those wrong things, which makes it more likely we'll do the correct thing (note: I'm not saying static types make things easier, I'm saying that the easiest thing to do in the presence of static types is usually more correct than the easiest thing to do when there are no types). Automated testing is another example, as is purity, effect systems, capability models, etc. Even the fact that Java forces us to write a class in a correctly-named file just for "hello world" is an example of making the path of least resistance more "correct" (although I disagree with Java's notion of what's "correct").
Even if we concede that these complainers aren't doing OOP "properly", that just means OOP is fraught with gotchas, misaligned incentives and lacks objectively checkable criteria. I wouldn't want to pursue any practice where earnest, researched attempts to follow it not only lead to the very problems that it claimed to avoid, but is met with advice to follow the practice "properly". Just look at how much of softwareengineering.stackexchange.com is bogged down in philosophical pontificating about the nature of OOP!
As for your specific claim, after doing OOP in several languages for many years, professionally, academically and recreationally, I count Java among the worst (PHP is slightly worse). I could say that if you want to do OOP "properly" you should try Smalltalk, but that would be yet more philosophical snobbery (you should instead try Smalltalk because it's a great language ;) )
Like, almost everything in programming these days (especially the frontend part)?
I actually like how functional code looks like, especially ML dialects - OCaml, Haskell, F#
What stopping me from trying to use these languages in prod is the lack of tooling (mostly refactoring), in the sense what Idea can offer.
And when you have >3 people in a project with a language which has no mature refactoring tools, there is a problem then, a big one. Imho of course.
> Like, almost everything in programming these days (especially the frontend part)?
Everything has problems; that doesn't mean everything is equally problematic. My point is that we should favour practices which are unambiguous and encourage objectively good things, and that OOP isn't either of those.
W.r.t. functional programming, I wasn't trying to argue in favour of it specifically here. I write a lot of procedural code, and keep looking for opportunities to learn/apply logic programming ;)
1. OOP is not necessarily bad.
2. The original OOP should be Smalltalk-like languages (messaging), but the term OOP we refer today basically related to Simula-like languages (inheritance, polymorphism).
3. OOP and FP are not exclusive. Language like Scala you can have a 'case class Stack[A]' with a method 'def push[A](a: A): Stack' returns new stack. First, it's a class and be able to have methods, inheritance etc. Second it's an ADT (Algebraic Data Type) and without any function with size effect. So both OOP and FP still makes perfect sense.
4. Main stream OOP languages design are mostly terrible. (Basically the 'Blub language' according to Paul Graham, I'll call those language Objective-Blubs because I want it to be a mainstream OO language without hurting someone).
5. There are a lot of arguments against Objective-Blub or OOP itself are related to state, side-effect etc. These are partially true. But I can't say stateless and pure is totally better than other approaches, I just feel better when I'm tackling complex domain so code does not affect each other in a crazy way. However if the whole program is pretty easy to make sense for you. For FP approach there is no obvious advantage over Objective-Blub. In this case choose the one with higher velocity.
6. The core problem that Objective-Blub to me is not simple state or side effects. It's the wrong/implicit modeling, if you are not modeling a problem, 9/10 you cannot address the problem. It's would bite you again and again. For small application these are fine, for large application these omitted modeling could be a real problem. But there are so few people mention this explicitly, please allow me to list some of them here:
a. Quick example - NullPointerException: Not modeling nullable concept makes you handle null everywhere. By explicit modeling Optional/Maybe/Some it could be resolved.
b. In Objective-Blub objects are reference type by default: Which means it's assume you only use the object in this specific machine and thread. Objective-Blub programmers always complain about object-relational impedance mismatch, and then they blame SQL. But the real problem is Objective-Blub itself. For Objective-Blub you have to throw away all the methods and references. Not only SQL, JSON or other serialization is also a big deal. If you use FP languages you just map tables to an ADT which is not a big deal. There's no assumption on identity on FP language at all, if you want an entity, just give it an id field. That also explained why FP languages is more concurrent friendly, because same data on different machines are still same data.
c. Sub-type polymorphism does not let programmer do the correct modeling easily: ADT have sum and product type. That's how you describe domain types, it's so straight-forwarded. Done. While languages like Objective-Blub have class and enums respectively, but no parametric enums. You have to write less obvious code to model simple concept like 'Payment = CreditCard(no, cvv) | Cash(amount) | FreeCoupon'
d. Sub-type polymorphism means strong assumption: This is so called inheritance, which encourage you do strong assumption like a Person extends a Head, which works but implicitly indicates a Person is a Head. This is ridiculous but if you convert Person and Head to some business type and services you can find too many code bases having this problem.
e. Sub-type polymorphism is under-powered than other polymorphisms: Less powerful is good when it's enough. But it's a huge problem when it's under-powered, which means you have to hack all the way round - like with old day Java, if you loop through an array, you have to cast type to every element. This is insane for a verbose statically typed language. Language should focus on simplify parametric polymorphism like OCaml or Haskell does to guide programmer choose a better way by default. Statically typed FP languages usually model effect with parametric/ad-hoc polymorphism, which could be a huge deal of separating dirty world concerns - like in Scala you can have your domain service accept F[_] as effect type parameter without knowing what this effect would be, it could be database IO or random generator or totally different abstract things.
f. Methods are everywhere: In Objective-Blub you have to write all methods in same class. So if a class having 100 methods is considered a code smell. And someone refactored them, move some of them to other classes and extract some of them to a new class. That's typically one reason why large code base are so hard to read, because typically in FP languages it's no big deal when you have hundreds of function operates on the same type, you just split them in several files if you want. For those extracted class and moved methods, they largely blurred how domain type and logic should be, because you have to create a new concepts, or move concepts to other places just because the old concept is too big.
g. Less expressive also blurred things: For the first time I was looking at a code base with design patterns everywhere I was so confused. Most patterns are the problem that language is so constraint that it cannot express simple idea, like TypeScript allow Partial<T> in constructor it would eliminate most of the builders. Like singleton and static properties could be addressed with Scala object. When a code base is filled with BarFactoryBuilder etc, that means the language itself does not modeling these common patterns at all. This make people(both reader and writer) fighting with languages instead of just expressing domain business.
This premise of this video is flawed from the beginning and the whole thing is basically a gigantic straw man.
Also, it's been my experience a lot of developers have a lot to learn about OOP principles. The most common OOP idea that's seemed to have been lost is asking an object to reason about its current state. If I had a nickel for every time I see code like below...
String fooJson = new Gson().toJson(foo, Foo.class); or
Double totalTax = TaxHelper.totalTax(orderItem.getCityTax(), orderItem.getCountyTax(), orderItem.getStateTax());
When he talks about the "kingdom of nouns" he's not complaining that the problem with "Manager" classes is that they're hard to name. Instead, the argument is that these manager classes are hard to name because you've reached a point in encapsulation where the association between behavior and state in inherently unclear and overly abstracted.
I think the author would agree that all programing deals with the difficult problem of composition, however, the argument in this case is that OOP introduces unrealistic constraints on composition. The argument in this video is that encapsulation and the single responsibility principle requires you to choose between a difficult to maintain tree-based object hierarchy, or to abandon encapsulation, and that both of these are sub-optimal choices.
Finally, I think the author would agree that he "has not learned how to use OOP appropriately" but would go one step further and say that "it's impossible to 'use OOP appropriately.'"