OOP Is Dead, Long Live OOP

(gamedev.net)

307 points | by starbugs 2016 days ago

29 comments

  • ilovecaching 2015 days ago
    Becoming a professional Haskell and Erlang developer really shifted my view on OOP (let OOP denote class based OOP as found in Java or C++). In my view, OOP is prove a poor model for computation, and the result has been that OO code is almost always significantly more complex and error prone than an equivalent computation written in a concurrent, functional, or structured paradigm. Recent trends in language design (see Rust, Go, Elixir) also seem to be abandoning OOP in favor of other models as well.

    OOP provides competing ways of abstracting behavior that in Haskell we can model with type parameters and constraints. Objects are not truly encapsulated in the way Erlang processes are and a poor fit for SMP. Objects also pathologically hide data in attempt to manage mutability, making it impossible to reason about the memory layout of the program.

    All in all, OOP is a toolkit for building bad abstractions: abstractions that do not easily model computation, that hide data, and has tended to create overly complex solutions to problems that are often full of errors that a language focused more on type expressivity could catch at compile time.

    • cryptica 2015 days ago
      I don't understand what the problem with OOP is. My OOP code has always been clean and easy for myself and other people to read and modify.

      When I read articles complaining about OOP, I just can't relate at all.

      The idea of functional programming makes no sense to me except for writing very specific simple programs. For example, I like using some functional programming on the front end with VueJS but even there, I still allow mutations in certain parts of the code.

      I like being able to store state in different instances and allow it to be mutated independently of each other.

      I like it when related logic and state is kept close together in the code; it makes it easy to reason about different parts of the code independently.

      On the other hand, with functional programming, it can be difficult to figure out where state comes from because there is very little abstraction to separate different parts of the code.

      • kamaal 2015 days ago
        >>When I read articles complaining about OOP, I just can't relate at all.

        Most problems start with OOP when people try to take the longest possible path to achieve a goal. Enterprisey code, like having to deal with thousands of classes(AbstractClassFactoryFactorySingletonDispatcherFacadeInitializer types), dependency injection, design pattern abuse. Then on top of this comes things like Spring framework etc. At that point in time you have like two problems to deal with, one is the problem itself, and second is the complexity of the language.

        This phenomenon has remained in the OOP world, almost forever. Things like maven have helped a little. But complexity hell has been the mainstay of OOP world for almost decades now.

        Sure you can write very readable OOP code. Every time I do that I see great discomfort on the face of Java programmers during code reviews. For example over all this years, I haven't met a single Java programmer who could explain why Beans are good, or even needed. So you have these religious rituals Java programmers do.

        They feel like their job protection scheme is at risk.

        • vbsteven 2015 days ago
          I feel like this comment is exactly what the article refers to as the typical straw-man argument against OOP. Yes, a lot of bad enterprisey OOP code has been written in the last 20 or so years, but that does not mean things like design patterns, dependency injection and the Spring framework don't have their place. Don't throw the baby out with the bath water.

          Yes, AbstractClassFactorySingletonWhatever classes do exist in the Spring framework, they are there to help abstract away the complexity and flexibility of the framework so you as the application programmer can have a simple, productive programming environment, and you can still reach into the complexity (and extend it) when you need to.

          Beans, interface inheritance, implementation inheritance and dependency injection are the building blocks of a programming environment that allows me to be very productive, and still write maintainable, testable, extendable and configurable code.

          • letstrynvm 2015 days ago
            > and still write maintainable, testable, extendable and configurable code.

            "reusable"... reusable code? That was the original point of OOP.

            After a few years being a believer n the late 90's as a C++ Ubermensch, I had the horrible realization neither I nor anyone else would reuse any of the classes I had given beautiful interfaces to, and carefully documented. Any time I spent planning for reuse I might as well have spent staring out the window.

            Of course who needs to reuse your own classes when you have the huge canker Boost you can drag around with you from task to task.

            • Gibbon1 2015 days ago
              When I was doing hardware in the 80 early 90's I came to the conclusion that the 'reusable code' fans were all missing a point.

              You are either writing a library,a framework, or something like that. Or you are implementing some 'business logic'

              The point of the former is reusability. The point of the latter is utterly not and you should not waste your company's time and money or worse let schedules slip because of it.

              Some further thoughts over the years.

              Reuseable code must have a public, well designed, sable, and documented API or it won't ever be reused. Be honest how many programs are going to reuse this code? Enough to justify all the above? Didn't think so.

              One of the problems with reusable code is dependencies. OOP code bases tend to have more than the ordinary number of dependencies.

              • letstrynvm 2015 days ago
                Yes that's basically what I found too.

                To practice OOP, its approach is every class should be a "library". Procedural code that doesn't have a class context is felt to be a shameful throwback to an earlier era degenerate C practices. From before some people feel they starting writing "good C++" apparently.

                • Gibbon1 2013 days ago
                  I think having done hardware first gives me a slightly different perspective. Hardware designs get reused a lot, but usually not as a drop in, it gets reworked at each reiteration. Either that are it's something you buy off the shelf in which case the design interface is absolutely stable. Think a uP chip or core. They'll fix bugs and shrink the die, but they never ever change the abstract design. You can still by 8051 cores and IC's and that design has not changed at all in 40 years.

                  Compare with business logic. The problem is the spec and use case keeps moving. Moving way too fast to be a good library or a framework.

            • vbsteven 2015 days ago
              I did not mention reusable anywhere in my comment. If you are writing code to be reusable, you are writing a library and in that case you better make sure that it is a separate project or module so no caller-specific details are leaking into your library.
              • letstrynvm 2015 days ago
                Yeah... you mentioned

                >>> maintainable, testable, extendable and configurable

                but not reusable. I'm not blaming you, or disagreeing... C++ didn't produce reusable results for me either.

                I dunno what you were doing 20 years ago, but I was doing this... there wasn't any problem with the classes being unclean or the boundaries cut in the wrong place. It was simply I was almost never going to use those classes a second time. If they had been C functions, I would not have needed them a second time, either, almost always.

                After a year or two I realized that being the case, the entire loving OO encapsulation of then was not simply worthless but an active waste of time. And I went back to C.

                If you get value from other C++ things that make it worth paying the price, that's great. But file away for a possible future horrifying 3am realization, perhaps nothing is worth the price of a gyre like boost, and just writing it in high quality C may be a better answer.

                • slededit 2014 days ago
                  In modern C++ the real reason is RAII automatic cleanup. Of course you really only need a dtor for that.
          • jondubois 2015 days ago
            Yeah I have a feeling that some companies don't trust their engineers at all and so they want them to use as many tools as possible to reduce the likelihood of mistakes.

            That's probably what functional programming, 100% code coverage and code linting trends are really about; allowing companies to not have to trust their engineers. I think this is a futile effort. If you want better code, just hire better developers with more experience who can be trusted. If you are not able to identify and recruit such people, then you shouldn't be a manager.

            Bad developers will find a way to write terrible code; in any language, any paradigm, with strict linting rules enforced and even with 100% test coverage.

            It really bothers me that most companies feel perfectly fine trusting financiers, lawyers and accountants with extremely sensitive company secrets and business plans but they absolutely refuse to trust engineers with their own code.

            • almostdeadguy 2015 days ago
              Where are there company executives that actually know and care about these kinds of things?

              Some of what you're talking about can be overdone by engineers (i.e. 100% line coverage), but a lot of what you're talking about are tools engineers have developed to make their lives easier and to improve their code quality. A linter checks for common mistakes, clarity problems, and can help ensure code is written in a consistent style which improves readability. Functional programming is a coding style that doesn't always mesh well with the languages it's tried out in, but when it does it can provide enormous benefits in local reasoning about code and I think a large part of why it's seeing a resurgence is the experience of engineers having to build large, complex systems that become baroque and difficult to reason about in OO/procedural style.

              This idea that good engineers have perfect competency and write code that immune from the problems these tools help solve is absurd and totally disconnected from the reality of the challenges involved in building software. Even the best, and most respected engineers write code that is riddled with bugs, and there are CVEs to prove it.

              Engineering tools and new paradigms weren't invented to add friction and police developers, they were invented to aid our very real, human cognitive limits in writing software. If anything some of these things make the process more enjoyable and far less error prone.

              • jondubois 2014 days ago
                I think that a lot of the tools and processes that we use these days are counter-productive overall.

                I think that a lot of rules enforced by code linters are rules that are good 90% of the time, but they're bad 10% of the time.

                Having 100% test coverage is great to guarantee that the software behaves like it's supposed to but it's terrible when you want to change that behavior in the near future. Most systems should be built to handle change. Having 100% test coverage disincentivizes change; especially structural changes.

                Also, more advanced project management tools like Jira don't actually add value to projects; they just give dumb executives the illusion of having more visibility into the project - Unfortunately, they cannot really know what's going on unless they understand the code.

                • almostdeadguy 2013 days ago
                  I don't disagree that things like testing are overdone. I'm currently consulting on a project that was largely written by another consultant who was fanatical about TDD. Most of the code was very poorly structured, but had hundreds of brittle tests (where it wasn't exactly clear what was even being tested). Since this project is still pretty new, one of the first decisions we made was to disable the tests and do a major refactoring (then rewrite maybe half of the tests). As a result we've been much more productive and the app is if anything _more_ stable now and much easier to understand.

                  And I agree that project management tools (especially JIRA) are pretty frustrating and provide a dubious value proposition. Project management is going to need to have a reckoning some day w/ the fact that it's very difficult to glean any insight from estimates and velocity (and that very few places actually even measure these things in a consistent fashion). The only value to "agile" and the ecosystem surrounding it IMO is that it de-emphasized planning to some extent and pushed the importance of communication. These are ultimately human problems though, not ones that can be solved by technology. Also I think there's a laughably small amount of interest in giving software engineers the time to train and learn.

                  Software engineering as a discipline is still in the dark ages. We don't really have solid data on the impact of things like programming language choice, development methodologies, etc. Most of the conceived wisdom about these things is from people who are trying to sell their services as consultants and offer project management certifications. That's not to say that there is no value in tools, techniques, language features, etc. to software engineering, there's a huge value, but we need to better understand how contingent the advantages to these decisions are.

                  As always, I think the things en vogue in software engineering are somewhat of a wash. I'm very happy that statically typed functional programming languages have seen a resurgence because I think they offer an enormously better alternative to TDD-ing your project to death by allowing you to make error states unrepresentable and discouraging mutable state (also I think encoding invariants in types actually makes it faster to adapt to changing requirements, especially w/ the refactoring abilities in editors). On the other hand, there are lots of bad ideas about architecture and scaling today, particularly with the "microservice" trend and I think people poorly understand how these things were mostly created as a response to organizational growth, not per se in search of better performance or some kind of invariant best practices about architecture.

                  In any case, I think there is an inherent tension w/ management that has indirectly lead to some of these practices, but I would push back on the idea that we only adapt some of these to satisfy management. Tools that push you towards correct and safe code IMO make the job less anxiety-inducing and gives your code a chance to actually meet the expectations users invest in your project by using it. In my experience these are the kinds of things management couldn't care less about until it impacts their profitability.

          • barrkel 2015 days ago
            Reusable, testable, extensible and configurable classes are the reason for bad enterprisey OOP code.

            Testability with the poverty of tools available in languages like Java is the single biggest driver, and the alleged induced "improvements" in extensibility and factoring makes people feel happy about making all their objects fully configurable, all their dependencies pluggable and replaceable so they can be mocked or stubbed, with scarcely a thought for the costs of all this extra abstraction.

            Extensible and configurable code is not an unalloyed virtue. For every configuration, there is a choice; for every extension, there is a design challenge. These things have costs. When your extension and dependency injection points only ever have a single concrete implementation outside of tests, they bake in assumptions on the other side of the wall and are not actually as extensible and configurable as you think. And your tests, especially if you use mocking, in all probability over-specify your code's behaviour making your tests more brittle and decreasing maintainability.

            And parameterization that makes control flow dependent on data flow in a stateful way (i.e. not mere function composition) makes your code much harder for new people to understand. Instead of being able to use a code browser or simple search to navigate the code, they must mentally model the object graph at runtime to chase through virtual and interface method calls.

            I think there are better ways to solve almost every problem OOP solves. OOP is reasonably OK at GUI components, that's probably its optimal fit. Outside of that, it's not too bad when modelling immutable data structures (emulating algebraic data types) or even mutable data structures with a coherent external API. It's much weaker - clumsy - at representing functional composition, with most OO programming languages slowly gaining lambdas with variable capture to overcome the syntactic overhead of using objects to represent closures. And it's pretty dreadful at procedural code, spawning all too common Verber objects that call into other Verber objects, usually through testable allegedly extensible indirections - the best rant I've read on this is https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo... .

          • kamaal 2015 days ago
            >>but that does not mean things like design patterns, dependency injection and the Spring framework don't have their place.

            Part of the reason why Go has picked up so well, And languages Python and Perl still such widespread use is because many people aren't developing monstrous megalith applications like in the pre-2000's.

            You needed all these big complexity management features because people were padding every tiny little feature callable from 'public static void main', then you see there are some very tiny far abstract patterns of code that overlap in such a giant megalith, and then to make use of that you unload the entire design pattern text book to make it happen.

            This was totally unnecessary. In fact its perfectly ok to have 5 - 10 % duplicate code if it comes at the expense of simplicity and maintainability of the remainder 95% fo the code.

            The overall rise of micro services architecture and those trends are only going to increase this way of developing software.

            >>Yes, AbstractClassFactorySingletonWhatever classes do exist in the Spring framework, they are there to help abstract away the complexity and flexibility of the framework so you as the application programmer can have a simple, productive programming environment, and you can still reach into the complexity (and extend it) when you need to.

            I'm not sure, but there are other language communities which solve the same problem without writing 30 classes just to do variable++

            >>Beans, interface inheritance, implementation inheritance and dependency injection are the building blocks of a programming environment that allows me to be very productive, and still write maintainable, testable, extendable and configurable code.

            Yet to see such an environment.

            It feels like Java community programmers create tons of complexity and then tons of complex frameworks to solve that complexity that shouldn't even exist at the first place.

        • gerbilly 2015 days ago
          One argument that I often don't see mentioned when talking about OOP code, especially the admittedly awful enterprise type, is that this style of coding is happening for a reason.

          I think the reason is this: you have a large sprawling codebase, consisting of tens of thousands of 'business rules' or more, and you have to find a way to allow cheap run of the mill programmers to make changes to those business rules in an approachable manner.

          I don't think it's fair to evaluate enterprise OOP code based on the style you would prefer when undertaking a standalone project, even a large one, when one of the very problems that this enterprise style is trying to solve is how to have hundreds of fairly green developers work on the codebase.

          Now, as an exercise imagine taking this social problem I've described above, and implementing it in your favourite programming style, be it lisp/scheme, functional programming, JS or whatever you prefer, and try to honestly imagine whether it would hold up any better under those conditions.

          • bigato 2015 days ago
            Do you mean that in order to allow green programmers to change big codebases you make the code more complex? I honestly don't understand.
            • gerbilly 2015 days ago
              Sorry not quite, I believe that people use OOP in enterprise settings perhaps thinking:

              * Encapsulation might make it safer for programmers to modify code without affecting other areas.

              * Inheritance will make it easier to set standards for large numbers of high turnover programmers.

              and that then it grows complex for various mostly social factors.

              The testable hypothesis in this is that if you started with another programming model, and applied the same social pressures to it, it would end up just as ugly and complex.

              These codebases have hordes of programmers banging away on them, with little regard for the bigger picture which makes the code ugly and inconsistent.[1]

              Architects then react to this and attempt to enforce standards by introducing patterns and subclasses and facades etc, which is what makes it complex.

              Basically, I'm saying that the main straw-man used to argue against OOP: the enterprise codebase is chosen for the wrong reasons.

              Put any other programming style under the same pressures and you'll end up with a similar big ball of mud.

              [1] https://en.wikipedia.org/wiki/Tragedy_of_the_commons

    • ilovecaching 2015 days ago
      Also for those of you doing OOP looking to try something different:

      Java/C++ -> Rust: It will feel familiar to you with its C style syntax and multi-statement function bodies. It takes the best parts of many different paradigms, and marries them in a very coherent way. It's performant, has incredible tooling (cargo, rustup), and the community is great.

      Python/Ruby -> Elixir: Elixir has the Phoenix framework, great support for web, and supports massive concurrency using the Actor model. Jose did a great job cleaning up the Erlang syntax, and it's gradual typing and heavy use of macros will feel immediately familiar to you.

      • AsyncAwait 2015 days ago
        Go also has a non class-based OO.
    • fooyc 2015 days ago
      It looks like that you are working on stuff that may not benefit from OOP. You seem to be doing heavy « computations », you care more about the data than the logic around it. Probably Haskell suits your usecases more.
      • ilovecaching 2015 days ago
        Actually I work primarily on web services. We benefit from Haskell in that HKTs allow us to model our service completely in the type system. Our Rest and GraphQL API are completely modeled in a type level DSL. Once we've ingested data, which can then be validated automatically using types, we can then express our data transformation in a way that makes the transitions extremely transparent in our code.

        Our backend services are written in Rust for things that need efficient computation.

        • codetrotter 2015 days ago
          That sounds very interesting. Did you open source any parts of your code, write any blog posts about it or give any public talks about it? I would love to read/watch whatever you have available to the public :)
        • ww520 2015 days ago
          Sounds like the service in your case is a sessionless gateway that does data transformation and dispatching. It fits the functional style.
        • westoncb 2015 days ago
          I do think this echoes fooyc's point though: this does not sound like an area that would benefit from OOP. Sounds like an ideal use-case for functional.
        • fooyc 2015 days ago
          Looks like functional programming sweet spot: data transformation.
    • Consultant32452 2015 days ago
      My personal anecdotal experience is that OOP lends itself well to very very large codebases. A language like java with packaging, classes, and encapsulation strongly encourages some meaningful organization. Even if that organization is implemented poorly by the user, it's better than what I have seen users create in the real world with languages like C.

      If you have a project where a single person could reasonably understand all the components then maybe OOP is overkill.

      I also recognize others may have had an equal and opposite experience.

      • josephg 2015 days ago
        I was a programming teacher some years ago and I would sometimes grade two submissions, which had both gotten full marks from the automated tests but where one was 150 loc and the other 500 loc. The most surprising part was that if I read the longer submission first, it would never really seem like there was that much excess code to take away. But obviously there was.

        > My personal anecdotal experience is that OOP lends itself well to very very large codebases.

        In my experience Java / C++ style OOP creates very very large codebases in the same way. I think its quite surprising that many engineers don't seem to have any idea how much waste there is in their "clean" java code. (Or C++ that follows the same style).

        I also almost always find it far easier to read dataflow oriented code or functional code compared to their OO counterparts because all the state transitions are made absolutely clear. In OOP, your system state is spread across every file in the project. Compare to, say, a website implemented using React/Redux. I can inspect a single object to see the entire state of the application. When there's a bug, usually either that object has the wrong value (so I know how to fix it). Or the object has the right value and the bug is in the rendered component. Easy peasy. In comparison, in a java program the bugs often show up between classes. They end up relating to the relative timing of function calls that mutate state. Or the lifecycle of objects conflicting in weird ways. Its much worse for comprehension and much more work to debug. (And yes, I've spent years working in both styles professionally.)

        • Consultant32452 2015 days ago
          I have a hard time getting on board with the "waste" or loc argument. The things that matter most to me are readability, organization, maintainability, etc. If the structure that provides those things results in there being more lines of code, or more "waste" then that's a price I'm happy to pay.

          The context you describe being a teacher is exactly the kind of case I was suggesting OOP may not be beneficial. If you're in a programming class you are generally not maintaining code bases with millions upon millions of lines of code. In that case, in a case where a single person is likely capable of understanding the entire code base, OOP probably isn't bringing any serious benefits.

          • badestrand 2015 days ago
            > I have a hard time getting on board with the "waste" or loc argument. . The things that matter most to me are readability, organization, maintainability, etc

            I think people also have different ideas on readability. Some prefer a single file with 2k lines and some prefer 40 small files in 12 folders with 50 lines each. Like the parent says you can often write the same code in 4x length, be it to prepare for future features or just to have it look "clean".

            "Good code" is different for different people.

            • josephg 2015 days ago
              Maybe. I suspect most people who say they prefer the java style are simply wrong. I don't think they've spun up on enough new projects to notice the extra weight that increased size and scale brings. Or they're assuming that the extra complexity is all necessary, and they're mistaken the same way I was while grading those assignments. (Aside: Isn't it super weird how reading code is so rarely encouraged at school?)

              I suspect you could objectively measure this - take two implementations of the same problem; one small and one large but where the implementations do the same thing. For example, write a simple website using a stateful OO style. Then write the equivalent code using the mostly stateless react component style. The latter would be functionally equivalent, while using less code. The latter would also use much less local hidden state.

              Then measure how long it takes new people to start making meaningful changes to the two respective codebases. I don't think its a matter of opinion or preference. I expect that the functional component model would come out as a clear productivity winner.

              The easiest code to change is code you never needed to write in the first place.

              • Consultant32452 2015 days ago
                The use case I gave is not a simple website, it's a code base with millions of lines of code. I agree that a simple website or small school project is a different situation with different needs.
        • ilovecaching 2015 days ago
          We came to the same conclusion! Hidden state does make debugging difficult. Glad to hear our data points corroborate.
      • ilovecaching 2015 days ago
        Have you tried something with typeclasses, ADTs, or some form of interfaces? C really doesn't provide any tools to make abstractions and lacks the basics, like namespaces.

        My experience with OOP is that it generally drives people to very large codebases that are hard to reason about at scale. Usually it brings in a framework of some sort to manage the complexity of wiring objects together in a manageable way. For example, Java Spring uses annotations to magically wire objects together.

    • westoncb 2015 days ago
      For some reason 'arguments' against OOP seem to follow a common pattern. You have said many things against OOP, but you haven't actually presented an argument for why it's bad. I'll present each of your assertions here individually to clarify.

      > OOP is prove a poor model for computation

      > OO code is almost always significantly more complex and error prone than an equivalent computation written in a concurrent, functional, or structured paradigm.

      These claims may or may not be true, but they aren't very useful at all if you don't provide a justification as well as merely asserting them.

      > Recent trends in language design (see Rust, Go, Elixir) also seem to be abandoning OOP in favor of other models

      There are a number of ways to account for these trends besides OOP being an intrinsically bad model. The most obvious one is that there are fashions in language design, and right now OOP is not fashionable. We already know that; it's not a strong argument against it. Ironically, many in the OOP opposition use the same argument to justify OOP every getting popular in the first place: "it was just fashionable."

      > OOP provides competing ways of abstracting behavior that in Haskell we can model with type parameters and constraints.

      > Objects are not truly encapsulated in the way Erlang processes are and a poor fit for SMP.

      You have pointed out here that more classical OOP languages do things differently from Haskell and Erlang. This should be expected and is not an argument against those OOP languages. (Yes, you could say, "Erlang is better at concurrency" because of the way in which it's different—but my understanding is it's pretty well accepted that Erlang is sort of freak of nature here, so it's not a good argument against OOP generally.)

      > Objects also pathologically hide data in attempt to manage mutability, making it impossible to reason about the memory layout of the program.

      They do hide data, but the 'pathologically' is something you've added on your own. There is a design philosophy in which this data hiding plays an important, positive role. When you say, "making it impossible to reason about the memory layout of the program." —this sounds to me like missing the point of that design philosophy: the purpose (and oftentimes tradeoff) of higher-level languages is that you don't need to personally manage these details. I think it's largely an application-dependent thing: you many be writing code that requires that, but not all interesting software hinges on low-level performance tuning.

      > OOP is a toolkit for building bad abstractions:

      > ... abstractions that do not easily model computation

      > ... that hide data

      > ... and has tended to create overly complex solutions to problems that are often full of errors that a language focused more on type expressivity could catch at compile time.

      Another collection of unjustified assertions, except the 'hide data' part which I accounted for earlier.

      So across ~10 negative assertions about OOP you have 3 quasi-justifications: newer languages aren't using OOP as much, hiding data is bad, and Erlang is better for SMP.

      • loup-vaillant 2015 days ago
        The pattern you talk about is mainly a product of not wanting to squeeze a whole essay into an HN comment.

        I also suspect the problems with OOP are hard to communicate. I for one always had a problem with OOP, but I could never quite point it out. Sure, when faced with an OOP design, I could almost always find simplifications. But maybe I never saw the good designs? Maybe this was OOP done wrong?

        I do have reasons to think OOP not the way (no sum types, cumbersome support for behavioural parameters, and above all an unreasonable encouragement of mutability), but then I have to justify why those points are important, and why they even apply to OOP (it's kind of a moving target). Overall, all I'm left with is a sense of uneasiness and distrust towards OOP.

        • westoncb 2015 days ago
          > The pattern you talk about is mainly a product of not wanting to squeeze a whole essay into an HN comment.

          That may very well apply to the GP's comment—but, my observation of the pattern is derived from a mix of mini-essay comments, and articles people are writing on Medium or their blogs or whatever, where the space constraints aren't so tight.

          There are a couple things you'll regularly find: laughably bad straw-men (GP is free of these), overly vague statements that only survive scrutiny because of their vagueness (e.g. when the GP says OOP produces "abstractions that do not easily model computation"), and unjustified claims.

          The net effect is something that sounds bad, but if looked at closely carries very little force.

          I suspect the reasons for it are:

          1) Actually evaluating a language paradigm is more difficult than these folks suspect. Their view matches their experience and they assume their experience is more global than it really is. Additionally, we don't have a mature theoretical framework for making the comparisons.

          2) People are arguing for personal reasons. They have committed themselves to some paradigm and they want to feel secure in their justification for doing so.

          • Twisol 2015 days ago
            > Additionally, we don't have a mature theoretical framework for making the comparisons.

            This is really the problem. As much as I have strong opinions and beliefs about how to architect code, every argument I come up with boils down to some flavor of "I like it better this way". Which is true -- I do like it better this way -- but hardly actionable, and it doesn't get at the essence of why I like it better.

            The problem with making everything an object -- or more precisely, having lots of mutable objects in an object space with a complex dependency graph -- is that it becomes very hard to model both how the program state changes over time and what causes the program state to change in the first place. I think the prevailing OOP fashion is to cut objects and methods apart into ridiculously small pieces, which takes encapsulation and loose coupling to an extreme. This gives rise to the popular quip, "In an OOP program, everything happens somewhere else." I can't think straight in this kind of setting.

            I believe that mutable state should be both minimized and aggregated. As much as is humanly possible, immutable values should be used to mediate interactions between units of code (be those functional or OO units), and mutation should occur at shallow points in the call stack. Objects can work well for encapsulating this mutable state, but within the scope of an object, mutation should be minimized and functional styles preferred.

            Using a functional style doesn't mean giving up on loose coupling or implementation hiding. Rust, Haskell, and plenty of other languages support these same concepts in the form of parametric polymorphism, e.g. traits or typeclasses. It does mean giving up on the idea that you can mutate state whenever it's convenient. Instead, you have to return a representation of the action you'd like to take, and let the imperative shell perform that action.

            Speaking of imperative shells and functional cores, Gary Bernhardt's talk called "Boundaries" is an excellent overview of this kind of architecture [1]. There was also a thread here on HN about similar principles [2].

            [1] https://www.destroyallsoftware.com/talks/boundaries

            [2] https://news.ycombinator.com/item?id=18043058

            • westoncb 2015 days ago
              That makes a lot of sense to me. Looking forward to checking out "Boundaries".

              Btw, one other idea I've had on the subject is that the problems with mutable state can be mitigated if we were able to more easily see/comprehend the state as it's being modified by a program; without that capability the only recourse we're left with is our imagination, which of course is woefully inadequate for the task. You can see more concretely what I'm talking about in my project here (video): http://symbolflux.com/projects/avd

              From what I've seen, structuring a program to not modify state is almost always more difficult than the alternative[0]. There are certain problems where this difficulty is justified (because of, e.g., reliability demands); but I think most problems in programming are not those, and if we could just mitigate the error-proneness of state mutation, that may leave us at a good middle ground.

              [0] The exception is when you're in a problem domain that can naturally be dealt with via pure functions, where you're essentially just mapping data in one format to another (i.e. no complex interaction aspects).

              • Twisol 2015 days ago
                Oh, that's very cool! I had a similar idea years ago, but I didn't have the technical chops to pursue it at the time, and I ended up losing interest. I think this would actually be even more useful in the kind of architecture I'm describing, since the accumulated state has a richer structure, and many of the smaller bits of state that would be separate objects are put into a larger context.

                > From what I've seen, structuring a program to not modify state is almost always more difficult than the alternative

                You're not wrong! I don't think we should get rid of mutable state, but I do think we should be much more cognizant of how we use it. Mutation is one of the most powerful tools in our toolbox.

                I've found that keeping a separation between "computing a new value" and "modifying state" has a clarifying effect on code: you can more easily test it, more easily understand how to use it, and also more easily reuse it. My personal experience is that I can more easily reason locally about code in this style -- I don't need to mentally keep track of a huge list of concepts. (I recall another quip, about asking for a monkey and getting the whole jungle.)

                There is a large web app at my workplace that is written in this style, and it is one of the most pleasant codebases I've ever been dropped into.

                • westoncb 2015 days ago
                  Interestingly, I think I built that project with an architecture somewhat reminiscent of the 'boundaries' concept (still just surmising at this point). It's a super simple framework with two types of things 'Domains' and 'Converters'. Domains are somewhat similar to a package... but with the boundaries actually enforced, so that you have to explicitly push or pull data through Converters to other Domains; Converters should just transform the format from one Domain to that of another (they are queue-based; also sometimes no translation is necessary).

                  I'll quote from the readme:

                  > This Domain/Converter framework is a way of being explicit about where the boundaries in your code are for a section using one ‘vocabulary,’ as well as a way of sequestering the translation activities that sit at the interface of two such demarcated regions.

                  Inside each Domain I imagine something like an algebra... a set of core data structures and operations on them.

                  But yeah, I have very frequently thought about visualizing its behavior while working on that visualizer :D

                  Is your research related to programming languages?

                  Also I'm going to have to think about "computing a new value" vs. "modifying state" —not sure I quite get it...

                  • Twisol 2015 days ago
                    > Is your research related to programming languages?

                    Yep: I just finished a Master's degree with a focus on programming language semantics and analysis. I'm interested in all kinds of static analyses and type systems -- preferably things we as humans can deduce from the source without having to run a separate analysis tool.

                    > Also I'm going to have to think about "computing a new value" vs. "modifying state" —not sure I quite get it...

                    It's kind of a subtle distinction. A value doesn't need to have any particular locus of existence; semantically, we only care about its information content, not where it exists in memory. As a corollary, for anyone to use that value, we have to explicitly pass it onward.

                    On the other hand, mutation is all about a locus of existence, since ostensibly someone else will be looking at the slot you're mutating, and they don't need to be told that you changed something in order to use the updated value. (Which is the root of the problem, quite frankly!)

        • jbergens 2012 days ago
          You do realize that a lot of people could have "a sense of uneasiness and distrust towards FP"?
      • dorgo 2015 days ago
        I upvoted you. Why OOP is bad: In 8 years professional (for money) programming experience i had zero use cases for OOP. Every time I tried to use OOP it backfired and I abandoned it. Maybe I just never really understood OOP or maybe I already use OOP all the time without calling it OOP.
        • westoncb 2015 days ago
          My main conclusion on the subject is that it really depends on what domain you're coding in.

          There are plenty of applications I would never use OOP for. I use Elixir for my backend work. I tend to use OOP for interactive simulation/game sorts of applications.

        • da02 2015 days ago
          What were the types of problems and systems you have worked on? Which languages and programming styles (eg functional) did you end up using to solve them?
          • dorgo 2012 days ago
            Mostly math, optimization and data transformations. Since java8 I use functional style (map, filter, monads..). A lot of business logic is also in sql queries.
      • tcbawo 2015 days ago
        Bad code is bad code, no matter which language. Languages won't save you from poor design choices.

        OOP is popular in large enterprise systems because it (purports to) promote encapsulation and abstraction, which allows many teams/people to interact. Organizations may like OOP because it helps reinforce their drive for independence and fiefdom (see Conway's law https://en.m.wikipedia.org/wiki/Conway%27s_law). Whether it is the best paradigm, the most practical, 'just good enough', the wrong choice is anecdotal. Also, in experience many companies would rather fail conventionally than succeed unconventionally.

        • antidesitter 2015 days ago
          > Bad code is bad code, no matter which language.

          It’s easier to write bad code in some languages than others.

          > Languages won't save you from poor design choices.

          They do. They save you from entire classes of bugs, and make it less easy to shoot yourself in the foot.

          Do we need to have this conversation every time we talk about language design? Do you think the tool you use to achieve a task doesn’t matter?

          • pka 2015 days ago
            > Do you think the tool you use to achieve a task doesn’t matter?

            I think many people do think that, with a twist; specifically, when presented with a higher level language they'd argue "it's just a tool", while a lower level language would be "the wrong tool for the job".

            I.e, for a hypothetical Ruby programmer, Haskell = "just a tool", C = "the wrong tool".

      • The_rationalist 2015 days ago
        Such a precise epistemic analysis should be incentivised on hacker news. Thank you.
      • didibus 2015 days ago
        You're right, people go with a all or nothing. Yet no OO is the same in two language. This is problematic both ways, I have a lot of people ask me how the hell do you model entities in FP? Because they equate entity modeling with OO.

        From my view, here are the concrete issues I have with some of the OO flavors out there.

        1) It encourages shared mutable state.

        OO creates a new layer of shared state, the object fields. In its very essence, the idea is to have methods which accesses and modifies the object state through direct access of its fields. Thus each field is mutated by the object methods which directly access them. You need to look at the method code to know what state they read and write too. You can not make the fields immutable, because that renders non read only methods useless. You must thus coordinate access of the fields between the methods using explicit locks. Over time, in practice, it also creates massive classes with too many fields and too many methods that only make use of a subset of them, degenerating into even more of a globally shared state structure.

        2) It makes dependency injection trickier and thus discourages its use.

        Once again, the idea of methods to have direct access to fields is the cause of this, it means that methods don't have their dependencies injected, instead they go find them themselves, through direct access. Testing becomes hard, configuration is pushed down the stack inside the methods and reuse is made harder.

        3) It makes inversion of control trickier and thus discourages its use.

        Because code can only be passed around wrapped inside an object, and objects are heavy constructs requiring a lot of verbosity to create: a new file is required, a class must be defined, an instance must be created, etc. It means that in OOP it is rare to see code being injected from caller to callee. Instead, conditionals creep inside the callee, and configuration parameters are passed it.

        4) It handles stateless code poorly, and thus discourages its use.

        Code that requires no state over time, aka stateless operations must be wrapped inside a class for no good reason. OO provides nothing to such code. OO is designed for statefull code. A class with no fields, it's not useful. Why do I need one if all my code is stateless? So people start using them like namespaces.

        5) Inheritance is too easy to mess up.

        Inheritance as a mechanism for code reuse appeared to be pretty smart at first. The problem is it turned out its pretty hard to do right. You soon find yourself with hidden state from parents and confusing override hierarchies, which forced us into single inehritance chains, which limited code reuse, etc. Bottom line, it just created ton of hidden coupling.

        6) Objects are not extendable from the outside.

        You can't add functionality or state to an object from the outside. You need to modify the source code of the class directly to do so, or rely on defining new subclasses through inheritance. This minimizes code reuse, and encourages object code to grow ever so bigger as more and more features are added.

        That's all I can think of for now. Now, some flavours of OOP work differently in that some or all of these problems might not exist or have solutions to them. Which is why I agree with you, it's best to know the actual problems so you can spot them. Just saying something is OOP doesn't imply all of them will exist.

        A lot of languages allow stateless subroutines to exist on their own and offer a seperate namespace system not conflated with the OO layer. Trait or mixin like systems enable open extension. Inehritance can support multiple parents, or composition is used in its place. Some allow subroutines to also be passed around, not needing to be wrapped in an object. Dependency injection frameworks were added to simplify and encourage its use. Value objects allow a bit more support for immutability. Etc.

        FP doesn't suffer from these issues though. That said, it has others in its place, and different FP languages have also found different solution to them. That said, in my experience with many languages that were more OO oriented and ones that were more FP oriented, I found FP overall had less issues and had found cleaner solutions to its limitations.

    • blub 2015 days ago
      You develop web services professionally in Haskell, Erlang and Rust, but you think that Java-style OOP is too complex?
      • ilovecaching 2015 days ago
        Yes
      • kroltan 2015 days ago
        I don't think it is the case that a certain programming paradigm can be intrinsically harder, maybe only less practiced.

        OOP / imperative style is almost the norm nowadays, but that doesn't imply it's the "easiest", just the one that got the most momentum (which can be attributed to social factors moreso than technical ones), and thus is widely taught and talked about, which helps with education on such style.

    • zumu 2015 days ago
      Are you hiring? Where are these mythical FP jobs?
    • joering2 2015 days ago
      Someone with 15 years programming OOP told me once: here is the best way to describe object oriented programming: you asked for object “monkey”, and you got the whole jungle, as well as monkey’s bananas.
      • 725686 2015 days ago
      • mehrdadn 2015 days ago
        I don't really follow this. A fleshed-out example describing the trade-offs is almost mandatory for this kind of criticism since this could be very easily due to a misunderstanding or misapplication of OOP rather than a problem with it.
        • kirkules 2015 days ago
          I don't have enough experience to agree or disagree with the quote you're responding to, but I think the intent was in part to claim that OOP gets misapplied enough that if you're working with a large enough group or company, that's what you'll end up with.
      • ilovecaching 2015 days ago
        Exactly, you get a tangle of objects and data that's impossible to reason about, and isn't made explicit in the types.
      • walterstucco 2015 days ago
        You're quoting Joe Armstrong, creator of Erlang

        > Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.

    • arendtio 2015 days ago
      > my view on OOP (let OOP denote class based OOP as found in Java or C++)

      Maybe you should take a look at a real OOP language like Smalltalk before you tell us why OOP is a bad idea. Otherwise, I might tell you why consumers will never adopt cars as I drove a Trabant once.

      • ilovecaching 2015 days ago
        Smalltalk OOP looks almost exactly like Erlang gen_servers and shares a lot of nice properties with them. Unfortunately the concept has been twisted into it's current day meaning that's akin to the OOP we find in Java, C++ etc.
      • mercer 2015 days ago
        Did you read the stuff between the parentheses?
        • arendtio 2015 days ago
          In fact, I did. But I think it is wrong to judge OOP based on crude implementations. That is the reason why I added the Trabant example:

          > The Trabant was loud, slow, poorly designed, badly built, inhospitable to drive, uncomfortable, confusing and inconvenient. (source: https://en.wikipedia.org/wiki/Trabant)

          Java and C++ are both great tools for specific jobs, but from an OOP perspective, they are badly designed. So if you want to discuss OOP as a design choice, it would be unreasonable to discuss it based on those poor implementations. Smalltalk, on the other hand, has its own set of problems, but a poor OOP implementation is not part of it.

          • mercer 2014 days ago
            Ah, I see what you mean. I do think it's valuable/reasonable to discuss OOP as it is 'in the wild', but I can also see how it's a bit of a dead horse to beat, and discussing what could be (or didn't become the mainstream) is at least more interesting.
      • crackerbarrmnd 2015 days ago
        Smalltalk is not an especially impressive language in 2018 and inspection and reflection aren't isolated to OOP today either.
    • shady-lady 2015 days ago
      > Becoming a professional Haskell and Erlang developer really shifted my view on OOP (let OOP denote class based OOP as found in Java or C++).

      Did you have many years experience with Java/C++ beforehand?

      • ilovecaching 2015 days ago
        I started as a C++/Python developer. I learned C++ and Python in school, and wasn't exposed to Haskell or Erlang until later in my career. I've also professionally written Ruby, Java (Swing/Spring), Rust, and Go.
    • n0n 2015 days ago
      Interesting view. How should i, as a sysadmin who just writes bash and powershell scripts, start to learn _serious_ programming? Is it still worth to force myself into OOP?
      • blub 2015 days ago
        If you want to get a job, yes.

        The world is built on OOP, it's just that too many like to use it as a punching bag.

      • amirouche 2015 days ago
        function are in the gray area in Python.

        I argue that you should start simple with functions and IF you see a use case for classes use them.

        To be able to make a reasonable choice, you must learn OOP at least the OOP machinery of your language.

        A few hints:

        a) Python's abc or Java Interfaces serves as template to implement multiple times the same behavior in different contexts

        b) If you don't inhirit the class, the class is useless, except if you use the class as an interface (see a)

        c) A class is more complex than a function ie. more guns to shot yourself in the foot.

        d) think function pointer / first-class function. Many times, you can avoid a class by passing a function as parameter e.g. filter.

        e) think function factory ie. a function that returns a function.

        f) think pure function / side effect free functions. This is a BIG life savior. A function without side effects is much simpler to test that a function with side effects. Of course, you can not avoid all side effects (io et al.) or mutations, so build your abstraction so that the side-effects / mutation to happen in a well known place.

        g) keep functions small

      • 1ark 2015 days ago
        Start writing some of the scripts in Python, for example, quite popular in both sysadmin world and _serious_ programming world.
    • stuaxo 2015 days ago
      I haven't done erlang or haskel, but this sums up what I've been thinking / trying to get around for years.
    • Chabs 2015 days ago
      I think you are conflating OOP with type erasure. Most OOP languages support type erasure (including rust!), but that's far from all there is to it.
      • ilovecaching 2015 days ago
        How so? I believe I was addressing the fundamentals of OOP: Classes, methods, inheritance, overloading, visibility, etc.

        Also, Rust, while allowing the assigning of behavior to a type, is explicitly not OOP. Check it's Wikipedia page (which doesn't include OOP in the list of paradigms) and the O'Reilly book (which says Rust isn't OOP).

        • Aloisius 2015 days ago
          Under the original definition of OOP as various alternate definitions, Rust would be OOP. This is all covered here:

          https://doc.rust-lang.org/book/second-edition/ch17-00-oop.ht...

          Personally, I think the idea that code isn't OOP just because you don't use inheritance to be utterly ridiculous.

          • steveklabnik 2015 days ago
            So, I wrote that chapter, and part of why we don't really discuss definitions is that there are so many conflicting ones. Instead, we tried to focus on goals, and how they'd apply to Rust.

            Personally, I believe the two big OOP definitions are "Java OOP" and "Smalltalk OOP", and Rust fits neither. People coming from heavy OOP backgrounds really struggle with Rust for this reason. It's also why this chapter was the hardest one to write.

            Java: https://docs.oracle.com/javase/tutorial/java/concepts/ (Sometimes phrased as "Encapsulation, Inheritance, Polymorphism, Abstraction")

            Smalltalk: http://wiki.c2.com/?AlanKaysDefinitionOfObjectOriented

          • ilovecaching 2015 days ago
            Except that Rust lacks almost all of the features and terminology you'd expect of an OOP language. Again, I take OOP to mean what one learns in school... A class based language with inheritance, overloading, visibility of members, constructors, etc.

            Rust can define behavior on any type of data, including scalar values. There is no concept of a "class" and constructors and simple functions that return instantiated values. They are not automatically called when a value is instantiated. Traits are more like interfaces, they constrain an implementer. A value has no way of inheriting behavior. Visibility is at the package level, and Rust offers no notion of encapsulation inside of a package.

            Rust is, by the trending definition of OOP, not OOP.

            • derefr 2015 days ago
              > Again, I take OOP to mean what one learns in school

              Don't do that, then. "OOP" is a term-of-art in an academic discipline. It means exactly what it was used to mean by the people who coined the term in the papers they coined it in.

              The thing that schools teach under the name "OOP" is a https://en.wikipedia.org/wiki/Lie-to-children intended to introduce something vaguely like OOP, not to introduce OOP itself.

              ---

              And, before you ask: no, there is no academic jargon term for "the thing that C++ and Java are." From an academic perspective, neither language has any particular unifying semantics. They're both just C with more syntax.

              OOP is a different set of semantics, based around closures (objects) with mutable free variables (encapsulated state), where an object's behavior is part of its state.

              C++ and Java can simulate this—you might call this the Strategy pattern—but if you build a whole system out of these, then you've effectively built an Abstract Machine for an actually Object-Oriented language, one that ends up being rather incompatible with the runtime it's sitting on top of.

              • loup-vaillant 2015 days ago
                Most people take "OOP" to mean what schools told them. And that's why "OOP" does mean that. Because most people vaguely agreed on some blurry definition, and use it.

                The fact that "OOP" is no longer used to point to what Alan Kay originally meant is immaterial. It's a shame, but that train has passed.

                • derefr 2015 days ago
                  I know you're arguing for "words are communication", and I'm a writer, so I certainly I agree with that. In general.

                  OOP is not a word, though. It's a jargon term. You can't redefine those. They mean what they originally meant, because if they don't, then you lose the ability to understand what the people doing real work using the word by its proper definition are doing. Jargon terms don't drift.

                  To be clear, I'm mostly talking about the same thing that is true of the term "Begging the question." Laymen can use it to mean whatever they want—and I don't begrudge them that, it's a phrase in a language and people will do what they like with it. But that lay-usage will never change what the phrase means in the context of formal deductive reasoning.

                  Likewise, programmers can use "objects" and "OOP" to mean whatever they want it to mean—but when having a formal academic discussion about programming language theory, an "object" refers to a specific thing (that came about in LISP at MIT before even Kay; Kay just was the first to write down his observations of the properties of "objects") and "OOP" refers to programming that focuses on such "objects" (as implemented in Smalltalk, CLOS, POSIX processes, Erlang processes, bacteria sharing plasmids, or computers on a network; but not by C++ or Java class-instances.)

                  I don't think we disagree, here; you're arguing that the lay-usage is X, and the lay-usage is X. I'm just pointing out that the lay-usage is irrelevant in the context of a discussion that requires formal academic analysis of the concept.

                  • dragonwriter 2015 days ago
                    > OOP is not a word, though. It's a jargon term.

                    Jargon terms are a subset of words.

                    > You can't redefine those.

                    You can. Anyone that's been around computing knows that “functional programming” (and even “imperative programming”, which at one point contrasted with structured programming) have drifted

                    > Jargon terms don't drift.

                    Jargon terms absolutely drift (and get overloaded) for the same reason as other terms do,the difference is the community of use in which those factors which drive drift/overloading operate.

                    > To be clear, I'm mostly talking about the same thing that is true of the term "Begging the question." Laymen can use it to mean whatever they want—and I don't begrudge them that, it's a phrase in a language and people will do what they like with it. But that lay-usage will never change what the phrase means in the context of formal deductive reasoning.

                    Mostly aside, but the popular alternative usage of that phrase is transitive verb phrase, and the older usage is an intransitive verb phrase (which, while this is clearly reversing the etymology, can be viewed as a special case of the transitive form with a particular direct object assumed) so the two neither conflict nor are incompatible. So, it's kind of a bad example of anything other than reflexive pedantry; accepting the alternative usage in formal circles wouldn't be drift or overloading because it is structurally distinct.

                • ilovecaching 2015 days ago
                  Yep
        • Chabs 2015 days ago
          I didn't mean rust was fundamentally OOP, but that rust implements, through runtime traits, exactly the behavior you are criticizing OOP for. Sorry about that
          • ilovecaching 2015 days ago
            Can you please point out exactly what I said when you say 'the behavior I'm criticizing OOP for'. Haskell typeclasses are very different from OOP inheritance or OOP classes. In any case, I don't see how that relates to the explicit criticisms I made.
            • Chabs 2015 days ago
              > All in all, OOP is a toolkit for building bad abstractions: abstractions that do not easily model computation, that hide data.

              That's a fundamental property of type erasure, which is not exclusive to OOP, and far from the extent of what modern OOP is about

              • ilovecaching 2015 days ago
                I don't think you understand what type erasure is.
    • jshowa1 2015 days ago
      A poor model for computation that is pervasive in the industry. Poor models usually don't become pervasive.
      • michaericalribo 2015 days ago
      • sidlls 2015 days ago
        The engineering organization where I work is full of dysfunction. This is because numerous poor practices were ignored or encouraged over the years. Now there is strong inertia against change because the incompetent long-timers know all the tricks and manage their job security through the system as it is.

        This is not uncommon especially in the Bay Area.

        • mbostleman 2015 days ago
          That would be the Big Ball of Mud pattern, in which the old timers are known as swamp guides.
        • da02 2015 days ago
          What does the software or company do? For example, do they make software for accountants? Construction?
          • sidlls 2015 days ago
            It's relatively young and the products are all mostly based on what is called data science and machine learning these days.
      • DonHopkins 2015 days ago
        Then explain web development.
        • jshowa1 2015 days ago
          What about it?
      • ilovecaching 2015 days ago
        Can you give an argument why it's a good model?
        • dataflow 2015 days ago
          In your top-level comment you argued it's a bad model and didn't really give an explanation for why. You just make assertions that things are pathological/overly-complex/error-prone in OOP and that other alternatives are better, that it's impossible to reason about memory layout, etc. But I could just as easily assert the opposite is true: that proper OOP does simplify problems down to manageable parts, that encapsulation is what you're supposed to do in proper OOP, that abstracting away memory layout is a good thing, etc... I'm not sure if you find these compelling or not, but if not, then I guess you could see why others feel similarly about your arguments.
        • eecc 2015 days ago
          IMHO OOP was a part of the big Software Factory model: a relatively simple, quick to understand tool to onboard masses of superficially trained workforce. A few enlightened Architects and Leaders would paint large strokes of class, collaboration, sequence diagrams while hundreds of "coders" would translate these "visions" into shippable artifacts.

          You know how well that ended... ;)

          ... oh, and watch out for Go! ducks

      • loup-vaillant 2015 days ago
        Qwerty did.
  • Chabs 2016 days ago
    One thing that I'm surprised this doesn't cover, especially since it's so C++-centric, is that modern C++ OOP is much more defined by lifetime/scope management than anything else. What defines something as an object is the fact that it doesn't exist until it is constructed, and doesn't exist after it gets destructed (which is the case even for fundamental types, with the exception of char/std::byte, btw).

    Hot take: RAII has basically taken over everything else as far as structural design foundation goes. Type erasure and encapsulation still play a role, but it's not nearly as fundamental anymore.

    • stochastic_monk 2015 days ago
      RAII is my primary use for objects in C+++, and I believe Rust is similar. Inheritance has fewer uses for me.
    • Ace17 2015 days ago
      RAII is a very powerful technique. However, it has actually very little to do with object-orientation. To my knowledge, the only other langages having it are D (multi-paradigm) and Rust (explicitly non-object oriented).
  • p2t2p 2015 days ago
    All my attempts to jump off OOP train break at exact same moment when I try to write a unit test.

    Closure: or, simply use this monstrous component pattern when 70% of your code is boilerplate or hack into namespaces and override functions in them in runtime. And don’t forget to do it in right order!

    JavaScript: yeah, simply re-define ‘require’ before importing dependencies in tests. Yeah, do it in right order.

    Recent example - I was researching how to mock calls to functions in packages in go... Well, the best thing you can do is to have package private variable, assign function to it and use it throughout the code so you can swap it with mock/stub in a test.

    There is none of that bs when I write Java or C#. I have a mechanism to decouple contracts from implementations - interfaces. I have mechanism to supply dependencies to modules - it’s called constructor parameters. I can replace implementations easily with mocks or stubs in tests without target even noticing that.

    Can somebody provide me with an example of this kind of decoupling achieved in other paradigms _without_ hacking the runtime of a language or ugly tricks like in go case?

    • loup-vaillant 2015 days ago
      Give it up. Mocks are mostly useless.

      If you want testable code, the first step is to separate computations from effects. Most of your program should be immutable. Ideally you'd have a mostly functional core, used by an imperative shell.

      Now to test a function, you just give it inputs, and check the outputs. Simple as that.

      Oh you're worried that your function might use some other function, and you still want to test it in isolation? I said give it up. Instead, test that other function first, and when you're confident it's bug free (at least for the relevant use cases), then test your first function.

      And in the rare cases where you still need to inject dependencies, remember that most languages support some form of currying.

      • humanrebar 2015 days ago
        That's a solid plan for acceptance testing and a great way to make sure you can never test diagnostic, recovery, rollback, and other something-abnormal-happened-here logic.

        For small programs with few interfaces to worry about, that might be fine, but as you number of users go up, the odds go up that you'll be ensuring rollback bits get flipped when filesystems fail. None of that is simple to test without some sort of dependency injection or other heavyweight design pattern.

        • yen223 2015 days ago
          Why would you use mocks to test those things?
          • humanrebar 2015 days ago
            Because manually triggering an optimistic locking failure is a pain. Triggering one of those and a filesystem write failure at the same time is a whole pile of work compared to the mocking.
      • vkjv 2015 days ago
        This. I'll also add that in the world of things like Docker and [Insert]CI you don't need to mock most external dependencies like databases.
    • ilovecaching 2015 days ago
      Go interfaces, Rust traits, and Haskell typeclasses and simple parameters do exactly what you ask. In fact, it's harder to mock/stub in OOP than it is in FP by the nature of FP making all dependencies always explicit. Being able to use a 'property' in the body of the functions means you're now relying on a side effect that will have to be magically mocked.

      In OOP, type signatures tend to lie. Not so true for FP.

      • p2t2p 2015 days ago
        The problem with simple parameters is that I need to drag all of my low level stuff through all of the layers of the application.

        I one was imagining a framework for Closure that would do something like that:

        - you define a bunch of functions. Some of this functions depend on other of those functions.

        - for dependent ones you define parameters and tag them somehow

        - you call this functions without mentioning those tagged parameters

        - during the startup the framework takes over and does partial application, generating functions with same names but without tagged parameters, so you kinda have your dependencies injected.

        I don’t know if this sounds too crazy or too incorrect for Closure’s paradigm.

        • ilovecaching 2015 days ago
          That's what composite values are for, combining related pieces of data such that they can be threaded through an application as a unit. State in FP is passed this way through an application.

          Adding a framework is a premature abstraction that is likely a sign that you've got your types wrong.

    • nothrabannosir 2015 days ago
      Dependency injection is probably my favourite paradigm out there, especially in a statically typed language: for the reasons you mentioned, and for discoverability with an IDE (or ctags).

      It is also a natural fit for “OOP”, or rather: classes. In a sense, it turns non-OO code into OO simply by storing the interfaces implementing the dependencies required by your code.

      If that’s your only state, are you still OO? I’d say no. (Not as it’s commonly understood: mutable state.)

      Hence, my proposed solution is: use classes & DI, but avoid (mutable) state (i.e. non dependencies).

      Curious to hear others’ thoughts on this, though.

      • pmoleri 2015 days ago
        I second this. I think that a class offers a simpler contract than a closure or a curried function, with the difference that you use "this" to refer to the enclosed objects. However, the same keyword can be used to hide state and that can lead to obscure or unexpected behaviors.
      • spdionis 2015 days ago
        Sometimes while reading/writing modern PHP it feels like it's more functional than OOP. It's definitely easier for your code to be pure and simple instead of becoming a stateful mess. To make it a stateful mess you'd have to go against many modern best practices. I think I don't really understand what kind of Java monsters people have worked with in the past.

        I will explicitly exclude legacy code from my previous statement, where globals, static calls with side effects, stateful objects and inheritance abuse were common.

        Now if only I had all the advanced type system features :/.

        • conradfr 2015 days ago
          You can see the Java AbstractInterfaceFactory creeping if you do Symfony development (which anyway is an impressive framework and very well run project).

          Sometimes it feels Php devs have an inferiority complex and want to use as many design patterns as possible to feel like a real engineer. Two years ago suddenly everyone wants to add DDD or hexagonal design on top of the framework. Bye bye KISS, hello boilerplate everywhere.

    • flukus 2015 days ago
      I've done it well in C projects by tackling it at build time. Compile each test separately and use the dependencies of the test to decide what to link against, so if the test has "#include <mock-thing.h>" then that gets translated to link against mock-thing.o which implements all the functions from thing.h (the interface) plus some ways to manipulate the state. Basically compile time dependency injection. I think this is a fairly language independent way to handle things.

      By relying on make for the heavy lifting the tests are completely incremental, only tests that depends on modified files get run. It also doesn't pollute the run time code with layers of indirection.

    • gtramont 2015 days ago
      The same ideas still apply. Take a look at this explanation: http://vvgomes.com/javascript-dependency-injection/ -- suggestion for after you read the post: depending on your needs I'd recommend not using the default value as, from a dependency perspective, you end up tightly coupling the two. Hope it helps!
      • p2t2p 2015 days ago
        Thanks a lot, I’ll take a look
  • jf- 2015 days ago
    Software developers are systematisers by default. We tend to value complexity for it’s own sake, hence the over-engineering common to software projects. The methodologies we use fall victim to the same tendency. We build complex, rigid rule sets that are claimed to improve software or development speed or whatever else, without any actual empirical evidence that these claims are true.

    All you can really do is try to be knowledgeable about the methodologies, use the right tool for the right job, and try to keep things as simple as possible. And don’t subscribe to anybody’s dogma.

    • ken 2015 days ago
      > use the right tool for the right job, and try to keep things as simple as possible

      Now all we need to do is get the field to agree on universally applicable definitions of "right tool" and "simple", and never change any requirement after any technical decisions have been made, and we'll be all set!

      I had a manager who used the term "ice cream" for phrases like this that sound good (everybody loves it!) but don't help drive any useful conversations or decisions. Should we use the right tool for the job, or the wrong one? Let's use the right one! OK, are we all agreed? Great! It's unanimous. Next issue.

      Unfortunately, the 5 people sitting around the table each have a completely different conception of what this means, so we're no closer to a decision than when we started. It's simply not a useful guide or metric. I think it's mostly code for "be quiet and do as I say".

      • jf- 2015 days ago
        Depends on how needlessly argumentative your team is. Generally you can reach a consensus through discussion, example and experimentation. We do have intuition, generally we know what simple looks like when we see it, likewise we recognise what the right tool looks like as we try several.

        Do you want a checklist for this kind of thing? You’re not going to get one. You have to use your own judgement.

        Possibly you’ve fallen victim to being on teams where ego dominates, and members refuse to seek the best option unless they came up with it themselves.

    • legulere 2015 days ago
      The full quote by Einstein is: “Make things as simple as possible, but not simpler.” I think the second part is important to not overshoot.
      • jf- 2015 days ago
        Well I wasn’t quoting Einstein, but sure. I would err on the side of less rather than more.
    • draw_down 2015 days ago
      I suppose, but to me that means we should judge methodologies or design approaches in part by evaluating their failure modes, the severity and frequency with which they occur.

      This is why I have so little patience for arguments like TFA, "well of course it doesn't work if you use it wrong!" You can make this argument for anything! If the thing is being used incorrectly a majority of the time, that is valuable information; it's no good to just say "well it would work if developers weren't so dumb." The happy path is always happy, by definition.

      A good methodology or design approach will understand its potential failure modes and include mitigations against them to help prevent those failures from happening.

  • maxxxxx 2016 days ago
    If we just had made inheritance as something to be avoided unless absolutely needed then OOP would have probably never got such a bad reputation. All the other concepts make perfect sense.
    • 3pt14159 2015 days ago
      I've found that inheritance is very useful in one situation, and adds nothing over mixins otherwise.

      If a class does two broad things simultaneously then inheritance can work great. For example, a User class that inherits from a DB mapper class. I don't want to have to tell my class how to write a record to the DB. All that code can be centralized into one thing and then relied upon for its uniformity across all my models.

      This isn't true the way most people use inheritance though. They do things like Sword inherits from Weapon and Weapon inherits from Item. But this just asks for trouble because as requirements get more complex there are more edge cases and the complexity bubbles up into overriding the inherited methods, which makes them less reliable from different calling contexts, or flipping the OO script and pushing class-based-if-statements in the ancestor class.

      Then you step back and say "why did we make Sword a weapon in the first place?" and the answer was we had logic somewhere else in the code that did things like check if a user was armed. Well we don't need inheritance for that at all. We can use plain old methods and properties / duck typing.

      • Chabs 2015 days ago
        Frankly, having User inherit from DBMapper is way more egregious in my eyes than Sword inheriting from Weapon.

        It doesn't really matter because both of these things can be better solved using composition + traits/interfaces anyways.

      • hota_mazi 2015 days ago
        Duck typing is the worst of all worlds, in my experience.

        And in defense of inheritance, the Liskov Substitution Principle is extremely useful and makes a lot of sense.

        If a function accepts a `Weapon` as parameter, surely you should be able to pass it a `Sword`.

        • TheOtherHobbes 2015 days ago
          The problem is that we're trying to formalise relationships that make perfect sense in natural language by assuming the same relationships make perfect sense in code.

          "Sword" instances may have specific properties that make them incompatible with a "weapon" superclass. Is a broken sword still a weapon? How about a sword with no handle? Or a sword that has been magically transformed into a flower? Is that still a sword and a weapon, or is it now "really" a flower, and should inherit all flower methods while throwing away all weapon methods?

          You can duck type and/or RTTI your way out of this problem, but you can't avoid the fact that traditional OOP is very bad at handling these "it depends" cases, because the only relationship it supports is a strict and static inheritance hierarchy.

          Unfortunately many domains, including natural language, can only be described by mutable context-dependent relationships.

          In the real world, swords don't turn into flowers, so you might think you're safe. (Except that you might want to include object mutability in a game...)

          But in NL the meaning of a phrase can change according to social setting, unstated subtext, speaker gender, age, and even time of day. It's all context, and it can't be ignored without losing essential detail.

          All current typing systems seem to be attempts to enforce limited-scope static relationships between opaque atomic objects with more or less static properties.

          Mutable context-dependent relationships are everyone's worst nightmare in CS. Academic CS seems to have spent most of its career trying to pretend they don't exist, or if they do, to make them go away.

          This is sold on the basis of making more reliable code. In fact it simply doesn't work elegantly for entire classes of problems, including many problems for which it seems to work just fine if you describe them in words, until you have to think about all the possible details.

          The worst case output is intolerant brittle code with limited features, and the best is an encrustation of exceptions, edge cases, and work-arounds.

          Note I'm not saying there's a simple answer, because there isn't. This is a research-grade problem, and it's barely been considered.

          I am saying - beware of simple principles like LSP that claim to solve this problem. Because there are many situations in which they simply don't.

        • pwm 2015 days ago
          The problem with LSP is that it is an incredibly hard property to prove (behavioural subtyping in general is undecidable). So in theory yes, it sounds great in practice with a sufficiently complex system good luck ensuring that you adhere to it.
        • humanrebar 2015 days ago
          Except Firearms require the reload() method be called periodically between calls to use(). So now we have to think about whether Swords need an empty reload() method or whether there need to be separate Firearms and Blade interfaces based on Weapon.
          • hota_mazi 2015 days ago
            Yup, that's how it works. And why inheritance and polymorphism is so popular: it's powerful, easy to explain, and captures elegantly a lot of problems we need to model.

            In contrast, non class based languages such as Haskell struggle to model problems that are trivial in OOP, such as how to reuse 90% of existing functionality but override 10% with more specialized behavior. Good luck solving that problem elegantly with type classes.

          • maxxxxx 2015 days ago
            "Swords need an empty reload() method"

            That always seems to the problem with deep hierarchies after a while. Suddenly you have something where one of the inherited methods shouldn't be there. You can't take it away so you have to do something clunky like throwing an exception or making it empty.

      • maxxxxx 2015 days ago
        "adds nothing over mixins otherwise."

        The funny thing is that languages like C# and Java abandoned mixins but kept deep inheritance hierarchies. When I did C++ more we used multiple inheritance a lot but with only one or two layers deep. This worked extremely well and now that that I am doing more in C# I miss it a lot.

      • fooyc 2015 days ago
        > I don't want to have to tell my class how to write a record to the DB

        You don’t need inheritance to achieve this.

        Your User class contains data and probably some business logic. The fact that it’s going to be persisted in a database at some point is a detail that the class shouldn’t know.

        Consider using the data mapper pattern (or an ORM implementing this pattern). In this pattern, your User object doesn’t even know it will be persisted. It doesn’t have any parent class. You just manipulate your entities like a graph of plain objects, and when you have finished you ask the data mapper to persist them. In my experience this is much better than what you are describing, which look like the Active Record pattern.

        > If a class does two broad things

        Doing this is a violation of one very commonly accepted principle: The single responsibility principle.

        • spdionis 2015 days ago
          Active Record should be considered an anti-pattern and is definitely in violation of single responsibility at the least.

          Myself, I wouldn't even put any business logic in the User object, except maybe representational logic (e.g. have a couple of helper methods that return multiple representation of the same piece of data).

          One of the best things to do is separate behavior from data in the first place, which feels kind anti-OO philosophy, but is definitely easier to reason about and work with.

        • maemilius 2015 days ago
          My understanding of SRP is that the _implementation_ of those two things should be separate, not the interface.
          • humanrebar 2015 days ago
            Well, the I in SOLID is Interface Segregation, so there's a problem there too.
      • seanmcdirmid 2015 days ago
        Mixin inheritance is also inheritance, just linearized multiple inheritance actually.
        • pitaj 2015 days ago
          Mixins can be modeled as inheritance or as composition.
          • dragonwriter 2015 days ago
            They can be modeled as inheritance or as composition plus delegation, but that's not saying much since inheritance itself can be modelled as composition plus delegation.
          • dvlsg 2015 days ago
            I think composition and mixins should be kept separate. In my mind (and maybe I have an incorrect view of it), mixins throw a bunch of new properties on the base object, whereas composition keeps the original object encapsulated in a single property. Would you consider the second type to be a mixin? I've never heard it described that way.
          • seanmcdirmid 2015 days ago
            Both Flavor-style mixins and Bracha style mixins utilize inheritance. All mixin definitions should fan out from that early work.
    • keerthiko 2015 days ago
      I agree. As with most tools in programming, not every mechanism of a language should be employed aggressively unless it truly improves your application architecture.

      Inheritance is very useful especially in game programming (in context of OP), and hardly ever anywhere else. In games it's useful to be able to pass around references to an object or have systems that manage pools of objects at exactly the hierarchy of inheritance that makes the most sense for the manager of that system. Not many other applications need to pass around objects between various hierarchical management systems as much.

      I think inheritance shouldn't be taught in intro-level programming classes. It's presented as "how to do all things in OOP, shove all the things into multi-level child classes" when really it should be "here's an advanced concept for situational architecture optimization". It defeats the purpose when you do multiple levels of inheritance and no longer ever use the parent classes for anything.

      • goalieca 2015 days ago
        > Inheritance is very useful especially in game programming (in context of OP), and hardly ever anywhere else.

        This is because OOP is good at modelling objects and games are modelling objects.

    • hota_mazi 2015 days ago
      Sure, the general consensus is that inheritance is better achieved through composition.

      But let's be serious for a minute here: even regular inheritance has hardly ever led to the kind of nuclear disaster than most pundits claim.

      It makes your code base a bit more unwieldy and a bit harder to evolve, but it's really not the end of the world.

      • jcelerier 2015 days ago
        > Sure, the general consensus is that inheritance is better achieved through composition.

        This sentence does not make sense. If you want to have a class composed of two other classes, whose behaviour is defined at run-time - say, a generic "Engine" object which is composed of a "GraphicsRenderer" and an "AudioRenderer" where the first one can be a D3D renderer or an OpenGL renderer and the second can be an XAudio or OpenAL renderer, you have to have inheritance somewhere, because you have to store at least one function pointer at some point. std::function in C++ is implemented with inheritance. Rust traits are based on inheritance. However you look at it, you can never completely "get rid" of inheritance if you want to hide both code and data behind a single pointer. Hell, even the linux kernel written entirely in C reimplements vtables by hand for device drivers because it makes so much sense.

        • hota_mazi 2015 days ago
          You are confusing inheritance of implementation with inheritance of interface.

          The consensus is to use inheritance of interfaces and use composition to implement the interfaces.

          Hence my phrasing: "Inheritance is better achieved through composition".

          • marcosdumay 2015 days ago
            > The consensus is to use inheritance of interfaces and use composition to implement the interfaces.

            A consensus by the "maximization of boilerplate" rule.

            Inheritance is the most powerful tool available at the OOP land. It's the one thing that FP languages still didn't replace with enough added advantages to make the OOP stuff look like a toy. So if you are programming in OOP while avoiding inheritance, you would be certainly better in another paradigm.

            (And, of course, powerful tools are easy to misuse. That's no reason for forbidding them.)

            • mercer 2013 days ago
              I'm not familiar enough to have a strong opinion on this, but the impression I get is that even the OOP camp (or thought leaders within those camps) seems to have abandoned inheritance in favor of composition.

              Assuming this is the case: why do you disagree and what do you think is the cause of this difference in opinion?

            • CMCDragonkai 2015 days ago
              Check out extensible records or Nix language fix pattern for its attribute sets.
          • jcelerier 2015 days ago
            But you still use the OOP concept of inheritance, which is basically "indirect methods with function pointers". Beside, "interfaces" don't even exist as a syntaxic concept in many OOP languages, C++ being the most prominent one.
            • munchbunny 2015 days ago
              That's splitting hairs. Regardless of the syntax mechanism used to achieve it, inheritance of interfaces and inheritance of implementation are qualitatively different, in the sense that you can usually tell the difference regardless of language.

              Also, C++ has abstract classes with only pure virtual functions, which accomplish the same thing.

            • spdionis 2015 days ago
              I think C++ is the only one that doesn't have interfaces.
              • maxxxxx 2015 days ago
                An abstract class with only abstract methods is essentially the same.
                • jcelerier 2015 days ago
                  It's most certainly not:

                  - you have to at least implement the destructor if you are going to use the abstract class across multiple DLLs - else, dynamic_cast won't work since each DLL may have its own type_info object for the abstract class

                  - even if a method is marked abstract, it can still have a default implementation: https://gcc.godbolt.org/z/bAE7v6 though other prominent OO languages are slowly catching up with this ;)

        • spdionis 2015 days ago
          extends != implements
    • kazinator 2015 days ago
      Inheritance can be so randomly abused as just a code saving hack.

      Look how I abused inheritance in this assembler (for a Lisp virtual machine):

      http://www.kylheku.com/cgit/txr/tree/share/txr/stdlib/asm.tl

      The object system is used to define opcodes. Methods on these objects (which get instantiated as singletons) then handle assembling and disassembling.

      There is a macro defopcode-derived which defines an opcode similar to another one, using inheritance.

      There is no reason for the relationship to go one way or the other; it's just "this thing is like that thing, except for this slight difference". The inheritance could basically go in either direction.

      Note that defopcode-derived doesn't even have provision in its syntax to specify behavior; the code is 100% re-used. The only thing different about the derived opcode is the instruction mnemonic and the opcode bits. Inheritance is used just to override a number and symbol which are static slots.

    • cloudkj 2015 days ago
      Inheritance indeed can be pinpointed as the one major problem in problematic OO code. A lot of the comments here talk about abuse and I think that hits it on the head. It’s not really a matter of knowing when to use or not use inheritance, but rather that the developers that do use it end up using it everywhere and a bad pattern proliferates into even more problematic areas, such as using inheritance for mock objects in unit tests.
    • noblethrasher 2015 days ago
      Or… if you're talking about languages like C# and Java (and presumably C++), just make sure all of your classes are either sealed or abstract. This turns classes into proper algebraic data types.

      To wit, implementation inheritance gives you a sum type (e.g. a Shape is either a Circle, or a Square, or…) whereas interface inheritance gives you a product type (class Foo : IBar, IBaz means that Foo is an IBar and an IBaz).

      • confusius 2012 days ago
        "languages like C# and Java" Not particularly your post, because compared to e.g Haskell you could say that, but I usually cringe when people say "C# and Java", and often list a hsot of bad attributes that are more particular to Java. Java is such an utterly verbose, dull, ugly, clumsy to use language that falls over its own feet all the time, expecially when adding new features that are at best half as useful as they could be because of early design problems... Whereas C# is, comparably, such a joy to use... ;)
      • rienbdj 2015 days ago
        Nice idea, but without match expressions (checked by the compiler) it’s just not as good as a true FP language.
    • bendavis381 2015 days ago
      And don’t use a class if it holds no state.
      • Ace17 2015 days ago
        This is not so simple. What about "NullLogSink" and "StdoutLogSink", both being implementations of "ILogSink"?

        Classes are very well suited to statically formalize the communication points (aka "interfaces", "protocols") between the various parts of your program.

        The fact that a concrete class might have a state is actually irrelevant (indeed, we generally make all of our data members "private").

    • pjmlp 2016 days ago
      We made it optional in many languages, the problem is how it many people teach OOP, and how it gets abused.
    • blindwatchmaker 2015 days ago
      Mutable internal state is problematic with or without inheritance.
      • maxxxxx 2015 days ago
        There are plenty of use cases where you need internal state. Think about UI in a desktop app for example. Not everything is a data pipeline with a clear in and out.
        • mercer 2013 days ago
          Considering the popularity of React/Redux, it does seem like avoiding internal, mutable state as much as possible and approaching as much of the rest as possible as a 'data pipeline' is a very valid way of dealing with UI's.
  • agentultra 2015 days ago
    For me, whenever someone invokes the GoF or SOLID, I’m reminded of the Brothers Grimm. It’s programming by folklore. That’s all the GoF did: they went out into the world and tried to observe how programmers were structuring their programs. And they seemed particularly interested in programmers using OOP.

    All of these principles have very little basis or formal definition. Bertrand Meyer did make some headway with Eiffel. But the type systems are so weak and the lack of formal semantics makes all of these discussions a bit of hand waving and bike shedding.

    At least the DOD folks have some guiding philosophy and are trying to optimize the design of programs to account for memory latency of modern hardware architectures.

    The OOP defenders are basing their argument on hot air and hand waving.

    There are more interesting languages these days with better designs. Ones that are based on better theories in my opinion.

    OOP will be around for a long time if only because it has so many adherents and people will be stubborn to change if history has anything to say about it.

  • LolNoGenerics 2015 days ago
    > This code may be typical of OOP in the wild, but as above, it breaks all sorts of core OO rules, so it should not all all be considered traditional.

    This argument drives me nuts. A simple OOP environment can be quite easy to grasp and (mis)use. Following all the golden rules and principles that SOLID et al call for, are comparabily hard to internalize. A rookie can only fail and has to collect, choose and study all the wisdom over the years until he becomes a master. To that point he will create OO code that will break untold rules. Ruling the majority of OO code out there as "bad" is hubris and ignores reality. It is easy to do OOP wrong and hard to get it right. This imbalance is proof enough for me that we don't know what we are doing, just justifing. (Yes this may apply to other paradigms as well)

    • dkarl 2015 days ago
      Ruling the majority of OO code out there as "bad" is hubris and ignores reality. It is easy to do OOP wrong and hard to get it right.

      It wasn't the inherent nature of OOP that caused that terrible style to become dominant. It's a style that was actively taught and promoted since at least the mid-1990s. It was taught, and taken for granted, that objects were containers for mutable values. Deep inheritance hierarchies were taught as the norm, not the exception. Java was built around this model and then became one of the most popular programming languages in industry, and learning materials for Java reinforced the style. Everyone interviewing for a Java programming position from the late 1990s through the mid 2000s had to learn special jargon related to this style and regurgitate it in interviews. We're suffering through a hangover from decades of this horrible version of OOP being promoted as the "right" way to write software in industry and academia.

  • ben509 2015 days ago
    This is the "you're holding it wrong" defense of OOP.

    Of SOLID, S, O, I and D are rehashing structured programming design principles. LSP is peculiar to it, and not an unreasonable way of thinking of objects, but I don't see people struggling to figure out how to come up with workable class hierarchies.

    The point of objects was that they were intuitive and easy, and where it tends to fall apart is in the details. It's often small tasks like writing an equality operator correctly that are absurdly complicated[1]. And while we can construct reasonable class hierarchies, the interaction becomes a bear and the bugs are subtle and confusing.

    What I see in OOP programming is that people avoid various idioms or patch around them because they don't trust their tools.

    I think the problem with most OOP languages is that some high-level concepts like inheritance were constrained by very low-level implementations, and they often tried to glom several ideas together.

    There's no "object algebra" even 40 years in. In C-like languages, objects are using a "struct and vtable in the heap" model. In dynamic languages, they're using the "type instance and a hashtable in the heap" model. Then they typically declare that a value is really a variable, unless it's an atom, and often other weird asymmetries like "the bottom type actually does have a value which is 'null'" and, of course, whatever weirdness they pick up such as floating point.

    Those constructs are then overused; this problem is especially apparent in Java where "everything is an object" means that your class becomes your tuple type, and if you want to combine two tuple types you're going to do that via inheritance. In most of them, you don't have a proper discriminated union, so all the stuff you'd do with sum and product types you now have to shoehorn into classes whether it makes sense or not.

    It all sort of works, but the reason OOP languages keep adopting non-OOP features is that it doesn't work very well.

    [1]: http://jtechies.blogspot.com/2012/07/item-8-obey-general-con...

  • Reedx 2015 days ago
    Some other takes:

    Casey Muratori (Handmade Hero) on why OOP is bad and how to get rid of that mindset - https://youtu.be/GKYCA3UsmrU?t=4m50s

    Mike Acton (Engine Director @ Insomniac Games) - https://www.youtube.com/watch?v=rX0ItVEVjHc

    • danschuller 2015 days ago
      Mike Acton works for Unity now.
  • FourthProtocol 2015 days ago
    Took me a long time to grok OOP and OOD (was introduced to OOP in '91). At first I thought I knew it, and then realized I didn't.

    Plane into the side of the mountain, no survivors, call off the search. Which is when I really started to learn (around '96/97). And now I love it.

    Until I come across people who always start by defining an interface first and then think about what might follow. And dependency injection. Holy priceless collection of Etruscan snoods DI makes me want to gouge out my eyeballs with a rusty cork screw.

    Like I said though I love it and today my happy land is about 80% OOP and 20% everything else.

    • DanielBMarkham 2015 days ago
      The thing I love about OOA/D/P is that it is a useful and productive way of thinking about things. It's probably even the most natural and intuitive way of thinking about them. Plato wasn't a C++ programmer, but classes and interfaces are direct descendants of his work 2k+ years ago.

      When I first fell in love with OO there was a time that I felt that there wasn't a problem in the world I couldn't solve using it. That moment it finally "clicked", and all the lights came on? Wow! One of the best intellectual experiences of my life. It changed everything.

      I was right about being able to do anything with it. What I was wrong about was how some things are more difficult than others. Rules-based systems, for instance, get abstracted away into a sort of gobbledygook in OO. Scripting is uglier than it should be.

      Then I had a similar realization about FP. It took much, much longer and there was no dramatic moment, but the impact was just as huge. It was a new way of thinking about and solving problems. It has it's own edge cases and antipatterns, of course, just like OO.

      I wonder what the next A-Ha! change will be? TLA+? Is there a system of thinking around morals and values as it applies to problem solving, as Kant and others thought? Beats me. I hope I get to find out.

      • barrkel 2015 days ago
        Data flow. Closely related to functional programming, and also relational algebra. Different variants, whether reactive, push vs pull, streaming vs batch, restartable, etc.
    • hacker_9 2015 days ago
      DI is bad, what? So how are you writing tests then.
      • tonyedgecombe 2015 days ago
        I always though dependency injection was an inevitable product of over zealous testing. As soon as someone says we need to test 100% of the code in our UI that is where you end up.
        • hacker_9 2015 days ago
          No it's just an inevitable product of 'testing'. It's a simple way to enable and disable dependencies. There is literally no better way, apart from not having any dependencies in the first place of course.
      • MarkMc 2015 days ago
        By letting the application load all required components into memory before running the test(s). If Component A needs Component B which needs Component C, they all get loaded as they would in production (ie. without mocks). If this means that almost the whole application gets loaded to run a single test, so be it.

        The only time this approach is really a problem is when doing slow calls such as I/O - eg. writing to the database. In such cases you can easily switch to an in-memory database or use a factory pattern (which is kind of like a hand-rolled mock).

        It's true that an error in say Component C may generate many failures in test of for other components, but in practice that's not really a problem. Just pick one of the test failures and drill down until you find the root cause - fixing that cause will magically fix the other 55 test failures.

      • Spearchucker 2015 days ago
        Like in procedural languages I write my own harnesses as needed. Because DI adds complexity. And for testing all DI does is make testing easier. You end up shipping all that complexity or you refactor your ship code. To be fair that might be acceptable to many in a typical corporate environment.
        • hacker_9 2015 days ago
          Well you are going to need to deal with your dependencies eventually. Unless you are just end2end testing, and not doing any unit tests. In that case good luck finding your bugs when they appear. Or perhaps you write 'if (mode == "test")' everywhere :)
        • vietjtnguyen 2015 days ago
          What do you mean by DI? I find DI is an overloaded term.
          • ahansen 2015 days ago
            In this context I believe he is referring to dependency injection.
            • vietjtnguyen 2015 days ago
              I guess I meant to ask what they mean by dependency injection. A bare version is just passing dependencies as arguments and I can't imagine what is so egregious about that. Maybe they mean something more complicated?
    • ilovecaching 2015 days ago
      DI is an essential feature of functional programming, and it's incredibly simple to understand. You're real issue is that OOP is a terrible, broken model that can only model a pattern like DI with a high degree of complexity.
  • evancox100 2016 days ago
    Author needs to actually state what ECS is. From context I don't think he/she is referring to Amazon's Elastic Compute Service.
    • flohofwoe 2016 days ago
      It's Unity's (the game engine) new Entity-Component-System, the blog post is an answer to this presentation:

      http://aras-p.info/texts/files/2018Academy%20-%20ECS-DoD.pdf

      Unity's traditional entity system is suffering from a number of "OOP-isms" which make it hard/impossible to optimize for performance.

      The new ECS strictly follows a Data-Oriented-Design approach, where everything is built around laying out the data in memory in a CPU-cache friendly way (and a few other things that neatly 'fall into place', like spreading work across CPU cores, a specialized 'high-performance' C# dialect, and the ability to move work from the CPU to the GPU).

      The big question is how the traditional Unity audience will react, since the ECS programming model is quite a bit different from the old way, and it's no longer as simple to build a game from a jenga-tower of adhoc-hacks ;)

      • TeMPOraL 2015 days ago
        I don't think Entity-Component-System is Unity-specific. From what I recall from other articles, Unity has its own idiosyncratic implementation, but the pattern has several slightly different interpretations.
      • reificator 2015 days ago
        I'm very glad that Unity is doing this, and it's made me interested in the engine for the first time in many years.

        However, it's important to note that Unity is adding an ECS, not inventing the concept.

      • modernerd 2015 days ago
        > The big question is how the traditional Unity audience will react

        It's been well received so far (for example: https://forum.unity.com/threads/ecs-or-why-should-i-bother-m... ).

        There's also a good incentive to adopt ECS for certain games; Unity is offering a hugely reduced runtime size if you follow the ECS pattern, which is much needed for web games and other “interactive experiences” (ads, mobile game demos). The “ECS for Small Things” presentation at GDC is worth a look for those interested:

        https://www.youtube.com/watch?v=EWVU6cFdmr0

      • yorwba 2015 days ago
        > the blog post is an answer to this presentation:

        ... which had some discussion earlier [1] and the top comment on that submission links to the article of this submission.

        [1] https://news.ycombinator.com/item?id=18202308

    • Hemospectrum 2015 days ago
      It refers to Entity-Component-System, an architectural design pattern (like Model-View-Controller) that uses composition instead of inheritance to define the behavior of entities in a simulation.
      • learc83 2015 days ago
        > that uses composition instead of inheritance

        What they are referring to as ECS is more than just composition over inheritance. There are entity component architectures that are just that--Unity's existing class based component architecture for example.

        But the ECS in this case refers to Data Oriented Design--basically laying out data like normalized database tables. Every component system has a table, and each row in the table is the data for one component (you don't actually use a relational DB for this, the data is just organized in a similar manner). The entity itself is just an id that all of the components reference.

        The concept as used in games started in the late 90s as Structs of Arrays instead of Arrays of Structs.

        Here's a a good free book on the subject (there's a paid hard copy as well) http://www.dataorienteddesign.com/dodmain/

        • tomelders 2015 days ago
          The confusing thing about the name “Entity Component System” is that “System” is thing in ECS. It’s not a system of Entities and Components, it’s a pattern thay has three parts; Entities, Components, and Systems.
    • tynorf 2015 days ago
      As a testament to how bad AWS's shorthand is, ECS is actually Elastic Container Service.
    • gagege 2016 days ago
      In a gamedev context, ECS stands for Entity Component System.
    • blarg1 2015 days ago
      From what I've read the implementations of it can vary a lot, but I liked the one described here:

      https://blog.therocode.net/2018/08/simplest-entity-component...

  • pjmlp 2015 days ago
    Published in 1997, "Component Software: Beyond Object-oriented Programming", followed by "Component-Based Software Engineering: Putting the Pieces Together" in 2001.

    https://www.amazon.com/Component-Software-Beyond-Object-Orie...

    https://www.amazon.com/Component-Based-Software-Engineering-...

    The problem is how badly many schools teach OOP paradigms, and how many frameworks abuse a specific style of OOP.

  • DrNuke 2015 days ago
    To be fair it was in the late ‘90s - early ‘00s a very clean way to make communication work among teams, at a time still deeply into the non-internet era and C or Fortran oriented.
  • bribri 2015 days ago
    I can't think of any oop abstractions that I prefer to functional abstractions. If you really need has-a is-a relationships or mutability you can get them a la carte with a language like Clojure, but they're not deeply baked in to the language nor the encouraged pattern for extending code.
  • noncoml 2015 days ago
    The problem is not OOP, but how C++ and Java implement it. Ruby is a much nicer OOP platform.
    • StillBored 2015 days ago
      Please don't include C++ in that statement. C++ doesn't "implement" OOP in any particular way. It gives you a pile of tools which can be mix/matched with other tools in the language to create any number of paradigms.

      The "pure OOP" style that is so roundly criticized is just one of C++'s possible styles. The whole article is effectively about how there are other ways to use C++. I actually tend to think that much of the style he is describing (composition based) is really just what the pure OOP people used to criticize as "C with objects".

    • twic 2015 days ago
      How so?
      • noncoml 2015 days ago
        The most important part missing IMHO is late binding.
  • zdmc 2015 days ago
    A good heuristic: if you’re not dealing with “state” (i.e., Games, DB ORM, Reinforcement Learning), then don’t use OOP
    • bribri 2015 days ago
      I completely agree. Use it carefully if you need it but it shouldn't be the default.
    • lerno 2015 days ago
      Really? I’d say the accidental distribution of state across objects due to cross cutting concerns is exactly where OO breaks down for me.
      • Hodgman 2015 days ago
        If cross-cutting concerns are making your architecture unwieldy, you likely haven't used composition enough / are doing OOP the bad way (tm).
        • lerno 2015 days ago
          No, some logic simply isn’t cleanly decomposable, plus the main problem here is that objects lets you get away with implicit state (e.g. if (this.x == 0) doA() else doB(); ) for long enough that when you start realizing you need explicit state, it’s usually distributed quite a bit.
  • wolfspider 2015 days ago
    This is a pretty good discussion and I’m surprised that while discussing OOP not much was brought up in the way of managing private versus shared memory which I think depending on the platform is not as universal as we all hoped it would be by now. Marshalling objects with pointers accessing vtables over uneven terrain is how it goes down. I think here is a good example: https://trac.webkit.org/wiki/WebKitIDL

    And to further that point there is no more Safari on Windows for this reason among many others. Remember MemMaker? It’s crazy there were so few applications we managed some of the memory ourselves but it worked very well didn’t it? OOP really took off back then too and then memory utilities were not needed and didn’t last too long. The convenience of not worrying about it is one of the many things OOP was able to solve as it advanced. It is still pulling off the same tricks today in a much more complex and metered way. OOP does so much more than just this of course but the solutions developed with it for managing memory are intense and as much art as science. So we should question it and many paradigms to make this better. A mentor of mine when explaining this would compare it to juggling...and then proceed to actually start juggling while talking about his code. He’d stop and look up, just pause, and say that’s all we are doing here just juggling.

  • faragon 2016 days ago
    More resources on DOD (Data Oriented Design):

    https://github.com/dbartolini/data-oriented-design

  • LiterallyDoge 2015 days ago
    I don't understand why this article is so angry? ECS is a great subset of OOP. Both are helpful tools where they make sense.
    • thrower123 2015 days ago
      For at least the last ten years, maybe fifteen, on the GameDev.net forums you haven't been able to swing a dead cat without hitting somebody going on and on about entity/component systems or pushing them as the One True Path, or trying to create their own implementation. It's probably worse now that Unity is so big. I'm not sure it is something I would pitch at beginning programmers, and there are a lot of them that wander into that, decide they are going to follow that advice, make a big mess, then wind up asking Hodgman and the handful other saintly regulars that keep that place going, to help them out.
    • Hodgman 2015 days ago
      It's because every single article that's promoting ECS does so by comparing it against incorrect inheritance-based code. The amount of time you see newbies jumping on the ECS bandwagon because inheritance is bad, while they don't yet understand OOP, ECS, procedural, relational, or functional... is infuriating.

      Do both. Teach how to use composition. Teach how to use the relational model. Don't avoid teaching either by using misleading sales tactics.

    • learc83 2015 days ago
      ECS as they are using it in this blog is about Data Oriented Design. It's not just OOP plus favoring composition over inheritance.

      DOD explicitly advocates separating data from behavior, and is strongly opposed to OOP in general.

      • LiterallyDoge 2015 days ago
        Just so I understand you right: they're advocating global functions to operate on predictably similar data structures? If that's the case, it seems like you'd want some of them to be object-oriented, and some not.
        • learc83 2015 days ago
          Each system tends to operate on different sets of data which don't tend to be very similar, and systems may operate on multiple data sets.

          You could build each system as an object, but you wouldn't want to store the relevant data structures within that object because other systems will likely need to use those data structures as well.

          The entire architecture is predicated on separating data and behavior. Yes you can build an ECS system using classes, but nothing about it fits into what you'd call OOP.

    • n42 2015 days ago
      it unfortunately undermines the message of the post for me. it just feels like another case of absolutism, fighting fire with fire, by the One True engineer who knows The Solution.
  • heinrichhartman 2015 days ago
    Does anyone know of a good reference for idiomatic OO(P)?

    Like the Codd paper for Relational Algebra.

    • arendtio 2015 days ago
      If you use a typical IDE that comes with Squeak or VisualWorks, you can quickly inspect a lot of idiomatic code from the standard library. The most common mistake I have seen is that people who know about other programming languages, start looking at OOP as a way to separate and organize code.

      Instead, it starts from the other side: OOP is based on a few core concepts, and those concepts are critical to understanding OOP. The first concept is 'Everything is an object', the second might be 'Objects can receive messages'. So if you have some code:

        1 + 2
      
      It means that there is an object '1' which receives a message '+ 2'. If '1' is an Integer, Float, String or whatever might be relevant for its inner workings but if you want to understand OOP you first have to understand the concept-side of it (and ignore the implementation). Once you know the concepts, you can reason about good and bad implementation/code.

      You might wonder about the preachy tone, but in the beginning, it took me a few months before I understood that I was trying to understand OOP from the wrong side (at the time I was thinking in C) and I would like others to have a quicker start than I had.

    • noblethrasher 2015 days ago
      Well, the best analogue to the Codd's relational algebra is Hewitt's actor model in my professional opinion. Both are based on mathematical formalism, though the Actor Model goes a bit further in that it's also informed by physics.

      But, just as SQL doesn't really implement Codd's relational algebra, so it is the case that most so-called OOP languages miss the mark vis-à-vis Alan Kay's original conception.

      The analogy is buttressed by the fact that many early RDBMSs didn't even support joins (path independence being an essential characteristic of RA), just as many mainstream OOP languages didn't/don't idiomatically endow objects with a strong way to protect themselves (encapsulation being a necessary characteristic, thus pervasive use of setters being the main sin)). But, Kay did praise Erlang for getting OOP right.

      • da02 2015 days ago
        Kay also mentioned the Internet is an OO system. At one point he was thinking every object would have it's own IP address.

        He does have an account here on HN. https://news.ycombinator.com/threads?id=alankay1 I hope he doesn't get tired of explaining the same things over-and-over again. I have asked questions and he has answered, but I usually end up misinterpreting the ideas. :(

        • noblethrasher 2015 days ago
          Yep, I follow him pretty closely, and knew about his account and comments on here.

          Funny story: That is at least the second account that he created on HN. He registered an earlier one just to reply to a comment that I had made[1].

          [1]: https://news.ycombinator.com/threads?id=alanone1

          • da02 2015 days ago
            Ha ha. I always wondered why that lonely one-comment account existed. I suspect many people are using data abstraction and calling it OOP. I am not a professional programmer. What do you like to use when designing software? Functional? Types? C? Haskell? Pharo?
      • mercer 2013 days ago
        > But, Kay did praise Erlang for getting OOP right.

        This comment does a nice job explaining why: https://news.ycombinator.com/item?id=2039750

        I also like how in the interim Elixir addresses some of the issues surrounding gen_server verbosity.

    • sk1pper 2015 days ago
      Practical Object-Oriented Design in Ruby by Sandi Metz

      Don't worry about the "in Ruby" part, it's just as good even if you know nothing of Ruby. I enjoyed it and have never written a line of Ruby in my life :)

    • mikmoila 2015 days ago
      There is a paper by Stroustrup et al titled something like "Idiomatic C++". Check out the books by Andrew Koenig (especially "Ruminations on C++") and Bertrand Meyers.
  • std_throwawayay 2016 days ago
    OOP is just a mental model. Deep down everything is made of bits. The church of OOP has failed but if something looks like a duck, walks like a duck and talks like a duck it probably is useful to make a duck class. We're now down to fighting for nuances. You can do most things with OOP or without OOP but each path has some upsides and downsides and most of the time it's good to use some things it provides where it makes sense and not get too religious about it. The great architect has the foresight on how the code will be used in five years and design it accordingly.
    • Waterluvian 2016 days ago
      I think this relates to what you're saying.

      I've never felt any frustration that OOP feels like the wrong tool when I'm using languages that give me the choice to use it or not (like Python, and JavaScript). But when I'm using Java, as one example, it often feels like I'm really locking myself into a design up front.

      In Python, especially. I'll find myself starting off all experiments or simple projects with functions and basic data types. As something evolves and I want some semantic clarity I'll stop using dicts and start using namedtuples. And then at some point I may replace the namedtuples with classes. From there I may discover value in having subclasses so I'll add a few (but this is exceedingly rare in my line of work).

      • std_throwawayay 2015 days ago
        My experience with Python is largely the same. I often start with local variables in a script, then put some parts into functions, then start putting some shared/persistent data into dicts. If after some time a pattern emerges where I think "gee, wouldn't it be useful to pack these three variables that always occur together and interact with each other into a class and use some methods to consistently modify them?". Then I create a class that does some specific thing. This transformation occurs gradually over the development process and the data shapes the design of the application. I would call this process a prototype development. At the end I would have the knowledge about the design where I could start implementing it in Java or C++. But the problem is already solved well enough in Python and you almost never need to take the leap to another programming language.

        The program is not good, the design is not well thought out and the implementation is not very clean. It is a design prototype that you use to write the real application. If only there were time for a fresh start. The solution that we have by that point is not perfect but it does three things: Actually solve the problem, solve it good enough for regular usage and it allows further modification with only minor pain.

        I wish I could tell the full story but most software stories end before the grand finale.

      • Chabs 2016 days ago
        But that's the whole point of JAVA. It's an opinionated platform with a hyper-standardized workflow. Sure, that limits creativity, but in many business contexts, the last thing you want is your programmers getting "cute".

        There's a straight line from requirements to implementation; no meandering involved. At least that's the theory. In practice...

        • bunderbunder 2015 days ago
          > But that's the whole point of JAVA. It's an opinionated platform with a hyper-standardized workflow.

          That may have been where Java wanted to go, but, when I'm working in Java, I don't feel like that's where I am. Ways of doing things in Java tend to be wildly inconsistent from project to project. Partially, I think, because so much core functionality in the Java ecosystem was allowed to be federated out to 3rd-party projects for so long. Take the long-standing popularity (and rivalry) of Guava and Apache Commons for handling even basic tasks that are hard to get done using the core Java APIs. If there's such a thing as a "platform smell", I'd say that certainly qualifies.

          With Python, on the other hand, there is a fairly consistent common understanding of what "Pythonic" means, and, even when there really is more than one way to do it, the question of which one to use can usually be quickly resolved to a predictable outcome by simply pointing out that one option is the more Pythonic way to do things.

          (edit: Though, to be fair, Java was first released into a world where languages like C, C++ and Common Lisp represented the status quo. Expectations were lower at the time.)

          • mikmoila 2015 days ago
            To be honest, I've never seen a problem I couldn't solve in easier way with core modern JDK libraries than with "3rd party" libraries.
            • bunderbunder 2015 days ago
              It's definitely gotten better over the past 5 or so years. But there was a lot of time spent acquiring technical debt over the preceding couple decades.

              Even if I don't use Guava or Apache Commons myself, for example, I still occasionally run into dependency conflicts that I need to resolve with awful hacks like package relocation because so many other major libraries rely on one or the other, and neither library is a particularly great citizen about breaking changes.

        • madmax96 2015 days ago
          https://medium.com/stanford-d-school/want-some-creativity-cr...

          It’s interesting - when you begin adding constraints, sometimes it helps solve the problem. You can’t be creative unless you’ve created a solution. It would be interesting to see the impact on this with programming languages.

        • jcoffland 2015 days ago
          I'm glad I don't work places that try to keep me from being "cute".
        • kamaal 2015 days ago
          When faced with complexity you have to use with whatever 'opinionated hyper-standardized workflow' to build 'un-opinionated hyper-unstandardized workflows'. The net result is like trying to build sculpture with glass with chisel and hammer.

          Its then you realize you had better started with clay.

        • Waterluvian 2016 days ago
          Yes. That may very well be. And I'm not saying Java is bad because of what I described. Just that I experience the, "ugh OOP isn't helping me do what I want to do here" with Java.
          • std_throwawayay 2015 days ago
            It probably is the right tool if you have an enterprise problem that needs an enterprise solution.
            • mikmoila 2015 days ago
              According to my experience "enterprise problems" are more related to scalability, data integrity, APIs, and workforce hiring than "what programming paradigm to use."
        • xapata 2015 days ago
          It was a hypothesis, not a theory. Usually a hypothesis is abandoned after so many counter examples.
      • ken 2015 days ago
        > I've never felt any frustration that OOP feels like the wrong tool when I'm using languages that give me the choice to use it or not (like Python, and JavaScript).

        Do you not miss more advanced features, like multiple dispatch? Do you just implement it ad-hoc when you need it?

        I don't know any way to say it that doesn't come off sounding condescending, but this looks like the Blub Paradox to me. Python's brand of OOP is better than most, but it still feels pretty limiting to me. It doesn't even offer syntactic abstraction to make it easy to work around. You have to hope (as the 'multimethod' package does) that other features accidentally allow you to.

        • Waterluvian 2015 days ago
          It's not something I've ever needed with such frequency that I wished it was in the core langauge. When I need it I just use a library that exposes it. I use a decorator based multidispatch.
    • mehrdadn 2015 days ago
      > OOP is just a mental model. Deep down everything is made of bits. The church of OOP has failed but if something looks like a duck, walks like a duck and talks like a duck it probably is useful to make a duck class. We're now down to fighting for nuances.

      No... I think this is missing precisely what was exactly the point of the article (see "(B)" in the text). This post is not a fight over OOP religion. The point of it is if you misunderstand or mischaracterize "nuances" about some idea (if these are actually all nuances, which I think is debatable) and propagate them, then you shield other people from prior (edit: or even current) literature in the area, and hence prevent them from understanding what the relevant techniques really are and how to use them properly at all. This makes them lose a potentially powerful tool in their toolset, which you should agree is an awful thing regardless of what coding 'religion' you follow.

      • trgn 2015 days ago
        Just to add on to that.

        Very few programmers know the prior art wrt. OOP, or have worked with the kind of code in which OOP is done well (I guess "OOD", using the terminology from the article). Instead, almost all junior (even senior) programmers I encounter parrot something along the lines of OOP being too enterpris-y and crufty, and something about inheritance being stupid. OOP is dismissed out of hand. It's high time for a correction in that mindset. The ability to structure your data and the operations on that data together in place is incredibly powerful, and OOP is a good approach to do that.

        Schools are partly to blame. They teach OOP as if it is an exercise in abstracting some sort of reality (e.g. "a dog barks, a cat meows, and both walk"). But that approach falls apart for the sort of concepts programmers work with. OOP is at its core a way to structure code, and to do so cleanly, to avoid repetition, and to enable easy navigation through a program. It is not intended to be mental map of some external reality.

        • discreteevent 2015 days ago
          >The ability to structure your data and the operations on that data together in place is incredibly powerful.

          Agreed. In a lot of cases if you don't have objects (the good parts) you are doomed to reinvent them:

          https://www.cs.cmu.edu/~aldrich/papers/objects-essay.pdf

          "Linux uses service abstractions in order to support multiple file systems. There are vtable-like structures such as file operations that are used to dispatch operations such as read to the code that implements file reading in a particular driver."

          • blarg1 2015 days ago
            I don't really see a problem with reinventing things when you need them. Classes etc are just syntactic sugar on top of functions and structs.
      • jshowa1 2015 days ago
        Just because prior literature exists, does not mean it should not be superseded. For example, generics wasn't even a thing when OOP originally started and yet LINQ and basic list ADT's wouldn't be as powerful without it.
        • dragonwriter 2015 days ago
          > For example, generics wasn't even a thing when OOP originally started

          Generics werent part of the earliest OOP because generics only make sense with static typing and the earliest OOP languages were dynamically typed; generics were around other places around the time of early OOP, though.

        • mehrdadn 2015 days ago
          > Just because prior literature exists, does not mean it should not be superseded. For example, generics wasn't even a thing when OOP originally started and yet LINQ and basic list ADT's wouldn't be as powerful without it.

          No, this not an example... generics did not "supercede" OOP.

          • jshowa1 2015 days ago
            My point is that earlier OOP documents did not implement generics.

            When they were finally implemented in OOP, it superseded the original intentions of OOP.

            The question is, are we now supposed to remove generics because they don't conform to the early literature of OOP?

            Looks like the OOP edited his comment though, so my point is irrelevant.

            • dragonwriter 2015 days ago
              > My point is that earlier OOP documents did not implement generics.

              > When they were finally implemented in OOP, it superseded the original intentions of OOP.

              No, when they were implemented in static OOP, it brought static OOP closer to intentions of the original, dynamic, OOP, where generic-ness doesn't require parametric polymorphism.

            • mehrdadn 2015 days ago
              > My point is that earlier OOP documents did not implement generics.

              So what? Early cars didn't have AC, therefore AC superceded cars? Or therefore I'm somehow arguing we should use AC instead of cars?

              > The question is, are we now supposed to remove generics because they don't conform to the early literature of OOP?

              Who ever claimed such a thing in the first place? It certainly wasn't me. If there is any question like this under discussion, it is whether OOP should should be removed because generics somehow superceded them (your idea), not the other way around. In either case the answer is clearly No because the idea is obviously ridiculous and not something anybody suggested.

              > Looks like the OOP edited his comment though, so my point is irrelevant.

              Not sure what this is referring to, but I haven't been able to agree with your comment since it was initially written.

              • jshowa1 2015 days ago
                So you don't really know what I was originally talking about yet you continue to act like you do? My point was in a response to the OP's implication that somehow the original OOP literature was perverted by misunderstandings and wrong implementations of the current generation, as if the original OOP documents were somehow "pure".

                I pointed out that just because the literature is original doesn't mean it can't be superseded. I gave generics as an example that was later implemented in OOP languages and fail to see how implementing these perverted the original intentions.

                I was then told that OOP intentions were originally meant to be dynamically typed and that the static typing of generics was meant to put it towards the original intentions of this dynamic structure (this is untrue because Alan Kay just didn't like static typing, but didn't make dynamic typing a requirement for OOP). Upon further research, the earliest OOP concepts were explained in the 1960's and the first OO language (Simula) was statically typed and a superset of ALGOL 60 which was a language made in 1960, with Simula following in 1965. Smalltalk came in at 1972 (around the time of generics) and is considered the definitive OOP language which is dynamically typed.

                So its hard to say, without direct sources, what the original intentions of OOP were, but considering OOP appeared before the first languages that contained generics (i.e. 1970's), generics is an idea that superseded OOP.

                Considering the utility of generics, its clear that later concepts that were added to OOP did not somehow perverse the original literature.

                So we've established the following: 1) The earliest OOP language (Simula) was statically typed. 2) Generics came in after Simula 3) The original intention of OOP could probably be attributed to Alan Kay, who created Smalltalk, but it borrowed heavily from Simula. And while Alan Kay coined the term OOP, the idea was not created in a vacuum as OOP concepts predate Smalltalk.

                Hopefully this provides some clarification. But my guess is people will continue to misinterpret what I meant.

    • pjmlp 2015 days ago
      A specific church of OOP.

      OOP the Eiffel, Sather, Smalltalk, C+@, BETA, CLOS, SELF way isn't the same thing as most people learn in school as THE OOP.

      Just like there isn't a single way of doing FP or LP.

      Also lets not forget that all successful FP/LP languages are actually multi-paradigm and also include OOP concepts.

    • swagtricker 2015 days ago
      >The great architect has the foresight on how the code will be used in five years and design it accordingly.

      Isn't he better off taking that crystal ball that gives him the foresight, using it to pick the correct lottery numbers and simply retiring?

      • pvarangot 2015 days ago
        Predicting position and velocity of little plastic balls tumbling inside some rotating container is a very different problem to predicting the behaviour of future API consumers. Specially if those consumers work in the same company you do and have shared objectives.

        So no, he's not better using that "crystal ball" to predict the lottery.

      • std_throwawayay 2015 days ago
        Many applications run maybe 10 times on real data and then their purpose is fulfilled. You don't need to design a microservice architecture with redundant servers when a simple shell script could do the job better.

        You need to foresee the scale of what you're building and how you would proceed to the next level. Some things must be solved right before the first deployment because you can never change them after the application is deployed. You must know what these things are and solve them right. You must also reduce their number ideally to zero if possible. You must use solutions that allow refactoring and later scaling in areas where your crystal ball is not sure. If the hard things are solved correctly you can use average workers to do the rest and it will work well.

    • Nursie 2016 days ago
      Yup, I like to think the religious war has subsided, and we're a little more free to do things in ways which work, are concise, readable and maintainable, even in previous bastions of enterprise OOP fervour like Java.
    • prophetjohn 2015 days ago
      > The great architect has the foresight on how the code will be used in five years and design it accordingly

      Perhaps this is a function of me working in startups and consulting my whole career, but it seems extremely misguided, if not negligent for an experienced engineer trying to design for use cases five years in the future. Five months into the future is even pushing it.

      What kind of companies operate in this way?

      • PeterisP 2015 days ago
        Probably every company outside the startup domain? If you're working on product-market fit, then you can expect to discard lots of systems, however, that's a niche and even startups are only temporarily in that position (unless they fail). For any business where there is a clear product-market fit (which, employee-wise, is pretty much all businesses) the systems rarely go away, they accumulate - if you're not a startup, or if you have found your product-market fit, then you expect that your products and processes won't disappear after 5 months or 5 years, and neither will the code that supports them, unless it's so broken that it's prudent to invest in a full rewrite.

        Even if a company fails, their products, processes (and code) usually get absorbed by some other company and need to be maintained - startups get acquihires that keep teams but discard products; "normal companies" get M&As that discard headcount but keep product lines, divisions and processes that require lots and lots of running code. The large companies often have multiple "inherited" codebases from all the other companies they have absorbed. And there is a lot of old code running; nothing is as permanent as temporary code - I have seen comments stating "this won't work properly on the boundary between fiscal years, but the system is scheduled to be replaced by then" that were made IIRC 6 years before I was looking at that system, so it obviously did not get replaced back then. In many industries a 10-year old company is a young company; heck, most of the current "internet startup unicorns" are 10, 20 or more years old; in established industries (you do know that the vast majority of software people work in non-software companies, right? most code is written for internal business needs, not sold as a service or product or consulting to others) there is a lot of mature code serving business processes that have been there for decades, will be there for decades, but often have some changes that require also code adaptations. The same goes for all the code that's inside industrial products - in the automotive industry, in home electronics industry, etc; you may have a new model of car every year, but most of the code in that car will be much older than that.

        I mean, the trivial fact is that if we look across the whole industry, all the statistics show that the majority of programmer manpower is spent on maintenance. So the total costs of software are dominated by how easy it is to maintain it, and a lot of that comes from proper design that takes into account what the likely needs are going to be after a bunch of years.

      • Fellshard 2015 days ago
        Consultant here, and I think that's a big blind spot we tend to have: we don't stick around for long enough to see the consequences of what we designed, usually.
        • prophetjohn 2015 days ago
          I have more experience at startups than as a consultant. I was the 6th hire at a company that grew to 130 over three years and I was never thinking more than a few months in advance. A lot changes in five years — your customers, the competitive landscape. It's an enormously long period of time in technology. It seems like such a waste of time when you have customers that have real, unsolved problems today
          • discreteevent 2015 days ago
            Unless it's some one off batch process or a prototype, if the codebase doesn't last at least six months then it's unlikely to be something that makes any money.

            In my experience, badly designed code tends to become a net loss after a couple of months because after that time someone is going to have to modify or fix it.

      • ssijak 2015 days ago
        Companies that are here to stay and plan to stay for longer, not the hipster wannabe unicorn type
    • Koshkin 2015 days ago
      > everything is made of bits

      Bits are objects, too...

    • grawprog 2015 days ago
      I kind of like the way D handles oop. D gives you structs, classes and interfaces.

      Structs are stack allocated and have no inheritance. But are otherwise syntactically work like classes.

      Classes are heap allocated and allow single inheritance, unless the parent is an interface.

      Interfaces are similar to a class but it's member functions must be overridden. A class can inherit from multiple functions.

      I tend to use a mix of these and templates depending on the type of data i'm handling. I find it gives the best of whatever design pattern works well for different parts of a project without locking you into a certain paradigm throughout and still keeping everything fairly logical and coherent to read through and understand.

    • jshowa1 2015 days ago
      How has the church of OOP failed? Nearly every used language is based almost entirely on OOP. OOP makes organizing software and code reuse incredibly easy. The only real downsides to OOP is that its arguably slower and has more overhead. But that's only a problem in niche applications (ie. embedded apps).
      • simongray 2015 days ago
        > OOP makes organizing software and code reuse incredibly easy.

        That is the big promise and the big lie of OOP. It, in fact, accomplishes the opposite.

        The medium used across systems today is data, not objects. Your objects are not compatible with systems across the wire, they need to be converted to data (JSON, XML, ...). They're not compatible with your data base, they need to be converted to data (SQL, ...). And if you want to use other people's objects (say from a library) you first have to make a layer that translates them for your own objects, since objects from other systems won't directly fit the model of your own object system, they always need to be engineered in. And if the objects are encapsulating data that you actually need, but doesn't offer ways to get it (private methods), you often have to jump through hoops to get it.

        Not to mention the fact that OOP often entails immutability which leads to problems while doing multithreaded processing.

        Clearly the answer is to use a more data-oriented perspective and use a programming language focused around data. Clojure gets it right and that's what I use. It's all concise functional code that skips all that class creation OOP loves, instead operating directly on immutable data (numbers, strings, maps, vectors, sets).

        I recommend watching some talks by Rich Hickey (the guy who made Clojure). They're almost all excellent.

        • jcelerier 2015 days ago
          > That is the big promise and the big lie of OOP.

          Well I wonder all those reusable libraries that I'm using all the time such as boost, Qt, POCO, openframeworks, etc... come from then. Am I dreaming them ?

          > The medium used across systems today is data, not objects. Your objects are not compatible with systems across the wire, they need to be converted to data (JSON, XML, ...). They're not compatible with your data base, they need to be converted to data (SQL, ...)

          not all code on earth is your average server app that communicates with a DB and sends JSON to the internet. I don't think I have even one installed program working like this on my computers. However I have an office suite, a lot of GUI apps, media authoring software, music player, web browser, mail client.. and they are all built with OOP languages - C++ being the one used for the immense majority - and OOP patterns.

        • jshowa1 2015 days ago
          Except for the fact that objects can be serialized and ORM's exist to convert your data into OOP. How do you think Entity Framework works? How do you think Rails works? You can build almost an entire ORM from a database without even writing any code in Entity Framework.

          > And if you want to use other people's objects (say from a library) you first have to make a layer that translates them from your own objects

          Funny, I've been using .NET framework objects without any type of translation. And the point is to use inheritance to mitigate the translation.

          Don't blame the model for its poor use. If there's no way to get the data you need, then that means the class was designed so you didn't actually need it or there are other ways to get it (i.e. through an interface).

          >Clojure gets it right and that's what I use.

          Except Clojure uses OOP principles and even admits to saying it uses immutable objects in the form of interfaces. Interfaces are essentially stripped down abstract classes. How this is not a subset of OOP, I don't know.

          I'm sure Clojure is great, but can you write interactive applications with it without integrating OOP libraries?

          • beders 2015 days ago
            > Except for the fact that objects can be serialized and ORM's exist to convert your data into OOP.

            Except for the fact that there's usually a mismatch between how your database handles data and how you want to get them back into objects. Cue: Object-relational impedance mismatch.

            And while your objects become bigger and bigger and your domain more complicated, you end up relying on ORMs who keep re-creating those objects from the database with every transaction and are loading lots of data no one requested. And then you are wondering why your stuff doesn't scale.

            > Interfaces are essentially stripped down abstract classes. How this is not a subset of OOP, I don't know. It's not. Interfaces have been around way before OO entered the field.

          • simongray 2015 days ago
            > Except for the fact that objects can be serialized and ORM's exist to convert your data into OOP. How do you think Entity Framework works? How do you think Rails works? You can build almost an entire ORM from a database without even writing any code in Entity Framework.

            That was actually sort of my point. You need all of this extra stuff _because_ your code is all objects. Data doesn't get serialised, data just gets sent and then it gets received. Why should you spend time serialising and de-serialising an object, when you can just send your map data structure directly? Maps can be represented 1:1 as e.g. JSON. Any JSON data is basically a big map data structure. It's one function call instead of hours of writing ORM classes or custom serialisation methods just to send some data over a wire.

            > Except Clojure uses OOP principles and even admits to saying it uses immutable objects in the form of interfaces. Interfaces are essentially stripped down abstract classes. How this is not a subset of OOP, I don't know.

            You don't use interfaces in Clojure, you tend to use multi-methods for most purposes where an interface is needed.

            I wouldn't say Clojure uses OOP principles. Its core is written in Java, so obviously that part is forced to use objects, but that is only used to create the immutable data structures used in Clojure, which are represented as data literals, not as objects. You typically don't operate on objects in Clojure unless you're doing interop with Java or JavaScript. Instead what you do is use pure functions that take immutable data as input and spits out new immutable data. There is no object to consider, only the raw input data. A vector or a map is as much of an object as a struct or an enum. Those are data types that also existed in C, not exactly intended as an object-oriented language.

            > I'm sure Clojure is great, but can you write interactive applications with it without integrating OOP libraries?

            Well, yeah? Why wouldn't you be able to?

            • jshowa1 2015 days ago
              >Maps can be represented 1:1 as e.g. JSON. Any JSON data is basically a big map data structure. It's one function call instead of hours of writing ORM classes or custom serialisation methods just to send some data over a wire

              Again, you don't write the ORM classes, the framework does it for you.

              And what you advocate is essentially sending a table over the network. So, what happens if your data within that map is complex? Are you suggesting to send every piece of a complex data type over the wire in separate chunks? If so, how do you relate it in the application? You still to make some sense of that JSON data in your application. Having it in a big map structure is akin to a god object.

              I my mind all you're doing is masking objects in different concepts just because you don't like using classes.

              • robto 2015 days ago
                And now you have to rely on a framework, where there's more opportunity for leaky abstractions, more surface area where bugs can show up.

                I'm not sure what you mean by complex data - data is data, and using a serial format like edn allows you to encode a lot of different stuff as data - even functions. I think you're stuck in the oo mode where you're passing around objects and classes instead of just data. Data is so much easier to deal with!

      • std_throwawayay 2015 days ago
        It failed by teaching that every data structure should be put into classes using many levels of inheritance, interfaces, encapsulation, accessors and the whole shebang when the thing really is just a plain old integer. OOP is really useful and powerful in many areas and applications but it is not the only tool that has to be used for everything.
        • jpindar 2015 days ago
          That sounds like a failure of the teachers then, not of OOP.
      • macintux 2015 days ago
        There are more things in heaven and earth, Horatio, Than are dreamt of in your philosophy.

        OOP-based languages are certainly more common, but definitely not the only game in town. Clojure, Erlang, Lisp, Perl 4, Forth, Fortran, Haskell...

  • Sharlin 2015 days ago
    I’d be very wary of hiring an ”OO” dev who can’t reasonably formulate what the SOLID principles are and why they exist.
    • gambler 2015 days ago
      SOLID is mostly a bunch of over-confidently stated opinions.

      Here is my over-confidently stated opinion: if a principle is not applicable to Smalltalk 74, it is not essential to OOP.

      The only rule in SOLID I would say someone should follow at all times is L, and even then only in statically typed languages.

      This should be obvious. For example, can you define "responsibility" in any way that's not entirely gut feeling? No. So how can you fault someone not knowing about a rule that's based on gut feeling?

      • jschwartzi 2015 days ago
        It's not really gut feeling. The following is very abstract but it's how I think about the "single-responsibility principle."

        When you're designing a software system you need to step back from the individual components for a moment and consider the overall system -- who is using this system, what is the problem being solved here, and how does this solution relate to other problems and solutions within the system. It's not obvious only if you don't completely understand the problem domain, which most people don't.

        If you find yourself thinking that some object's responsibility is ill-defined, you need to talk to whomever authored that part of the code to see if there's something you're missing about the system that the code controls. Either you are or they are, and at any rate the conversation will make the software system better somehow, which is the goal.

        So once you understand the system and you understand the problem that you're trying to solve, and you think you have a solution, it's an exercise for you to sit down and identify specific activities that the software system has to undertake in order to solve the problem within the context of the system. You can consider these "responsibilities" at a top level and start partitioning them up further into classes that mutate and emit data in response to data received from other components of the system. The boundaries of these classes are interfaces, and each interface should be responsible for operating on a specific kind of data in response to its inputs.

        Now the interface itself also needs to be defined in terms of the problem the class is solving. So you use words like "setMotorSpeed" on a class called "ConstantSpeedServo," and your ConstantSpeedServo might be composed with a "ValveController" which is responsible for controlling the position of a valve based on some input data.

        This all goes back to thoroughly understanding the system you are controlling so that you can write code in terms of that system, but it goes beyond that in that you have to be very explicit in your understanding of what it is the system does in order to write software that accomplishes that goal.

      • Sharlin 2015 days ago
        They’re guidelines and tools. You have to know how and when to use them. There are no unambigous rules in programming beyond what the language itself enforces. But that doesn’t mean some rules aren’t useful. As the saying goes in science: all models are wrong but some of them are useful. Or in art: you have to know the rules before you can break them.
    • jimmy1 2015 days ago
      Even Solid, like OOP is one of those concepts that has morphed more into an ideology than it needs to be with blind faith adherents

      https://speakerdeck.com/tastapod/why-every-element-of-solid-...

      I'll take someone who writes simple code that can be easily changed 101 out of 100 times over a SOLID, OOP Adherent.

      • neeleshs 2015 days ago
        That presentation basically says "write simple code". I'm not sure how is that helpful to anyone.
        • jimmy1 2015 days ago
          It's immensely helpful.

          Stuck in a rut designing your type hierarchies and making sure they are perfectly SOLID? STOP. Write the simplest, most clear and concise thing that works, refactor it once it gets too big to fit in your head.

          Not sure if your AbstractFactoryBeanProxyImplImpl is generic enough to cover all use cases? STOP. Write the simplest most clear and concise thing that works, refactor it to adapt to more problems as needed.

          Tired of fighting your type system because your stuck in a wheel of is-ha has-a is-a has-a? STOP. If it acts like a duck, and quacks like a duck, it is a duck! Write a duck!

          • neeleshs 2015 days ago
            I'm by no means advocating SOLID is the right/only way. I agree with "Write the simplest, most clear and concise thing that works", but it offers no help on how to do that. The how comes from experience. Simplifying complex and large things takes effort and thinking. SOLID helps with that, if used right and not as a religion. I feel "do the simplest" is way too generic an advice.
          • jschwartzi 2015 days ago
            "You Aren't Going To Need It" is the most overlooked piece of advice anyone has ever given anyone.
    • jcelerier 2015 days ago
      I'd be wary of companies hiring "OO" devs.
    • ozim 2015 days ago
      Depends on what this dev would be building, if you have to build framework or you build a library then I agree. If you build next off the mill "business" system then...

      Single responsibility principle, yes nice but we have deadlines and no time to argue if that one function should be somewhere else, fix it when we have some slack.

      Open closed principle, you don't have to think about if you use DI and have interfaces.

      Liskov substitution is useless, you should not use inheritance but composition.

      Interface segregation, you have to think about it but it is a lot like SRP.

      Dependency inversion, interfaces plus DI framework takes you far enough.

      So IMO if one senior guy sets up project with DI framework and gives general direction, someone who can't formulate SOLID still can be good OOP developer.

      • Sharlin 2015 days ago
        > Liskov substitution is useless, you should not use inheritance but composition.

        Liskov substitution is more about interface inheritance than implementation inheritance, really. LSP basically says that the implementer of an interface must obey the contract the interface specifies. As a real-world example, throwing UnsupportedOperationException in a method implementation is an LSP violation. Of course, this makes it vital to get the interfaces themselves right, carving the solution space at its joints.

        • ozim 2015 days ago
          Ok yes, it is about subtypes and interface is quite of an abstract base class. But if you have interface as in Java or C# and maybe 2 or 3 implementations without complicated type hierarchy under it, it is not an issue in practice to be not strict about contract, because you can spot deviation from it and fix it quick (ideally you also have integration tests to spot it for you).
          • jschwartzi 2015 days ago
            I think the point is to forget about specific language features for a moment and think about interfaces the same way a mechanical or electrical engineer does -- as things to be glued together.
            • ozim 2015 days ago
              No, Liskov substitution is much more abstract level, if you go with physical interface definition it does not work at all. It is not about attaching things but about type hierarchy. Thing you describe does not account for Covariance and Contrvariance and then it also goes into generic types which also huge abstraction which does not fit in physical world.
    • EliRivers 2015 days ago
      Do they have to agree with them, or just be able to name them?
      • Sharlin 2015 days ago
        Well, it's definitely a plus if they can convincingly argue against any of them. They're tools, not dogma.
  • beders 2015 days ago
    I think OOP is fine as long as it's the only thing you are doing and as long as it is single-threaded.

    Problems will occur if you need to convert the innards of your objects to data (i.e. JSON etc.), if you need to materialize objects from data (i.e. ORM) or if you need to write multi-threading safe code.

    If you expose things as data, just be honest about it and treat it as such. It's already out in the open, why hide it in objects again?

    • jschwartzi 2015 days ago
      > I think OOP is fine as long as you are doing it and as long as it is single-threaded.

      I don't really see how threading has anything to do with it.

      What OOP allows you to do is choose what terms you want to express your solution in. You can choose language that exposes thread-safe aspects of your problem domain without requiring the user to be aware that they are expressing things in thread-safe terms. In my last project one of the early elements of our architecture was a thread-safe message queue for each thread to receive messages from across the thread boundary. Discussions on our team involved talk of messaging and threading because those were the abstractions we chose to use to express our solution.

      We knew that order of access was important and so we chose to make it explicit in the language we used to express the problem. And the abstraction we chose allowed us to hide the details of the problem in such a way that we did not have to worry about memory protection as long as we were consistently using the architecture we built. That was a concern of the messaging system's maintainer. We could just as easily have chosen rows and columns and operators on such and expressed the same solution in those terms. What mattered to us was making sense of the problem, domain and solution.

      It really doesn't matter which paradigm you use. What matters more is that you express your solution in terms that are consistent with the problem domain, and that you actually make an effort to understand the problem you're trying to solve well enough to express the solution. Anything beyond that is just sugar.

      • legulere 2015 days ago
        OOP is linked strongly to mutable objects, which is inherently thread-unsafe. Sure you can get thread-safety in OOP, but it’s hard. It’s the same reason why global variables are bad but worse.

        Mutability is rarely needed and often makes things more complicated than needed.

        • jschwartzi 2015 days ago
          Well, my original point is that thread-safety is easy with the right abstractions, and that choosing your abstractions is a conscious thing. So I don't see how mutability is "inherently thread-unsafe." Rather, programmers are "inherently thread-unsafe" and we have a responsibility to understand the problem domain and choose abstractions that make sense to us and that actually solve the problems we're encountering.

          Now if you want to talk about specific languages, then I would generally agree with you about C++ and C, but then there are ways you can design a program in those languages to be thread-safe. With FP you're just choosing a different solution to the problem which comes with its own drawbacks in some cases.

          • legulere 2015 days ago
            I just doubt that those abstractions are easy and would say that they often break. Blaming the programmer for thread-unsafety in e.g. Java is like blaming programmers for memory unsafety in C.

            I agree if you stay in purely functional programming languages you run into points that are solved better with mutability. However those parts of a program are small and most parts are expressed better by pure functions.

            • jschwartzi 2011 days ago
              I wouldn't say that they "often break" considering that we never had a single issue with our software that could be linked to any of our threading abstractions in the 4 years of rigorous development and testing. But rather it's important to be clear about how your abstractions are to be used, and that you don't inject a lot of special-case logic into it. In our case we had to accept significantly reduced performance in exchange for the abstractions, but in the end it was worth not having to debug everyone's thread-safe code separately. A lot of it came back to choosing an abstraction that was simple enough to be well-understood and about not pushing the envelope too far.
        • mikmoila 2015 days ago
          I'm not aware of such "linking". It's very common to write classes of which instances are immutable or thread-safe.
          • legulere 2015 days ago
            It is doable, but it is a concerted effort, like doing OOP in C. It’s hard to keep the discipline especially when new people start working on the code
      • beders 2015 days ago
        OOP has no built-in facilities to deal with multi-threading. It's orthogonal to it and that's a problem.

        Thread safety can't be expressed or enforced and you can't grab a bunch of classes from somewhere and assume anything about their fitness for multi-threaded code.

        Typically one has to carefully design a class for multi-threaded use. Reasoning about state inside objects then quickly becomes infeasible.

        • p2t2p 2015 days ago
          Which _paradigm_ has those facilities? Not a language, but a paradigm?
          • trylist 2015 days ago
            Pure functional programming has this property by its nature. Anything immutable cannot be changed during another threads execution.
      • humanrebar 2015 days ago
        > You can choose language that exposes thread-safe aspects of your problem domain without requiring the user to be aware that they are expressing things in thread-safe terms.

        What's a couple orders of magnitude of performance and some heavyweight link time dependencies between objects?

    • Hodgman 2015 days ago
      Follow up articles in that series are going to show how to make it multi-threading and OO-compliant. "Typical" bad OOP code is a threading nightmare because the flow of control and flow of data becomes impossible to follow - it's all just random objects calling random objects calling random objects. Not spaghetti code, but spaghetti flow. To write threadable OOP, you need to avoid fine grained polymorphism and deep call graphs. Instead of doing work immediately by calling out to the next object, return some results to your caller and allow them to make the call. This also allows your caller to collect large batches of work a'la DOD... which often actually increases performance (much better I$ and D$ usage) AND it's also much closer to old school OO message passing theory, and assist in writing simple, decoupled components.

      I'd go as far to say that even if you're not targeting multiple threads, accounting for them in this way (NOT the stupid "just put a lock on each object" way) results in much better/simpler code.

    • seanmcdirmid 2015 days ago
      OOP with threads is fine. Mutability is orthogonal to OOP anyways.
    • the_af 2015 days ago
      > If you expose things as data, just be honest about it and treat it as such. It's already out in the open, why hide it in objects again?

      What if this exposure occurs at the boundaries of your system, or when interacting with certain libraries? Surely there's value as "mostly" handling some things as objects within your system, even if you expose their innards and maybe break their invariants at the boundaries.

    • Koshkin 2015 days ago
      > as long as it is single-threaded

      Yet, there is a well-known pattern, which is to have objects each running on its own thread. (Incidentally, this gives a new - I'd say, true - meaning to the notion of 'message passing' as the idea of how objects are to communicate.)

      • beders 2015 days ago
        How many projects do you know that do that? Yes, the original intent of OOP is message passing between objects. But no on thought about massive parallel computing back then.

        And how many objects do you want to run in its own thread? 10k? 100k?

        • Jtsummers 2015 days ago
          I'd be comfortable with hundreds of thousands or millions of concurrently executing objects. If your system was actually operating in the "message passing" concept of OO, this wouldn't be impossible to support.

          Now, you'd need something lighterweight than many threading libraries (such as C++ and Java programmers usually use), you'd want something closer to Erlang's process model.

        • Hodgman 2015 days ago
          This is called the actor model, and we actually implemented it in a prototype game engine once. We made one thread per CPU core and collected messages in "cycles" - where you'd process a batch of messages in parallel, which could produce the next batch. Messages would be sorted/grouped by target object address and then partitioned among the thread pool for execution. Most of the architecture was wait-free (no locks/atomics) so it really was quite fast. This resulted in a C++ framework where you could write 'typical' OOP code and it would automatically be deconstructed into call-graphs and safely scheduled for execution across any number of threads in a completely deterministic way, without locks. We abandoned the prototype because the "typical" OO behaviour of having deep call graphs is not actually something we want to keep, making the purpose of the framework kind of shaky. Also, while performance was good, the characteristics weren't quite suitable for games -- it worked best if you had large objects, receiving multiple messages each, doing computationally complex work. In our games though, we typically have huge numbers of very simple objects, so batching messages by class instead of by object typically gives the best performance.
    • kazinator 2015 days ago
      > if you need to convert the innards of your objects to data

      Not to mention: when you need to make objects correspond to a database.

  • jorgeleo 2015 days ago
    "Before you decide that OOP is shit and ECS is great, stop and learn OOD (to know how to use OOP properly) and learn relational (to know how to use ECS properly too)."

    This is why DDD needs to be in place before using OOP

    • LolNoGenerics 2015 days ago
      So DDD is now a prerequiste to use OOP? Wow!
  • tomelders 2015 days ago
    Is FROOP a thing? Cos that’s what I’m doing it seems.
    • da02 2015 days ago
      Functional Reactive Object Oriented Programming?
  • leowoo91 2015 days ago
    OOP in game development is in good use with engines like UE4 and Godot, so it is about choice. What really matters is how comfortable you are with the way you develop the game.
    • gracenotes 2015 days ago
      Oddly enough I've been getting into UE4 and things would be so much easier for me if the core classes were better decoupled.

      For instance, I'd like to have a quadraped skeleton with the ability to apply a movement vector. Unfortunately, in the Actor->Pawn->Character hierarchy, you cannot have movement (applying vectors, walk/jump/fall state) without a capsule root component - which is the only one respected for collision - so either I have to rewrite all of the movement code or somehow make the capsule vestigial and carefully maintain its state.

      In short, in some cases it's useful to say that a Pawn is an Actor with a set of extra features, or that a Character is a Pawn with a set of extra features, but even there, the way inheritance forces you to adapt an exact set of extra features and no others and also those features are not piecewise reusable elsewhere is a pretty big structural problem.

      I've already done enough arguing about OOP for one career in programming, I think, but the more I see what's out there, the more that I think the real enemy is any use of class inheritance - even the TAPL formalization of OOP uses interfaces only.

      • leowoo91 2015 days ago
        I certainly agree, I ever used ECS from the beginning. Just surprised when I saw a new engine like godot follows OOP. It might be because engines are better fit but not the game itself and gets omitted easily.
    • leowoo91 2015 days ago
      I don't get it when a functional programming comment gets highest score in a game programming pattern thread while a game-dev related view gets the lowest.
  • SolarNet 2015 days ago
    Everything here is bad and wrong on so many levels. It's hard to even wrap my head around the amount of wrong going on everywhere here.

    The initial Object Oriented (OO) code - as partially demonstrated by the author, and more succinctly by the grand-author - is badly designed. In the grand-author's slides they remove a number of these stupidities, which the author appears to ignore (both for final performance comparisons and for understanding why they were even there). Notably they (the grand author) originally built a reflection (runtime type) system [0] directly into their game objects (application level code). This of course means they then ended up fighting their programming language over the performance of this as they attempted to use and improve it.

    The author's improved code is also a total failure. He removes a significant amount of the flexibility in the original system, which was a requirement of the problem space. By removing features he gains a significant amount of performance (in actuality about 2x - not 10x - compared to the grand author's slides). The lack of understanding why those features were there in the first place demonstrates he doesn't understand the actual design space of the toy demonstration code. It also demonstrates he failed to read the grand-author's slides where they go through some of these changes and why they aren't viable.

    The author also fails to even discuss the Entity Component System (ECS) which it should be noted is still faster (and feature intact!) than the author's code. I cannot be more emphatic on that point, an ECS is a solution to the performance of run-time composition problems, and it still worked even better than an attempt to do the OO solution right (again by removing features!).

    Though the author makes an excellent point about how most ECS solutions tend to fight the programming system they are in, he doesn't really explain or demonstrate it. What the grand-author did wrong here (though probably actually not in their talk considering who they work for) is not point out that the ECS should be part of one's programming tool to be most effective. Notably the ECS optimization should be part of one's programming language [1] (a game engine's programming language - like Unity's - would count).

    Point is this author is very wrong and does not understand design (in general) nor the entity component system pattern in specific at all. The grand author's example code base is disingenuous of actual OO principles, and does not provide reasonable advice on how to deploy an ECS. Oh and they both got wrong that ECS solutions should be part of one's programming tool, not application space.

    [0] Engines are often attempting to solve a sufficiently complex problem space that they require runtime type systems. This is not something one can avoid unless writing a bespoke "engine" for a single game.

    [1] Unless your programming language is sufficiently advanced to allow creating - effectively - compiled code at runtime as a generalized library. Which C++ is because of template meta-programming, but the included example code is not.

    • ByThyGrace 2015 days ago
      Can you cite or link to properly implemented ECS solutions, in your opinion?
      • SolarNet 2015 days ago
        I think the C++ library https://github.com/skypjack/entt gets pretty close to the ideal for a purely compile time implementation. It is implemented with modern C++ template meta-programming - which is to say it is basically a DSL encoded into C++ and can generate arbitrary code at compile time - while better than it used to be to read C++ template meta-programming is still very difficult to read. It also comes with a number of utility libraries for using it efficiently and correctly, including a signal library, service locator, and scheduler (among others).

        It still has it's limitations though. Because it is a compile time library, it can't do any runtime optimizations like it could with a JIT. Also, because C++ reflection is atrocious, it doesn't provide the best support for runtime type manipulation (reflection features are often paired with the component pattern implementation, especially in general purpose game engines). This comes back to dynamically meta-programmed languages (e.g. python, lisp) that could theoretically out perform even ENTT using a JIT - with the appropriate reflection features - and meta-programming. However an ECS only really makes sense in a non-garbage collected language (or if the language exposes a non-GC meta-programming interface). Since there aren't really any viable JIT (+ reflection) + meta-programmed + non-GC languages out there, I don't think a perfectly correct implementation exists.

        I think an ideally implemented example will eventually be Johnathan Blow's experimental language Jai (or a library for it) given what has been stated publicly about it (metaprogramming, JITted, reflection, not garbage collected, has built in support for SoA types). But that's not a viable example to look at at the moment.

    • Hodgman 2015 days ago
      The fact the the original code is a straw-man is discussed. The fact the flexibility is being removed is discussed too, along with why it was there and hits on better ways to re-achieve it later. It's mentioned that these were going to be covered in a follow-up. You need to work on your speed reading skills before throwing shade...
      • SolarNet 2015 days ago
        > The fact the the original code is a straw-man is discussed.

        Yes, but instead of timing it against the improvements the grand-author made to the straw man, you still timed it against his straw man.

        So I'll quote you: "You need to work on your speed reading skills before throwing shade..."

        > The fact the flexibility is being removed is discussed too

        You state: "Why do these frameworks exist then? Well to be fair, they enable dynamic, runtime composition. Instead of GameObject types being hard-coded, they can be loaded from data files. This is great to allow game/level designers to create their own kinds of objects... However, in most game projects, you have a very small number of designers on a project and a literal army of programmers, so I would argue it's not a key feature."

        Which is failing to recognize that the whole point of the original code - written by a game engine developer - is to demonstrate exactly how to do this for performance and flexibility for any small team.

        > along with why it was there and hits on better ways to re-achieve it later. It's mentioned that these were going to be covered in a follow-up.

        With out it being there, I cannot judge it. But considering that you removed features and barely managed to match the ECS for performance is... not promising.

        The point of the ECS is to be a very efficient solution to the run-time composition problem. You removed run-time composition and still weren't technically able to be more performant than it with your solution. You are comparing apples to oranges and still loosing on arguably the most key aspect of the entire problem space!

        • Hodgman 2015 days ago
          Calm your titties my dude, you're angrier than me, here... You're still missing my point, claiming I'm making points I'm not, and insulting me for it. Grab a handful of good faith, please.

          I timed it against the bad starting point because that's what Aras did too when showing that "ECS beats OOP by 10x". My version wasn't designed to be optimised - it was designed to be a simple rule abiding OOP rewrite. The point of the blog was about the annoying ECS evangelism trope of comparing ECS to bad OOP code and showing massive wins - the simple rewrite shows that the optimised ECS version is like a 1.03x win, not a 10x win.

          The fixes that Aras makes to the starting straw-man code help perf, yes, but they fly in the face of idiomatic C++ and are still bad OOP. Still, I can update the graph is perf resufrom each of Aras's commits if it makes you happy.

          Run time composition is(will be) added in the next version in a way doesn't add any performance overheads at all (a benefit of using composition properly that the straw man code lacks). There's no need for bloated frameworks to achieve this. BTW I'm also an engine dev, previously at a 400 person company, and now indie for a small team. I'm familiar with many kinds of entity frameworks... Making a game without one is perfectly valid. Many of the big commercial games that I've worked on didn't have one at all (just normal/good code)...

          The particular ECS implementation from Aras (again its bad due to it being learning material, not a real project) has lots of holes too. The memory usage is atrocious due to it allocating one component per entity regardless of need, but he presents a memory-usage win against his straw man OOP version! His example framework also has silly restrictions on composition, such as a limit of one component per entity. This kind of stuff doesn't fly outside of toy game projects.

          • dang 2015 days ago
            Welcome to HN! (I'm a moderator here.) Please stick to civil, substantive comments, regardless of how wrong someone else is (or you feel they are). Swipes like "calm your titties" (or even telling people to "work on their reading skills", as you did upthread) are the sort of thing we ban accounts for. We're trying hard to prevent this place from sinking into a toxic swamp, as so much of the internet has become. Doom is probably inevitable in the long run, but we're still hoping to stave it off for a while.

            If you'd please read https://news.ycombinator.com/newsguidelines.html and follow the rules when posting here, we'd appreciate it.

  • spacesarebetter 2015 days ago
    and the website is dead as well