17 ms·
The author’s OO example is hard to understand, but they’re wrong about why. It’s not bad because it’s OO, but that it’s very badly done OO: the class couples tw
by benkuhn 6y ago
The author’s OO example is hard to understand, but they’re wrong about why. It’s not bad because it’s OO, but that it’s very badly done OO: the class couples two different concerns (network API client and database). That’s why it makes more sense as a bag of functions.
The general version of the point doesn’t work very well, and many of the other OO use-cases the author discusses actually work much better than alternatives.
For example, on abstract base classes: if you replace this with a bag of functions I think you end up reinventing virtual dispatch—that is, each function’s top level is a bunch of `if isinstance(...)` branches. This is much harder to read, and harder to add new implementations to, than abstract methods. It’s also no easier to understand.
(There is a subset of this advice that I think does improve your code’s understandability, which is “only ever override abstract methods,” but that is very different from “don’t use OO.”)
For impure classes, the author suggests e.g. using `responses` (an HTTP-level mocking library) instead of encapsulating these behind an interface. This is a fine pattern for simple stuff, but it is not more understandable than a fake interface. The hand-written fake HTTP responses you end up having to write are a lot less readable than a mock implementation of a purpose-built Python interface. (Source: I once mocked a lot of XML-RPC APIs with `responses` before I knew better; it was not understandable.)
- throwaway894345 6y ago> It’s not bad because it’s OO, but that it’s very badly done OO This argument seems to come up for every criticism of OO. The criticism is invalid because true OO would never do that. It seems like a No True Scotsman. Notably, when you whittle away all of the things that aren’t true OOP, you seem to be left with something that looks functional or data-oriented (something like idiomatic Go or Rust). There isn’t much remaining that might characterize it as a distinct paradigm.
- na85 6y agoI disagree. Good OO is about encapsulation of concerns. Make a Client object and push data to it; let the class worry about the networking and sockets. Make another object to deal with the database. For sufficiently complex systems I don't see how functional or other paradigms can manage without holding a huge amount of global state.
- lostcolony 6y agoHow do you manage to not have global objects? FP and imperative handle data without huge amounts of global state the same way. By structuring your code properly. Per your example, your Client instance still has to be passed around everywhere it's needed, and you call client.push_data() on it; in FP or imperative approaches, you would pass around a Client struct or tuple or similar, and call push_data(client). OO just bundles state and functions together, as fields and methods on an object. Which has its pros, and its cons.
- zabzonk 6y ago> Per your example, your Client instance still has to be passed around everywhere it's needed Yes, because our program probably needs more than one Client.
- lostcolony 6y agoNot sure if you're trying to make a point here?
- deleted 6y ago[deleted]
- na85 6y agoI mean in Java or C# there are "global" objects, being the Main class, I suppose. There's always a top-level construct. But my Sword class doesn't hold a reference to a NetState object.
- lostcolony 6y agoThat was a rhetorical statement. The same reason you don't need global objects in an OO language is the reason you don't need global data structures in an imperative or functional one. Your Sword class doesn't hold a reference to a NetState object, but then, my Sword struct doesn't need a reference to a NetState struct, either. If I were to climb the reference hierarchies back to my 'main' function, I would find a common ancestor, just like in OO if I were to climb my reference hierarchies, I would get back to the class with my 'main' method. But that doesn't imply global state; it's all scoped down in the code where I defined and use it.
- benkuhn 6y agoI think you took me to be arguing for a stronger claim than I actually was. My preferred paradigm is also roughly “idiomatic go or rust.” But the OP isn’t arguing for idiomatic-go-or-rust, as far as I can tell they’re arguing against ever using methods or virtual dispatch (which are core features of both of those languages)! My claim is that the OP’s diagnosis of the problem—“virtual dispatch causes your code to be confusing, get rid of it”—is incorrect.
- GolDDranks 6y agoThe style OP is advocating lines well with Rust at least. Rust still tends to use method syntax, but it is very strict around mutability and virtual dispatch is rarely used, so you tend to write your programs with similar flavor as the OP is arguing for.
- mumblemumble 6y ago> This argument seems to come up for every criticism of OO. The same is true of functional and procedural programming, too. The problem, as I see it, is that the profession has a history of treating programming paradigms as just being a bag of features. They're not just that, they're also sets of guiding principles. And, while there's something to be said for knowing when to judiciously break rules, blithely failing to follow a paradigm's principles is indeed doing it badly. This is a particular problem for OO and procedural programming. At least as of this current renaissance that FP is enjoying, advocates seem to be doing a much better job of presenting functional programming as being an actual software design paradigm. It's also the case that, frankly, a lot of influential thought leadership in object-oriented programming popularized some awful ideas whose lack of merit is only beginning to be widely recognized. If null is a billion dollar mistake, then the Java Bean, for example, is at least a $500M one.
- throwaway894345 6y ago> The same is true of functional and procedural programming, too. There are some specific criticisms that are levied against FP for which proponents respond “that’s not true FP”. For example, the criticism “FP is too preoccupied with monads” might engender such a response; however, this response is appropriate because it’s not one of the defining features of FP. Yet for FP, there are still pretty widely-agreed upon features or conventions (functional composition, strong preference for immutability, etc). For OOP, I can’t think of any features or styles that are widely agreed upon. If you mention inheritance, half of OOP proponents will argue that inheritance isn’t a defining characteristic because Kay didn’t mention it in his definition. If you mention message-passing, many others will object. If you mention encapsulation, then you’ve accidentally included virtually all mainstream paradigms. If OOP is a distinct thing, and it isn’t about inheritance or message passing or encapsulation or gratuitous references between objects (the gorilla/banana/jungle problem), then what exactly is it?
- mumblemumble 6y agoSo, that's still focusing on features. If we're talking about it from a paradigmatic perspective, instead, then I would argue that the prototypical idea behind object-oriented design is indeed encapsulation. It's true that this is not a distinguishing feature. I don't see that as problematic, it's just how things work. Object-oriented programming, like any paradigm, evolved over time, as people built languages with new ideas, and then spent time figuring out how best to make use of them. And there's no rule saying that nobody is allowed to subsequently adopt and adapt a useful idea in other domains. Fuzzy boundaries are part of the natural order, and that is a good thing. But, if you go back and read the seminal papers, it's clear that the common thread is encapsulation, every bit as much as referential transparency is the core idea that unites all the seminal work in functional programming. The idea was that programs would be decomposed into modules that were empowered to make their own decisions about what code to execute in response to some instruction. And that this was supposed to liberate the programmer from needing to micro-manage a bunch of state manipulation. Not by eliminating state, as is the ideal in FP, but by delegating the responsibility to manage it. This is why the concept of Beans bothers me. A Bean is a stateful object that throws its state into your face, and forces you to directly manage it. That sort of approach directly contradicts what OOP was originally supposed to be trying to accomplish. For my part, I am convinced that the bulk of the backlash against OOP is really a response to the consequences of that sort of approach having become orthodox in OOP. Which is deeply ironic, because this is an approach that enshrines the very kinds of practices that the paradigm originally sought to eliminate.
- dkersten 6y agoMy problem with these arguments is that its meaningless, if this "bad OO" (or "bad <insert paradigm>") is what most developers write. Its a big problem I have with ORM's. Every time a discussion about what's bad about ORM's is brought up, there's a multitude of people saying that its just used wrong, written wrong or otherwise done wrong, yet that's basically my experience with every non-toy codebase I've ever worked on as part of a larger team. Telling me that its just done wrong is useless because its out of my hands. Is it the ORM's fault? I argue yes because it encourages that kind of programming, even though its not technically the ORM's fault. I see it the same with OO or anything else. Is this "bad OO" the type of OO people tend to write? If yes, then OO is the problem. If no, then OO is not the problem. Personally, I like a mixed approach where I use some OO and a lot of functional. I'm writing a little toy game in C++ from scratch and I have a very flat design, preferring functional approaches where I can, but I have a few objects and even a little bit of inheritance where it makes sense. Sometimes dealing with an object-based API is just more convenient and I won't shy away from using OO principles, but I also don't default to them just because either.
- AmericanChopper 6y agoBut every programming paradigm can be (and perhaps most often is) done badly. They each just create their own set of issues when "done badly". I think it just comes from not properly understanding why you're making certain choices. Deciding to use a design pattern is going to come with a certain set of strengths and weaknesses, so ideally you'd consider your problem and choose design patterns that makes the most of the strengths and minimizes the impact of the weaknesses. But I don't think it's very common to put that much thought into programming decisions, so people will often make a design decision, and then invest their effort into fighting the constraints it imposes, while usually failing to realize the potential benefits of it. OOP can be a perfectly suitable tool to solve a problem, but if you haven't thought about the actual reason why you want to use it, then you're just going to end up creating complicated abstractions that you're not properly benefitting from. ORMs can be great tools to, but if you want to use an ORM, you have to solve your problems using the approaches implemented by the ORM. If a programmer can't/won't do that, then they've just chosen the wrong tool for their problem/the approach they wanted to take to solve it.
- keithnz 6y agomany software problems boil down to badly done X, where X is some paradigm, technology or technique. Then someone concludes, like this article, that it is pointless / to be avoided etc. It would instead be better that you write an article where you steelman X, then compare it to Y, and look at the comparative advantages. Badly done X or Y should always be highlighted but not really used as a reason not to do X or Y. If you can steelman X and show that even best effort has far too many disadvantages, and there doesn't seem a way forward to overcome them compared to other approaches, then sure, call it out. What I see is often new things have less history of people badly doing it compared to the old things that have a lot of examples of people badly doing it and they mistake that as the old thing as being bad.
- rualca 6y ago> This argument seems to come up for every criticism of OO. The criticism is invalid because true OO would never do that. This passertion is disingenuous. The whole point is that the reason why bad code is bad is not because it's OO, it's because it's bad code. You do not fix bad code by switching programming paradigm, specially if it's to move back to procedural programming and wheel reinvention due to irrational class-phobia.
- throwaway894345 6y ago> This passertion is disingenuous. The whole point is that the reason why bad code is bad is not because it's OO, it's because it's bad code. This is pretty false on its face. Let's say that some paradigm insists that inheritance should be used for every problem. Almost all programmers agree that this is bad code, thus code written in this paradigm is bad because of the paradigm. > You do not fix bad code by switching programming paradigm, specially if it's to move back to procedural programming and wheel reinvention due to irrational class-phobia. Agreed. Instead you should start with your current paradigm and remove the problematic aspects until you're left with something that works reasonably well. I posit that when you start with OO and drop its problematic aspects like inheritance or Armstrong's observation of banana-gorilla-jungle architecture, you end up with something that looks pretty functional or data-oriented, a la idiomatic Go or Rust. If your definition of "OO" already excludes these things, then it probably looks something like Go or Rust or perhaps even Erlang. The issue I'm raising with "OO" is that there is no consensus about what OO is; rather every defense of OO is about what it isn't (and then there's the ever predictable straw man, "But you can write bad code in any paradigm!").
- rualca 6y ago> Let's say that some paradigm insists that inheritance should be used for every problem. There is no such paradigm, including and specially OO. In fact, your example clearly illustrates the perils of blaming the tools for problems caused by incompetence. I mean, how many decades have passed since "composition over inheritance" has been taught like a mantra in every OO programming 101 course? And still the best OO example you could manage to come up wit is a blatant error that would lead you to fail a OO programming 101 course? I'll restate the obvious: the reason why bad code is bad is not because it's OO, it's because it's bad code. And here you are, trying to pass blatantly bad code as somehow an OO problem?
- ogre_codes 6y agoI've found that objects best when they are used in moderation. Most of my code is plain old functions, but occasionally I find that using an object to do some things is just much cleaner. The rest of the time doing away with all the baggage associated with objects and using structs or other, simpler structures is just easier to wrap my head around. Java IMO is the classic case of going way too far overboard with being object oriented. Much worse than Python.
- Gibbon1 6y agoI feel like OO is okay if objects implement good well thought out interfaces. The problem is designing good well thought out interfaces is hard. Also the rise of distributed computing causes problems for OO. Passing data between systems is pretty easy. Passing functions with that data, not so much.
- rocqua 6y agoMy experience with distributed computing is that the biggest issue isn't passing functions with data. Instead the biggest issue is sharing state between systems. This is part of why immutability is very nice.
- ogre_codes 6y ago> I feel like OO is okay if objects implement good well thought out interfaces. I find that much of what I do is taking something from structure A and manipulating structure B. If you are a slave to OOP, you end up bolting that method to either one or the other object when really it affects both and belongs in a separate place entirely. The other big issue I have with OOP is frequently I don't want or care for reference passing, I just want value passing. That is usually the place where I start using objects is when passing references is useful. It's particularly useful if you are building a thing which requires multiple steps and you need that state to persist between steps. I've seen it done well with functional programming, but for me it's just easier to build something out with an object that holds persistent state.
- theamk 6y agoBut that's exactly the point! Object-oriented means few different things: it could mean "my program contains 'class' keyword" to "Everything must be a class which obeys SOLID". If you say general statement, like "OO in Python is mostly pointless", you are likely wrong. There are ways to use 'class' keyword to make programs better (shorter, more readable) and there are libraries out there which use OO this way. If you want to write something which can be proven true, you want to have something much more specific. Here are some possible titles one can have meaningful discussion about: "SOLID everywhere is not worth it" "Don't use objects where a function will do" "single responsibility is the key to usable OO" and so on
- ashtonkem 6y agoYou’re missing the selection bias at work here. The issue is that good OO doesn’t get you upvoted in programmer related forums like HN because it’s not terribly popular. Instead what does get upvoted is arguments against OO, and these often contain a lot of bad OO code as counter-examples.
- kelnos 6y agoAnd yet a lot of this so-called "bad OO" is representative of most of the OO I see written. If something that is bad is the norm, that says something about the paradigm itself, as well.
- krageon 6y agoBad code is the norm (regardless of paradigm), because it's a profitable industry that rewards entry but not necessarily skill. It's not really a failing of the paradigm that it's applied wrong when that is the norm in all aspects of the practice.
- ashtonkem 6y agoThat is not my experience. I see a lot of boring, working, OO code. Nothing that I’d excitedly put on the front of HN as an example of anything, but code that’s reasonably easy to read, and just works day in and day out.
- knuthsat 6y agoYou can create an OO mess in Go and Rust that is equivalent to this too. Dependency injection is a nice name for factory functions + dependency list + topological sort, but people still use it horribly, injecting dependencies everywhere, not writing functional and data-oriented composable code. It's nice to learn about patterns but it's wrong to think about patterns as a solution.
- throwaway894345 6y ago>> ...idiomatic Go or Rust > You can create an OO mess in Go and Rust that is equivalent to this too. To be quite clear, I qualified with idiomatic. I don't think you can create an OO mess with idiomatic Go or Rust, but then again, the whole point of this thread is that there's no clear consensus on what OO actually means, so what does "OO mess" mean.
- knuthsat 6y agoIdiomatic says nothing about code design. Code design is far above what idiomatic means. You can still have dependencies where you shouldn't have them. Your services can still be designed without composability in mind.
- krageon 6y agoIt's not like the criticism in this case is particularly esoteric: Literally the first explanation is that the proposed classes don't encapsulate single concepts. That's the theory of the first course you'd get about this in school, or if you're self taught something you'd see within the first 30 minutes of casual googling about the subject. I think it's fair to question what is going on in the article if the mistakes made are of such a basic nature.
- cztomsik 6y agoIf you want to see real OO, look here: - https://pharo.org/ https://pharo.org/ - https://gtoolkit.com/ https://gtoolkit.com/ If you don't want to, it's ok, just stop bashing it without first learning it (you learned FP too, it took some time too).
- theelous3 6y agoRight but it actually is just horrible code with zero separation of concerns. Having a client class is a great example of good OO. The library you're using having a Session object is basically essential in python. Any other paradigm would be a mess. Things storing state about themselves in a totally unambiguous way is brilliant. Most complaints about OO just focus on bad usecases. Calling that is not a no true scottsman. If you saw someone only driving in reverse, complaining about car UX, would you not state the obvious?
- IOT_Apprentice 6y agoThis is an industry where the waterfall lifecycle was presented as being a bad methodology, which was then promptly adopted by almost every firm in existence. Then Agile & SCRUM arrived and now people complain about giving status on what they are working on now, what issues they have and what they intend to work on today. You see posts here bitching about it. I still get software from engineering that appears to lack ANY sense of what we are supposed to use it for. The developer gets tons of back slapping by engineering management, and it leaves us STILL doing grunt work with the poorly designed tooling, that the developer is now writing perl scripts to deal with issues in his delivered work.
- cmiller1 6y agoIt's also a cherry picked example, networking code, of a problem that can be expressed equally as nicely with OO or functions. Now refactor a gui or a game without OO and you'll find the OO version easier to understand or cleaner.
- lostcolony 6y agoWell, yes. When in a domain that is heavily noun driven it makes sense to reach for OO. It's just that most domains aren't (hence Yegge's famous article re: nouns and verbs).
- cmiller1 6y agoExactly, my take is "use the right tool for the right job"
- fifilura 6y agoIsn't the point here whether nouns should be able to perform verbs or not? In a "noun driven domain" you can still disallow the noun to perform the verb.
- lostcolony 6y agoMore do verbs -have- to be bundled with nouns (or live second class citizen lives), as that's more what OO requires. Even in functional languages, you -can- have verbs living with nouns (your struct can have a field that is of function type); it just isn't required (or recommended for most use cases). It's not a question of can they, or should we prevent them, it's 'should it be required?' It may make sense to require all verbs to be coupled to a noun when your problem domain is mostly modeling nouns, and verbs only describe how those models change and interact with each other. But when the problem domain is mostly process, with nouns being ancillary, it is unwieldy to talk that way. It's why you have so many "ThingDoer" and "Utils" and the like in most OO codebases.
- henry_bone 6y agoIs this true for games? I read an article[1] some years ago that pointed out the OO design (with C++ in this case) was bad for performance due to the data not being arranged in a particularly cache-friendly way. [1] http://harmful.cat-v.org/software/OO_programming/_pdf/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf http://harmful.cat-v.org/software/OO_programming/_pdf/Pitfal...
- deleted 6y ago[deleted]
- leontrolski 6y agoHi, what I'd really like is to test my conjecture - would you (or anyone reading) be able to email me a (< 500 line, ideally not super-deep-library-code) bit of code (constructed or otherwise) that you think is "so innately OO friendly that the conjecture won't hold". Given some examples, I'll do a follow-up blog post.
- teawrecks 6y agoFwiw I've felt this way for the 10 years I've used python. IMO all of Python's strengths lie outside of it's oo functionality.
- AQXt 6y agoYour conjecture may only hold for tiny (< 500 lines of code) problems. But when you deal with big problems -- millions of lines of code, written by hundreds of developers over a period of years -- you'll definitely see the benefit of OO. Of course, most projects are somewhere in the middle; but you shouldn't dismiss OO just because you can't see the benefit in tiny projects.
- leontrolski 6y agoI'm currently on a project with 100s of thousands of lines of code (admittedly not millions) - working in the bag-of-functions style - and I've yet to miss any OO features. Surely it's possible to construct a smaller example? It's not like the millions of lines codebase isn't de-composable?
- bananaface 6y agoFor what it's worth, Emacs is an example of what you describe and it's extremely functional (and carries a huge amount of global state). Millions of lines, an insane number of contributors, development period of decades with a very decentralised management structure. Emacs has a lot of problems but once you learn the base language, it's quite easy to interface with. I'd argue it's because of the limitations on structural complexity - you don't need to wrap your head around non-locally-defined structure in order to understand any one line. The parts that attempt to emulate OO tend to be a lot harder to deal with as an outsider.
- kleiba 6y agoyou end up reinventing virtual dispatch But you can have virtual dispatch without OO, see e.g. Clojure.