[HN Gopher] If Inheritance is so bad, why does everyone use it?
___________________________________________________________________
If Inheritance is so bad, why does everyone use it?
Author : harperlee
Score : 124 points
Date : 2024-04-11 06:35 UTC (16 hours ago)
(HTM) web link (buttondown.email)
(TXT) w3m dump (buttondown.email)
| signa11 wrote:
| huh :o) the wisdom since late 90's has been to avoid it like a
| plague, at least in c++ circles. for example, see numerous
| articles in 'guru-of-the-week' by herb-sutter.
|
| the same wisdom can be applied elsewhere as well, there is no
| need to keep discovering the same truths in different contexts
| again, and again.
| stingraycharles wrote:
| Yeah in C++ especially nowadays using compile-time traits is
| much, much more idiomatic.
| constantcrying wrote:
| >huh :o) the wisdom since late 90's has been to avoid it like a
| plague
|
| I am sure that hundreds of thousands of students have at some
| point heard that these hierarchical organization of data is how
| you should model a system.
| est wrote:
| IMHO inheritance works and only works for graphics related
| programming, e.g. GUI and games. Visual objects can be abstracted
| in the manner of inheritance.
| mattnewport wrote:
| Inheritance doesn't work well for games, that's why so many
| games take a component based approach like Unity, or full blown
| ECS.
| lewispollard wrote:
| Well, it does work well for the engine part of game engines,
| and graphics related code in particular as the GP says - it's
| just gameplay code where OO falls a bit flat.
| ipaddr wrote:
| Unless it'a role playing game.
| professoretc wrote:
| The _implementation_ of inheritance (more specifically,
| polymorphism and dynamic dispatch via vtables) is a problem
| in games, because it adds an extra layer of indirection, and
| screws with cache locality. But the _semantics_ of
| inheritance ( _X_ ISA _Y_ ) still apply. In an ECS, the
| implementation is different (struct-of-arrays) but you can
| still _think_ of an entity as "inheriting from" the various
| components its built from.
| fxd123 wrote:
| That's strange because I was about to comment the exact
| opposite. ECS is for games, inheritance is for almost
| everything else
| ipaddr wrote:
| For games having your character as a type of a general class
| fits well. Having enemies be a type of a class with a base of
| abilities can fit well too. Weapons, etc all module well.
| Game play. Scenes.
| davedx wrote:
| This has more or less been my experience too. Things like UI
| toolkits or game engine primitives are often solved nicely with
| inheritance.
|
| I've been doing a lot with LeafletJS lately, and it has some
| light inheritance around its drawing primitives (things like
| boxes, circles and lines you draw on top of maps). It works
| well.
|
| For 3D engines you still need to be quite careful with where
| you apply it though. It's possible to get into a huge mess if
| you use it too much.
| flohofwoe wrote:
| Especially in games, inheritance doesn't work all that great
| and had been replaced by composition via components a long time
| ago where each component implements one specific aspect or
| feature of a game object and can be (more or less) freely
| combined with other components.
| KevinMS wrote:
| and it became popular at just about the same time as GUI
| programming, coincidence?
| cess11 wrote:
| The picolisp database convinced me that it also can work for
| databases, where it's used both for data storage, querying and
| as a GUI backend.
| doomlaser wrote:
| Lots of design patterns have their uses, but can suffer when
| applied too overzealously.
| JauntyHatAngle wrote:
| Like almost anything in IT, if you are good at using a hammer,
| you treat everything like a nail.
| chii wrote:
| if you're good at using a hammer, the nail will be hammered
| in perfectly and without faults.
|
| The only problem is if you're bad at using the hammer, and
| you only know how to use a hammer.
| high_na_euv wrote:
| Do they? I rarely see people using it directly and I think this
| is fine
| angiosperm wrote:
| Most of us have no contact with Java. But Java offers little
| else than inheritance to use to organize a system, so Java
| coders use it for everything. They are not exactly wrong,
| except in using Java at all. But sometimes is all that is
| allowed.
| pgwhalen wrote:
| This is much less true for modern Java than it used to be,
| with records, pattern matching, sealed classes, etc.
|
| The trick though is you actually have to use modern Java,
| which means you need to both be on the right version of Java,
| and have developers that understand the value/power of these
| newer constructs. Which is surprisingly rare for a programmer
| that self-identifiers as a Java programmer.
| bedobi wrote:
| java offers composition, like pretty much all programming
| languages
|
| if you have a person class and you have students and teachers
|
| the correct thing to to is NOT to make student and teacher
| inherit from person
|
| it's to make student and teacher have a person attribute +
| the other attributes that constitute a student and teacher
| respectively
| kuang_eleven wrote:
| "correct" is certainly not the right way to put that.
| Inheritance and composition here are both fully valid
| methods for modeling the relationship, and the decision to
| use either should be dependent on how the models are being
| used and the expectation for future extension.
| angiosperm wrote:
| By "correct" here you only mean fashionable. Either
| approach works. Each has its merits and its costs.
|
| Any big enough system will have parts that are most
| sensibly built object-oriented, and other parts that are
| more reasonably functional, plus anything else you can
| think of.
| brodo wrote:
| Same here. For me, implementation inheritance is a code smell.
| But I rarely see it.
| KorematsuFredt wrote:
| Here is my take on it.
|
| At one point "Object Oriented" became "Blockchain" (or now Gen
| AI) of those times. You had to be "object oriented" in order to
| be taken seriously. This applied to everything. Even finished
| software products were called "built using object oriented".
|
| The esoteric concept of inheritance became popular after that. At
| some point it became so popular that people figured out that it
| is not really a good thing.
| blowski wrote:
| Pragmatists used inheritance because it gave a quick benefit
| now, and the longer-term costs were ignored. Some pragmatists
| became successful because they moved quickly, so the
| "Programmers from the Church of Purity" used inheritance
| because successful companies used inheritance. When inheritance
| was no longer the quickest way to move quickly, the pragmatists
| moved on. The Church of Purity now bangs on about Functional
| Programming in the same way for the same reasons.
| mewpmewp2 wrote:
| FP rather slows things down though.
| pyrale wrote:
| What specifically slows it down in your context?
| mewpmewp2 wrote:
| Doing every simple thing that used to be done in "normal
| procedural" way in functional paradigm instead when using
| something like Scala or fp-ts in TypeScript.
|
| Causing engineers to completely change their mental model
| of how the code runs, which I still have intuitive
| trouble imagining correctly and I see it with other
| developers as well. A lot of energy goes into trying to
| understand how to do a simple action. It is much harder
| to read the code and have a correct mental model as well.
| pyrale wrote:
| Would you say it's a retraining cost, or would you
| imagine still having this issue many years from now,
| assuming you keep working on it?
| mewpmewp2 wrote:
| It likely also depends on the person so I couldn't speak
| on behalf of everyone. I have been exposed to it to some
| extent, not the majority of work for over 5 years and it
| still intuitively is challenging for me. So similar task
| would take longer to code and existing code would take
| longer to understand. Significantly more effort would go
| to that.
|
| I feel like I spend more time and energy on how to get
| something done functionally rather than focusing on what
| the correct business logic should be.
| bedobi wrote:
| lol it's the opposite? map, flatmap, fold etc are very
| clearly defined operations with very clear use cases and
| rules. loops are not, you can do whatever you want (often
| mutating the underlying, rug pulling you every iteration)
|
| not being familiar with fp doesn't mean it's objectively
| worse
| throw_m239339 wrote:
| > Pragmatists used inheritance
|
| Because at some point mainstream OO languages made
| inheritance easy and composition harder, nothing more.
|
| Composition with Java used to be verbose. Inheritance
| declaration was simply a single keyword.
|
| If Java had "mixins" from the start, people would have used
| composition way more.
| StressedDev wrote:
| I saw a lot more composition than inheritance in C++ and
| C#. I think inheritance and polymorphism have their place.
| Like anything, they can be misused.
| pyrale wrote:
| > The Church of Purity now bangs on about Functional
| Programming in the same way for the same reasons.
|
| FP in the industry is a niche thing, so the two phenomena are
| not really comparable.
| blarg1 wrote:
| Also a semi religion, I'm always seeing people say before OOP
| everything was terrible, and everything good thing is due to
| OOP (eg structs, methods, polymorphism)
| bluetomcat wrote:
| In the 1990s, it coincided with the proliferation of GUIs and
| their respective programming interfaces. Most frameworks use
| hierarchies like Object->View->Control->Button->ImageButton.
| Then people decided that this is the way for modeling abstract
| problems that don't have to deal with visual or real-world
| entities whatsoever.
| dsego wrote:
| And you would get nice automatic completion when doing object
| name, dot, and waiting for the IDE to list all possible
| methods. This was exploratory programming, the copilot of its
| time.
| fardo wrote:
| I think there's a tension between the tinkerer, blogger and
| hobbyist side of the field, and anonymous 9-to-5 workhorse line
| of business programming in this discussion.
|
| The core reason everyone uses it seems to me to be essentially
| the same reason the former groups hate it - it's what everyone
| else does, it's an incredibly square and "day-job"-esque approach
| to one's tooling and architecture, it often reeks of bureaucracy,
| compartmentalization, and organizational superstructures often
| unrelated to the technical work, dictated primarily by managerial
| fiefdoms and needs to organizationally coordinate, and it's a
| well-defined space with extremely predictable solutions and
| headaches, and therefore has very little excitement or novelty to
| offer.
| Animats wrote:
| Many of the headaches associated with Rust come from an inability
| to reference from owned to owner. Affine types have much more
| impact on design than inheritance vs. composition.
| bl4ckm0r3 wrote:
| OOP is easy to understand and explain and it makes sense
| initially, plus a lot of people specialize in
| languages/frameworks that enforce it, so it becomes easy to get
| trapped in the OOP world. It also usually comes with DDD (which
| is a solution to a problem you shouldn't have had in the first
| place) which is a way to limit the damages of OOP into contextual
| areas. I also think the blanket statement (OOP is bad) does not
| apply necessarily everywhere. A good mix of composability and a
| bit of inheritance where it makes sense is the best way imo.
| cess11 wrote:
| I disagree. Starting with separating class and object have
| never been helpful when I've tried to teach computer
| programming, while functions seems to have been more intuitive
| to those people.
|
| After a while you get to closures, which are pretty much
| objects without classes, and then factory functions that
| produce closures and there you have something like a class as
| well. If I were to design a course I'd probably follow this
| flow to get to 'OOP' in that sense.
| xwolfi wrote:
| But you go from the bottom up here, I don't like to describe
| it that way.
|
| I prefer to say that we naturally classify objects around us
| using fuzzy boundaries like "a house", "a cat", which don't
| exactly mean anything: they are templates we use later to
| actually generate an actual house, or an actual cat (on a
| piece of paper as a drawing for instance).
|
| The world being separated between our internal classes and
| the interactive instances of them, we can code that way too:
| we look for what we actually need in a concept, name it and
| define only what we need, then instantiate several concrete
| actors interacting together. Each actor is complete, well
| defined, with contracts for interactions.
|
| You can then start building on top of this more complex
| systems, talking in English and defining rationally the world
| of your little model.
|
| If you start talking about functions, you're basically
| aliasing a memory address for a bunch of code: you'll goto
| that function address, with some parameters at some other
| address, do a bunch of things and put the result in another
| address for the next function to process. You're building at
| best a suite of pipelines, which ends up being a little bit
| too technical and static for my taste.
|
| Another way to defend modelling a software with classes and
| object, in my view, without trying to talk about functions,
| is the blank page effect: imagine arriving at random at some
| assignment. You understand vaguely the business problem, now
| you need to code a solution. You have 6 months, will generate
| a few millions a year and will require a team of 10 people
| eventually to test, maintain, operate and debug: you start
| with defining functions generating functions with callback
| parameters in Python, or you launch IntelliJ and you do a
| Java class model ? I'd be terrified modelling complex
| problems with just functional pipelines, I think it's just
| way more obvious to talk about objects at all time if you're
| gonna do something that is not just processing inputs into
| well defined outputs.
| bl4ckm0r3 wrote:
| I think you identified the selling point of OOP, it's easy
| to reason about it and logically it makes sense to human
| because we do think in terms of objects (the user, the
| order, the item etcetc) and responsibility segregation
| (User->login(), Order->fulfil(), etc). To your example of
| just launching intellij and using java (or ruby or
| whatever) to build something, yeah it works and it works
| fast and it's easy to add new features, and it's also easy
| to end up with a >3k loc User class in python that no one
| can touch (true story).
| cess11 wrote:
| I prefer to start with an interactive programming
| environment, like a REPL, to keep the feedback loop tight
| and code short. If I needed to talk for ten minutes about
| templates and instances and how you need to have a little
| meeting with yourself and do modeling I'd have lost their
| interest right away. These words mean nothing to a newbie,
| unless they're some kind of philosophy nerd so I can hook
| into ancient greek ideas about Forms or something.
|
| 'Here is a way to do simple math, here is a way to glue
| text to text, now that has grown a bit, you can shorten it
| for repetitions by giving it a name like this', and so on.
|
| And to me, starting out functionally is easy. Data is
| pretty much always a given, it's very rare in practice that
| I first need to model speculatively and then generate data
| ex nihilo unless I'm creating mocks. Usually there is a
| data source, a file, a network API, whatever, and then I
| start building against that. Small, simple things at first,
| like mapping all the input to an output interface, then add
| transformations and output for these, and so on.
|
| In general I spend more time thinking about the shape of
| data I already have than architecting code or inventing
| models.
| zelphirkalt wrote:
| Some modern programming languages do not even have classes to
| inherit from. Instead they have concepts like traits and structs,
| which decouple structure and behavior.
| julienreszka wrote:
| Nowadays I have rarely seen it if ever
| cess11 wrote:
| Smalltalk having a more or less revolutionary developer
| experience was likely quite important. A graphical interface to
| your system where you can traverse a tree and inspect
| _everything_, and more importantly, change it.
|
| In Smalltalks as I know them, which is sporadically and
| shallowly, inheritance seems to be used as a factoring tool, when
| you break down functions into smaller functions there's a
| structure in which to place them with the implicit incentive to
| put them as high up as possible.
|
| To me it seems to work pretty well, but it's also a rather weird
| environment where even classes themselves are objects.
|
| In Java one can get around inheritance by using injection into an
| attribute in a wrapper class that extends or changes an object
| API. This leads to a similar decoupling as more functional
| composition, but you pay by having more objects swimming around
| in the VM during execution. Extending on the class level (i.e. in
| declarations of classes, interfaces, traits and the like) instead
| of the object level might have effects on performance and
| resource management.
|
| Usually that likely doesn't matter, but it might. In Java-like
| languages I tend to use inheritance as a quick way to abstract
| over or extend functionality in a library class, I get the public
| methods 'for free' in my own class without having to write the
| wrappers myself. And that's usually where I stop, one level of
| inheritance, since Java isn't as inspectable and moldable as,
| say, Pharo, and doesn't give the same runtime ergonomics with
| large class/object trees.
| moshegramovsky wrote:
| I work on a very large codebase. We use inheritance and
| composition. I totally agree that inheritance should be used
| carefully. However, there are times when composition requires a
| lot more code and downstream maintenance.
|
| Let's stay I have a base class A. Let's say I have 70 classes
| that inherit from A.
|
| These classes must serialize/deserialize from disk.
|
| My choice here is that I can add a new data member to A and then
| update the read/write method in one place, or I add a new data
| member to all the classes that are derived from A, meaning that I
| get to update 70 classes. Seriously? Who would think this is a
| good idea? You'll have to write 70 times as much code... there
| will be times when that's definitely a very bad idea.
|
| Maybe don't use inheritance. Then give every class its own "Name"
| data member like std::string c_nName. Every class can have its
| own function to set the name, or we could use an unencapsulated
| free function and do things like C. Except then the experts will
| say things like "well, now you're doing C with classes". Then you
| get a ticket that the user can enter bad names. You can then
| figure out some way to validate the names, right? That could be a
| free function, or maybe write some class called NameValidator.
| Except now the experts say you're writing C++ like Java. Too many
| classes, too few classes, too many objects, too few objects, too
| may free functions, too few free functions.
|
| C++ expert of the month may say X, Y, Z, but look at Microsoft's
| APIs.
|
| Inheritance is everywhere.
|
| Look at any open source C++ project.
|
| Inheritance is everywhere.
|
| Does that mean that you should make something like:
|
| A
|
| --B : public A
|
| ----C : public B
|
| ------D : public C
|
| --------E : public D
|
| ----------F : public E
|
| Or this: A B \ / C
|
| Not unless you have a damn good reason. But the point is that
| inheritance is a valid tool and it can reduce bugs, code
| duplication, maintenance.
|
| Right tool for the job, yeah? I once read something in the C++
| FAQ that said something like "Don't take advice from people who
| don't understand your business problems."
| gridspy wrote:
| > Let's stay I have a base class A. Let's say I have 70 classes
| that inherit from A.
|
| The alternative case is that you have 70 classes that CONTAIN
| A. You still only have to change A.
| moshegramovsky wrote:
| Yes, but in my example, in the 70 classes, all the ::Read and
| ::Write methods must still say: a.Read( ...
| ) a.Write( ... )
|
| And depending on the use case and design, they also need
| methods such as: A& GetA() { return a; );
| const A& GetA() const { return a; }
| planede wrote:
| The answer is that you retain A as a pure interface and maybe
| you add a convenience base below A that adds the data member,
| then you inherit from from the convenience base for your 70
| leaf classes. You can also use CRTP for the convenience base,
| if it's appropriate. You can still avoid confusing interface
| and implementation inheritance.
| drewcoo wrote:
| Not even wrong.
|
| Inheritance isn't necessarily bad.
|
| If anything is common, someone will try to seem like a brilliant
| iconoclast by writing an essay against it. The particular essay
| in this case was from a Pythonista (not surprising, given
| Python's weird love/hate with objects):
|
| https://solovyov.net/blog/2020/inheritance/
|
| And not everyone uses inheritance.
|
| Maybe you have another question, like "why would someone use
| inheritance?"
| peter-m80 wrote:
| Speak for yourself, I don't use it.
| bigstrat2003 wrote:
| I don't think inheritance is bad at all. It is very often the
| easiest way by far to model a problem. Sure, it's not perfect,
| but I think it is wildly overhated by a vocal minority.
| neonroku wrote:
| Depends on how you use it, but in Java I prefer to use
| interfaces and no subclassing. I find classes with abstract
| methods hard to reason about, and much prefer to leverage
| Functions/lambdas where possible.
| arethuza wrote:
| Have you ever seen wildly over-architected OO code where
| everything seems to be an abstract class and it seems
| impossible to find out where stuff actually happens?
|
| Inheritance is like a lot specialised tools - it can be useful
| in some situations but I think those are rarer than supporters
| might thing. Completely refusing to use inheritance and always
| using inheritance both seem like extreme views that should be
| avoided.
| pyrale wrote:
| You haven't lived life to the fullest until you've had to
| debug an issue in a 12-layer-of-inheritance class with the
| original call ping-ponging a couple dozen times across the
| layers and overrides everywhere.
|
| I guess one could say this would be workable with proper
| tools, but the IDEs just aren't there. Move up a level in
| inheritance, ctrl-click on a call? It was overriden somewhere
| in the hierarchy, but the IDE will send you to the parent-
| class definition regardless.
| touisteur wrote:
| I have actually seen one (1) beautiful C++ codebase with
| very good use of OOP and multiple inheritance, and author
| (I was his intern) painstakingly taught me about
| inheritance and its good uses. He used Design and Evolution
| of C++ (!) as a bludgeon. It was 2006.
| ben7799 wrote:
| The person who wrote that would have made your life
| miserable if they had architected it 5 other different ways
| as well.
| touisteur wrote:
| Feels like that time my physics PhD student girlfriend asked
| me "hey you're a programmer, right" and I _was_ one until I
| discovered ROOT and suddenly I lost taste for life and
| anything related to Computers.
| esafak wrote:
| Did you become a reclusive mathematician like Perelman or
| Grothendieck??
| kaba0 wrote:
| Sure, but I have also seen C code where random structs with
| function pointers are passed to god-knows-where, and I will
| take inheritance over that any time of the day.
| MattPalmer1086 wrote:
| I have found that single inheritence can be useful but it is
| very restrictive.
|
| More than once, I have found an inheritance hierarchy that made
| sense when first created no longer models the problem well. It
| becomes hard to change it at that point.
|
| Frequently I find I really want to mix in cross cutting
| concerns across different hierarchies. This isn't really a
| surprise; most problems do not naturally decompose to only a
| single view of things.
|
| I don't mind abstract base classes containing common
| functionality, so it's useful there, but mix ins would be
| better to be honest.
| thiht wrote:
| I think that's just what you're used to. Since I switched from
| Java to Go 7 years ago, I don't think I've missed inheritance a
| single time. I haven't needed to model anything with
| inheritance once. There are definitely things I've missed from
| Java, but not inheritance.
| jerf wrote:
| A lot of people who only know inheritance and can't
| understand why a lot of us slag on it so much don't
| understand how inheritance conflates implementation and
| interface. Polymorphism based on interface is a fundamental
| tool of programming. The way inheritance also drags along an
| implementation turns out not to be, and to probably be more
| trouble than it's worth. There are other ways of bringing
| along implementation, including the basic function, and those
| work better.
|
| When inheritance is your only tool for interface, I
| absolutely agree that it looks fundamental, but the
| fundamentalness is coming from the interface side. Once you
| separate out interface from implementation, it turns out I
| have little to no use for the implementation portion.
|
| I have also been programming in Go for a long time now. I
| sketched out an inheritance system once for an exercise. It's
| doable, though it's a terrible pain in the ass to use. I've
| kept it in my back pocket in case it is ever the right
| solution to a problem even so... but it never has been. And
| that's not because of any irrational hate for the pattern
| itself. I've imported other foreign patterns sometimes; I've
| got a few sum types, even though Go isn't very good at it,
| because it was still the best solution, and I've got a couple
| of bits of code that are basically dynamically typed, again
| because it was the best solution, so I'm not against
| importing a foreign paradigm if it is the correct local
| solution. Inheritance just... isn't that useful. The other
| tools that are available are sufficient, and not just
| begrudgingly sufficient or "I'm insisting they're sufficient
| to win the argument even though I know they really aren't"...
| they really are sufficient. Functions are powerful things, as
| the functional programming community (it's right there in the
| name) well knows.
| constantcrying wrote:
| I have never really seen it work. To me it seems it can work if
| and only if you are doing serious waterfall projects, with a
| tightly bounded scope.
|
| If you have to model your data exactly once I believe it can
| work. But if you are doing any kind of agile, or even somewhat
| flexible waterfall, you will find out that at some point none
| of your data models work.
| BeetleB wrote:
| > I don't think inheritance is bad at all. It is very often the
| easiest way by far to model a problem.
|
| I think the point of the article is that in some (very popular)
| languages, inheritance comes with a lot of baggage - precisely
| because classes in those languages support 3 different use
| cases. It's not saying that your usage is bad, but that your
| language didn't provide you with the best tool(s).
|
| A lot of how people use classes can be solved via ADTs - which
| are much simpler to grok.
|
| A lot of how people use classes can be solved via
| namespaces/modules, but not all languages have good support for
| them - so they use classes.
|
| Etc.
| pvdoom wrote:
| IDK, I recently did a big presentation on the topic in my
| company's Python community and there were tons of people that
| were genuinely surprised it's not great. And people in my
| department love throwing random classes that inherit from
| everything in the universe for no reason at all... for python.
| Like not even Java or C#. And coming in as the lone new-comer its
| not going to be easy to convince everyone that has been there for
| years that the common sense aint great.
| jillesvangurp wrote:
| Kotlin has a few nice patterns here. It allows inheritance but
| only if you mark your class as open (it's closed by default).
| This prevents people inheriting from things that weren't designed
| from that. It also has extension functions, which allows you to
| add functions and properties to types without having to inherit
| from them. This is very nice for fixing things that come with
| Java libraries to be a bit more Kotlin friendly. Spring offers a
| lot of Kotlin extension functions for its Java internals out of
| the box. Another nice pattern is interface delegation where you
| can implement an interface and delegate to another object that
| implements that interface: class Foo(private
| val myMap: Map<String,String>=mapOf()): Map<String,String> by
| myMap
|
| This creates a Foo class that is a Map that delegates to the
| myMap property under the hood. So you side step a lot of the
| issues with inheritance; like exposing the internal state of
| myMap. But you can still override and extend the behavior.
| Replacing inheritance with delegation is built into the language.
|
| The net result of this is that inheritance is rarely needed/used
| in Kotlin. It's there if you really need it but usually there are
| better ways.
|
| Scala and other languages of course have similar/more powerful
| constructs. But Kotlin is a nice upgrade over Java's everything
| is non final by default (or generally defaulting to the wrong
| thing in almost any situation). Go has a nice pattern based on
| duck typing where instead of declaring interfaces, you can just
| treat something that implements all the right functions as if it
| implements a type. Rust similarly has some nice mechanisms here.
| All these are post-Java languages that de-emphasize inheritance.
| frizlab wrote:
| Same with Swift
| pdpi wrote:
| One of the ways that I think Kotlin and Rust are objectively
| better than Java and C++ is in that they have saner defaults
| than their predecessors (like open/final and mut/const).
|
| I've lost count of how many talks I've watched by Kate Gregory
| where she advocates for people tagging everything they can as
| const in their C++, but asking people to eat their veggies
| never works.
| jonnycomputer wrote:
| Sane defaults are the bomb. Defused bomb.
| v1sea wrote:
| The article has a nice history review of inheritance, but it
| would have been useful for there to be some concrete examples.
|
| The worst example of inheritance I've ever found is in java
| Minecraft's mobs[0]. It is very deep in some places and has
| numerous examples of sub classes not fully implementing their
| parent interfaces.
|
| Example 1: Donkey[1]
|
| Donkey > Chested_Horse > Abstract Horse > Animal > Ageable Mob >
| Pathfinder Mob > Mob > Living Entity > Entity
|
| Example 2: Blaze[2]
|
| Blaze has a bit for 'Is on fire', but how is this any different
| from Mob 'Is aggressive'? Blaze > Monster > Pathfinder Mob > Mob
| > Living Entity > Entity
|
| [0] https://wiki.vg/Entity_metadata#Entity
|
| [1] https://wiki.vg/Entity_metadata#Donkey
|
| [2] https://wiki.vg/Entity_metadata#Blaze
| Sakos wrote:
| Both of those look like perfect cases for composition instead
| of inheritance.
| coffeebeqn wrote:
| Anyone not using components for game objects is insane. How
| do you add a fire breathing horse without that
| feoren wrote:
| Yes, now take it further. Anyone not using components for
| _all domain modeling_ is insane. Why does everyone assume
| this wonderful practice only applies to games?
| monknomo wrote:
| why is monster vs ageable mob a good cleavage? This is odd
| NotGMan wrote:
| Black and white extremist views.
|
| Sometimes inheritance is extremely useful. Sometimes it's
| harmful. Sometimes it's not the best but the most practical
| nontheless.
|
| Language concepts are tools. These people with extreme black and
| white views live under the delusion that there can be some
| "Perfect Language which will cause the program to never
| develove/get technical debt TM".
|
| Pure delusion.
|
| Real life is an approximation where you do what is best in
| practice, not in some delusional daydream of perfection which
| breaks the moment you try and bring it to reality.
| nusl wrote:
| I quite like inheritance personally, assuming that it's done
| reasonably well and isn't a confusing mess.
| captainmuon wrote:
| I feel people don't understand what inheritance and (object
| orientation in general) is useful for, misuse it, and then it
| gets a bad reputation. It's not about making nice hierarchies of
| Cars, Fruits, and Ovals.
|
| For me the main point is (runtime) polymorphism. E.g. you have a
| function that takes a general type, and you can pass multiple
| specific types and it will do the right thing. And if you want to
| avoid huge if-else statements, you should put the code for the
| special cases in the classes, not in each function that operates
| on them.
|
| You can get this without implementation inheritance, it is also
| possible to just have something like interfaces. But I do find it
| very convenient to put common code in a base class.
| bluetomcat wrote:
| Polymorphism is doable in plain old C with lookup tables and
| function pointers. If that is the only benefit, what is the
| point of creating a language where everything is an object?
| mrkeen wrote:
| > Polymorphism is doable in plain old C with lookup tables
| and function pointers
|
| Not without casting. qsort is still: void
| qsort_r(void *base, size_t nmemb, size_t size,
| int (*compar)(const void *, const void *, void *),
| void *arg);
| vidarh wrote:
| Because syntactic sugar and abstractions matter. You can do
| everything in assembly too, yet we prefer something higher
| level.
| michaelrpeskin wrote:
| > what is the point of creating a language where everything
| is an object?
|
| I think that's the ultimate culprit in everyone hating
| inheritance. If it weren't for Java, I think we'd all have a
| healthier view of OO in general.
|
| I learned OO with C++ (pre C++-11), and now I work at a Java
| shop, but I'm luck that I get to write R&D code in whatever I
| need to, and I spend most of my time in Python.
|
| In C++ and Python, you get to pick the best tool for the job.
| If I just need a simple function, I use it. If I need run-
| time polymorphism, I can use it. If I need duck-typing I can
| do it (in Python).
|
| Without the need for strict rules (always/never do
| inheritance) I can pick what makes the best (fastest? most
| readable? most maintainable? most extensible? - It depends on
| context) code for the job.
|
| Related to TFA, I rarely use inheritance because it doesn't
| make sense (unless you shoehorn it in like everyone in the
| threat is complaining about). But in the cases where it
| really does work (there really is a "is a" relation), then it
| does make life easier and it is the right thing.
|
| Context matters, and human judgement and experience is often
| better than rules.
| Kranar wrote:
| C++ templates are a form of duck typing as well, and
| combined with type erasure give you a lot of the benefits
| of OO without the downsides.
| michaelrpeskin wrote:
| Yes, you're right. I've done that in high-performance
| code where I couldn't afford the double function call of
| a virtual function. I forgot about that.
| ano-ther wrote:
| Yes. I have a framework for an embedded system that uses
| various types of sensors. When changing a sensor, instead of
| rewriting the polling loop for every new case, I can keep
| looping through 'sensor[i]->sampleNow()' and add the specifics
| in a class inheriting from SensorClass.
| touisteur wrote:
| Interface inheritance could do the trick then? Maybe function
| pointers even.
| mrkeen wrote:
| > For me the main point is (runtime) polymorphism. E.g. you
| have a function that takes a general type, and you can pass
| multiple specific types and it will do the right thing.
|
| The runtime part is what I dislike. If I have a fruit which is
| an apple or a banana, I can't pass that to a method expecting
| an apple or banana. It can only be passed as a fruit.
|
| > And if you want to avoid huge if-else statements, you should
| put the code for the special cases in the classes, not in each
| function that operates on them.
|
| This is common OO wisdom that I strongly disagree with. For
| example, in my program I have a few types (Application,
| Abstraction, Variable, etc.), and a lot of transformations to
| perform on those types (TypeCheck, AnfConvert, ClosureConvert,
| LambdaLift, etc.).
|
| I prefer to have all the type-checking code inside the
| TypeCheck module, and all the closure-converting code inside
| the ClosureConvert module. I'd take the "huge if-else"
| statements inside TypeCheck rather than scatter typechecking
| across all my datatypes.
| rtz121 wrote:
| > If I have a fruit which is an apple or a banana, I can't
| pass that to a method expecting an apple or banana. It can
| only be passed as a fruit.
|
| You can by overriding the method on apple or banana. If your
| method is on some other object, then yes, you cannot do this
| unless your programming language supports multiple dispatch.
| zbentley wrote:
| > I prefer to have all the type-checking code inside the
| TypeCheck module
|
| There are ways to have your cake and eat it too, here, at
| least in some languages and patterns.
|
| For example, in Go you could define "CheckType" as part of
| the interface contract, but group all implementors' versions
| of that method in the same file, calling out to nearby
| private helper functions for common logic.
|
| Ruby's open classes and Rust's multiple "impl" blocks can
| also achieve similar behavior.
|
| And yeah, sure, some folks will respond with "but go isn't
| OO", but that's silly. Modelling polymorphic behavior across
| different data-bearing objects which can be addressed as
| implementations of the same abstract type quacks, very
| loudly, like a duck in this case.
| kaba0 wrote:
| > The runtime part is what I dislike. If I have a fruit which
| is an apple or a banana, I can't pass that to a method
| expecting an apple or banana. It can only be passed as a
| fruit.
|
| Heh? An apple _is_ a fruit, you can pass it to any place
| expecting the former. Like, this is Liskov's substitution's
| one half.
|
| With generics, you can be even more specific (co/in/contra-
| variance).
| cornstalks wrote:
| I think GP is talking about something like this:
| Fruit* fruit = new Apple(); ConsumeApple(fruit);
| // Doesn't work; requires Apple* fruit = new
| Banana(); ConsumeBanana(fruit); // Doesn't work;
| requires Banana* ConsumeFruit(fruit); //
| Okay, function signature is void(Fruit*)
| parpfish wrote:
| if i were writing an intro to programming book, i would
| introduce OO as a means of building encapsulation. i'd only get
| into inheritance in later chapters.
| gentleman11 wrote:
| You can have encapsulation without oop. Polymorphism is the
| real benefit of oop imho
| gorjusborg wrote:
| > Polymorphism is the real benefit of oop imho
|
| It's pretty much the _definition_ of OOP.
|
| The core feature of OOP is just bundling functions with the
| state it processes.
|
| When you bundle state and functions together, you can't
| predict what calling the function will do without knowing
| both the code and state.
|
| You can say its 'the real benefit', I guess, but that feels
| like circular reasoning. Its pretty much the definition of
| what OOP _is_ , so calling it a benefit feels weird.
|
| Unfortunately, designing systems as a collection of
| distributed state machines tends to become maintenance
| nightmare. Functions and data being separated tends to make
| code better, even when working in so-called 'OOP'
| languages.
| eddd-ddde wrote:
| Except inheritance is the premature optimisation of interfaces.
|
| Inheritance forces you to define the world in terms of strict
| tree hierarchies, which is very easy to get wrong. You may even
| do a great job today, but tomorrow such properties don't hold
| anymore.
|
| Regular composition allows the same functionality without
| making such strong assumptions on the data you are modelling.
| sameoldtune wrote:
| Arborescent vs rhizomatic approaches, to get philosophical.
| 082349872349872 wrote:
| Unfortunately Deleuze's _The Fold_ has little to say about
| either awk or catamorphisms.
| dragonwriter wrote:
| > Inheritance forces you to define the world in terms of
| strict tree hierarchies,
|
| No, it doesn't.
|
| Inheritance is the outcome of _deciding_ to model _some part_
| of the problem space with a tree hierarchy (that potentially
| intersects other such heirarchies). It doesn't force you to
| do anything.
|
| I suppose if there was a methodology which forced you, as the
| only modeling method, to exclusively use single inheritance,
| that would force you to do what you describe, but...that's
| not inheritance, that's a bunch of weird things layered on
| top of it.
| sameoldtune wrote:
| You're describing the strategy pattern, which is probably one
| of the most practical coding design patterns. Ex: each chess AI
| difficulty gets its own class, which all extend a common
| interface.
| krapht wrote:
| I still find it funny that somehow the idea of runtime
| function dispatch via function pointer is called the
| "strategy" pattern.
| sameoldtune wrote:
| Hey I didn't name it!
| VirusNewbie wrote:
| >For me the main point is (runtime) polymorphism.
|
| But you don't actually care about runtime polymorphism here.
| You care about polymorphic behavior, which can be implemented
| in a much more composable way with parametric polymorphism.
| layer8 wrote:
| You can't build a dynamic list of objects implementing the
| same interface in different ways with parametric
| polymorphism.
|
| As another example, the Unix file interface ( _open()_ ,
| _read()_ , _write()_ , _flush()_ , _close()_ ) etc. is an
| example of runtime polymorphism, where the file descriptors
| identify objects with different implementations (depending on
| whether it's a regular file, a directory, a pipe, a symbolic
| link, a device, and so on).
|
| All operating systems and many libraries tend to follow this
| pattern: You can create objects to which you receive a
| handle, and then you perform various operations on the
| objects by passing the respective handle to the respective
| operation, and under the hood different objects will map to
| different implementations of the operation.
| VirusNewbie wrote:
| >You can't build a dynamic list of objects implementing the
| same interface in different ways with parametric
| polymorphism.
|
| Yes you can. that's the whole point of type classes.
| layer8 wrote:
| Not without runtime polymorphism. Parametric polymorphism
| does not imply nor by itself implement runtime
| polymorphism. I.e. C++ templates, or generics in other
| languages, provide parametric polymorphism, but not
| runtime polymorphism.
| Kranar wrote:
| Type classes are not parametric polymorphism.
| dblohm7 wrote:
| Unfortunately a lot of developers are conditioned so heavily to
| believe that inheritance is intrinsically bad, that they
| contort their code into an unreadable mess just to re-implement
| something that could have been trivial to do with inheritance.
|
| I'm not saying that I like it everywhere; IMHO it's a tool to
| be used sparingly and not with deep hierarchies. But it's not
| reasonable to avoid it 100% of the time because we're soiling
| ourselves over the thought of possibly encountering the diamond
| problem.
| tombert wrote:
| I feel like Clojure-style multimethods accomplish this better
| than inheritance. I can simply write a dispatcher that
| dispatches the correct function based on some kind of input.
|
| This is evaluated at runtime, thus giving me the runtime
| polymorphism, but doesn't make me muck with any kind of
| taxonomy trees. I can also add my own methods that work with
| the dispatcher, without having to modify the original code. I
| don't feel like it's any less convenient than inheritance, and
| it can be a lot more flexible. That said, I suspect it performs
| worse, so pick your poison.
| BeetleB wrote:
| > I feel people don't understand ...
|
| > For me ...
|
| Sorry, but right off the bat this is just painting you as an
| example of "There are N camps, and each camp declares the other
| camp as wrong."
|
| Yes, that's what inheritance is for _you_. For _others_ it is
| something else. Why is _your_ way the one that "correctly
| understands" it?
|
| The article itself covers this - that some languages have
| lumped 3 different concepts into one that they call
| inheritance, leading to the different camps and comments like
| yours. Your camp is specifically mentioned:
|
| > Abstract data type inheritance is about substitution: this
| thing behaves in all the ways that thing does and has this
| behaviour (this is the Liskov substitution principle)
| CharlieDigital wrote:
| I feel people don't understand what inheritance and (object
| orientation in general) is useful for, misuse it, and then it
| gets a bad reputation. It's not about making nice hierarchies
| of Cars, Fruits, and Ovals.
|
| Agree 100%. It starts from the earliest programming course
| where we just teach it all wrong; way to abstract (no pun
| intended).
|
| One point to add to yours: well executed OOP allows for
| "structural" flow of control where the object hierarchy,
| inheritance, events, and overrides allow for the "structure" of
| the hierarchy to control the flow of logic.
|
| I wrote two articles on this with concrete examples and use
| cases:
|
| https://medium.com/codex/structural-control-flow-with-object...
|
| https://medium.com/@chrlschn/weve-been-teaching-object-orien...
| quaunaut wrote:
| If people get it wrong so regularly, what value is it providing
| as a concept? These concepts are supposed to help us reach
| something better, if you have to add 30 caveats to every part
| of it, all it did was hide its own complexity from you, instead
| of managing it for you.
| datascienced wrote:
| Inheritance is not so bad for native UI stuff where it makes
| sense for the class libraries.
|
| But in work code I don't see it much. If it is used it is light
| weight. Often used incorrectly for splitting code into different
| files rather than actual inheritance (giveaway is one one derived
| class!)
| nertirs wrote:
| I only tend to see inheritance in engines and libraries, where it
| makes sense to create more generic, reusable and composable code,
| since most of the functionality in these is defined by technical
| people.
|
| It makes no sense to use inheritance in the business layer,
| because a single feature request can make a lot of the carefully
| crafted abstractions obsolete.
| photonthug wrote:
| I've never seen it put quite like this, but it feels right and
| is refreshingly concrete. Trust the abstractions you can
| actually design/control for, treat all the other ones as
| suspect. One still needs the wisdom to tell the difference, but
| at least focusing on "feature request" focuses the mind. This
| is at least simple even if it is not "easy".
|
| An argument against OOP where you first need to define/explain
| differences between composition/inheritance, Liskov Sub,
| compare and contrast with traits, etc is not really that
| effective when trying to mentor junior folks. If they
| understood or were interested in such things then they probably
| wouldn't need such guidance.
| weinzierl wrote:
| My impression is the opposite, especially from when I used to
| work as a full-time Java dev. I often asked myself:
|
| Is there a place outside of GUI programming where inheritance is
| used in non-habitual and useful way? I can't think of many.
|
| More often than not you have a final class that you are supposed
| to use and the petrified hierarchy above that is of not much use.
| dan-robertson wrote:
| Perhaps having some abstract class with a real and mock
| implementation inheriting from it, which can then be injected
| into your code for testing other components is one example.
| Gibbon1 wrote:
| I actually think with generics without type erasure and duck
| typing a language doesn't need inheritance. And when
| applications are built around passing data between services OOP
| tends to be less useful. (eggs are passed to the frying pan
| service and bread goes to the toaster service)
| otikik wrote:
| Inheritance is a local maxima. When the hierarchy of classes to
| consider is small, it often "seems to fit". It allows the
| programmer to progress quickly and with low effort as a lot of
| the code sharing behavior is provided by the inheritance
| mechanism.
|
| Trouble often comes down the line. We keep adding classes, and
| soon we find that the hierarchy no longer is "shaped like a
| tree". A soccer ball is a spherical object but also a sports
| equipment and a bouncing object, and there's no way to organize
| those into branches.
|
| Our reality isn't "tree-like", except when we simplify it
| extensively.
| wwweston wrote:
| Hence traits.
| gentleman11 wrote:
| That's true of all knowledge organizational structures though.
| Even with composition, you end up with hierarchies that later
| you might turn on their heads, and also, those structures might
| end up being cyclical.
|
| Paradigm shifts happen and shake up even loosely organized into
| structures like human perception, and most people's perceptions
| aren't trees
| cryptos wrote:
| My impression is that inheritance is in many common programming
| languages the easiest way to share code. Sharing code in another
| way would require some more thoughts and sometimes code, so the
| lazy programmer takes inheritance.
|
| Traits as a general concept would be very useful in many
| programming languages, but only some (like Scala) have proper
| support for it. In principle a Java interface with default
| methods (implemented methods) is also something like a trait, but
| very limited compared to traits in Scala. I have no statistics,
| but my impression is that inheritance is much less used in Scala,
| because the language has an easy to use alternative.
|
| Another example is Go, where structs with their associated
| methods can be embedded, what is exactly composition supported by
| the language. Since Go doesn't support inheritance, programmers
| obviously needs to use this approach, so you would never see
| inheritance in Go programs.
|
| So, my conclusion is that the usage of inheritance depends on
| what the language supports and how easy it is to use.
| ehnto wrote:
| Traits and contracts or interfaces, but even still inheritance
| has it's merits. Being fast and easy is a benefit, and I find
| defensive programming in extensible systems can benefit from a
| foundation of expecting inheritance.
| josephg wrote:
| What do you mean by this sort of defensiveness? Can you give
| an example?
| Supermancho wrote:
| Traits are increasingly common.
|
| https://en.wikipedia.org/wiki/Trait_(computer_programming)
| AndrewDucker wrote:
| It can be useful in the situation where you want to override
| functionality in a class without making the functions public.
|
| If you're composing a class out of multiple chunks of
| functionality then all of the bits you want to call need to be
| public. If you're sublcassing and overriding then the bits you're
| overriding can be internal, and not part of your public API.
| hkkwritesgmail wrote:
| I find that it helps most if we make algorithms the centerpiece
| of the design and then use inheritance to facilitate coding and
| make everything succinct. The days of unwieldy inheritance
| structures coming out of big design patterns are clearly over.
| vegetablepotpie wrote:
| There's a rule-of-thumb I've found about OOP where you want to
| use composition when extending data, and you want to use
| inheritance when extending behaviors.
|
| The zoo animal metaphor that you often see used in teaching
| inheritance (lions are animals, simba is a lion all animals have
| a length, but lions also have claws etc.) is a bad perspective
| and you should never use inheritance to model a problem like that
| in the real world.
|
| The only time I've used inheritance, has been on toy problems
| that didn't need it. Not to say it doesn't have its place but
| when I encounter it, it's time consuming to peel back all the
| layers or deal with idioms that don't quite fit a problem that a
| developer has enforced through inheritance.
| gwbas1c wrote:
| > The only time I've used inheritance, has been on toy problems
| that didn't need it
|
| See https://news.ycombinator.com/item?id=39999644
|
| It's very useful when a base class provides an incomplete
| implementation, and the child class completes the
| implementation.
|
| Sometimes I use it when testing, where the child class alters
| some behavior in a way that mocking can't do correctly.
|
| In C# / Java, inheritance is used for setting up filters for
| exceptions
| jonathanstrange wrote:
| I prefer languages with full OOP, ideally with multiple dynamic
| dispatch. What I'm not fond of are languages like Java that
| overuse OOP design patterns, force you into them, or require lots
| of OOP boilerplate. If you have a class whose sole instance
| serves as a factory for other objects, you know you're on the
| wrong path. But I've never seen any coherent and sound arguments
| against OOP in general. Nobody forces you to use it, and, for
| example, it would be much easier to develop and deal with GUI
| frameworks in Go if it had classes with inheritance. As a rule of
| thumb, if for some reason your language drives you to emulate
| dynamic dispatch with explicit type switches on one or even
| multiple arguments, then it probably should have inheritance and
| dynamic method dispatch.
| mrkeen wrote:
| > But I've never seen any coherent and sound arguments against
| OOP in general.
|
| How about arguments _for_ OOP? Especially for things which can
| 't be done better elsewhere.
| poisonborz wrote:
| My take is that after something becomes sufficiently popular and
| mainstream, for a lot of people who want to be seen as
| innovative, the only way is to lash out against it. See all the
| articles on how hellishly bad agile, php or javascript is. A lot
| of criticism is valid of course, but that is irrelevant in the
| larger scheme of things - there is a reason they became what they
| are today.
| EPWN3D wrote:
| It seems like protocols solve 90% of the problems that
| inheritance does, but with 10% of the headaches. Instead of
| trying to ensure that two types can both be passed to a function
| that only knows about the parent type, just have a parameter that
| says "Whatever's passed has to conform to this, I don't care what
| it is otherwise."
| vram22 wrote:
| Code example?
| layer8 wrote:
| "Protocols" is Apple-specific (or Objective-C/Swift-specific)
| terminology. They correspond to types 1 and 2 of inheritance
| that TFA mentions.
| david2ndaccount wrote:
| Python also has protocols: https://typing.readthedocs.io/en/l
| atest/spec/protocol.html#p...
| eyelidlessness wrote:
| Protocols as a term have other prior art. Which shouldn't be
| surprising because a protocol is also descriptive of
| interface boundaries _between_ software products (such as,
| but not limited to, network protocols).
| layer8 wrote:
| The term is unintuitive to me in that usage, because to me
| a protocol implies an exchange or a sequence of steps
| between two or more parties, such as in network or
| cryptographic protocol or a diplomatic protocol.
| Interfaces, however, only specify one endpoint of an
| interchange, in an inherently asymmetric way, and they
| primarily specify operation signatures, where there are
| often few constraints on the possible sequence of
| operations.
|
| An interface can specify or be part of a protocol as a
| special case, but to me the term doesn't match the general
| case.
| shagmin wrote:
| I'm guessing the closest equivalent in languages like Java or
| C# are interfaces, right? Underutilized IMO.
| neonsunset wrote:
| Interfaces in C# are used way more often than abstract
| classes and inheritance. It is also becoming more important
| as there is more code written in Rust style with structs
| implementing interfaces for zero-cost dispatch through
| generic constraints e.g. void Handle<T>(T
| value) where T : IFeature1, IFeature2...
| seanwilson wrote:
| Nobody calls it this, but cascading styles in CSS is just like
| inheritance and I think should be avoided for all the same
| reasons. I feel it's a big part of why CSS at scale becomes
| unmaintainable. There isn't even a built-in way to compose two
| classes together if you want to avoid cascading/inheritance.
|
| It looks like composition over inheritance has caught on as the
| better default in other languages, but in the CSS world people
| still cling to the cascade as a best practice for some reason.
| ethanbond wrote:
| Obligatory: Tailwind
| seanwilson wrote:
| I tried to avoid mentioning that, but Tailwind is showcasing
| how to write CSS using composition from inheritance. But it
| can't overcome the knee-jerk reaction from many CSS
| developers that don't see the problems that comes with CSS
| cascade/inheritance. I think this is mostly because leaning
| on cascading rules is seen as a best practice and most aren't
| going to question best practices much.
| Fauntleroy wrote:
| Tailwind is not a panacea--it has its own problems which
| withhold adoption, "kneejerk reactions" notwithstanding.
| danielvaughn wrote:
| I'll never understand why a lot of CSS devs are opposed to
| it. I can understand the initial gut reaction, but I've
| been writing CSS for 14 years and I _love_ Tailwind.
|
| The cascade is a wonderful idea that has unfortunately not
| played out well in practice. Anyone who thinks it's just a
| skill issue is deluding themselves. In all my years, I've
| _never_ seen CSS that (a) leverages the cascade, and (b)
| scales elegantly. It all breaks down past a certain point.
|
| Tailwind has it's flaws, but it doesn't have the scaling
| problem.
| explaininjs wrote:
| Mainly because nobody is a "CSS Dev". If someone's entire
| job was developing CSS, you might expect that they would
| be willing to learn one new slightly different way of
| doing it. But in reality the pushback comes from full
| stack developers who already have a million other things
| to worry about, such that relearning all the basic CSS
| they already know, to achieve benefits that are rather
| minuscule in the grand scheme of things, is a low
| priority. I personally am working on a Tailwind product
| that is fairly small (~10k lines, perhaps), and the
| Tailwind isn't an annoyance per-say, but it's definitely
| less ergonomically friendly than the well-engineered
| enterprise application I was working on before, which was
| 100x the size and used exclusively pure CSS.
|
| Personally, my dream would be if inline styles supported
| the full CSS gamut of pseudo selectors and the child
| element selector. Then you'd have the admitted benefits
| of not needing to synchronize the two files, along with
| the benefit of not needing to relearn all of CSS.
|
| Edit: it's funny, in a way, that all these developers
| complaining about how CSS "doesn't scale" are likely
| writing their Tailwind in an large scale application
| styled entirely via CSS. https://github.com/search?q=repo
| %3Amicrosoft%2Fvscode++langu...
| danielvaughn wrote:
| On the contrary, Tailwind's ergonomics are what attract
| me to it. With the autocomplete features via the VSCode
| intellisense plugin, I'm able to create UIs at a pretty
| extraordinary pace.
|
| As a trivial example, let's imagine we need to apply a
| border radius to an element. Without Tailwind, it looks
| like this: 1. I find the element I need
| to style 2. I look at which class it's using
| 3. I navigate to my CSS file 4. I scroll down until
| I find the selector 5. I type "border-r", then tab
| on the auto-complete to fill in "border-radius" 6.
| I type the colon character, then space 7. I think
| about what unit is appropriate - rems, ems, pixels,
| percentage 8. I think about what value is needed
| for the design 9. I also look around to see if this
| style is used elsewhere 10. If the style is used
| elsewhere, I think about whether I need to refactor
| 11. I type in the desired value 12. I type a
| semicolon to mark the end of the statement 13. I
| type cmd+s to save the css file
|
| Here's the same example, with Tailwind:
| 1. I find the element I need to style 2. I type
| `round` and wait for the autocomplete to present my
| options 3. I use my arrow key to select which one I
| want 4. I type enter to add the desired tailwind
| class 5. I type cmd+s to save the html file
|
| The Tailwind interaction path takes less than half of the
| concrete steps to complete. But it's even more dramatic
| than that, because several of the steps taken in the
| first example require enough thought that it breaks your
| workflow and takes you out of your flow state. Then you
| have to get back into that flow state to keep working.
| But this keeps happening, so you're constantly stopping
| and restarting. With Tailwind, I tend to find myself
| staying in that flow state, because as I demonstrated
| above, there's very little getting in my way.
| seanwilson wrote:
| Fully agree with this. The regular arguments against
| Tailwind like "it's just inline styles", "learn CSS
| properly", "it looks ugly" and "normal CSS is easy" say
| nothing about how fast the Tailwind approach lets you
| make edits and stay focused in comparison.
|
| Normal CSS is usually worse than this too e.g. you hit
| save, and your edit doesn't change anything, so you have
| to use the web inspector to hunt down which class is
| overriding your style then weigh up options for how
| you're going to refactor while jumping between multiple
| files. It's exhausting when you're trying to focus on
| styling.
| danielvaughn wrote:
| I also assumed that a class already existed for it.
| Because otherwise you have to think about whether you use
| a class or an id or an element selector, you have to
| think about what the class name is going to be, which
| file it should go into, etc etc. What I presented was
| _absolute best case scenario_ lol.
| explaininjs wrote:
| Only if you're refusing to consider inline styles. Which
| is an odd decision to make if we're comparing to
| Tailwind.
|
| _That said_ , I agree that this doesn't work for pseudo
| selectors, very unfortunately, and I wish it would.
| explaininjs wrote:
| I actually just downloaded the VS Code extension earlier
| today as a result of this discussion, perhaps that will
| change my opinion. Because for me the two flows would be:
| 1. I find the element I need to style 2. I
| add/edit the inline style={{}} attribute I have for it by
| typing `sty<TAB>{borrad<TAB>` 3. I add a value
|
| VS: 1. I find the element I need to
| style 2. I add/edit the className attribute
| 3. I pull up the tailwind documentation to find how to
| type the CSS I already have memorized in their DSL (I
| know some Tailwind by heart, but wayyyyy more CSS)
| 4. I wait for it to load 5. I scroll down to find
| the version of the class name that I need 6. I go
| back to the editor and add the class.
|
| Also a very common flow for me is to edit the CSS
| directly in the browser, it's a much faster devloop than
| the fastest live reload server. In that case it's far
| easier to just copy from the `changes` view into the CSS
| than go through and remap everything from CSS into
| Tailwind.
| danielvaughn wrote:
| That's fair - styling directly in the browser does indeed
| cut down several of the steps I mentioned, or at least it
| condenses it to one step at the end. I do think that
| installing the extension changes the experience entirely,
| because then you don't need to reference the docs.
| gentleman11 wrote:
| Explain?
| danShumway wrote:
| I'm inclined to semi-agree with this, although I'm not sure I'd
| be _quite_ as adamant about it myself. But I do generally think
| that CSS is taught in a way that encourages some bad practice
| around cascade /inheritance. I will point out though that (at
| least in my experience), BEM solved the majority of these
| problems for me even without a preprocessor. The language is
| definitely oriented towards inheritance/cascade, but I think
| there are ways to avoid it.
|
| There's some movement towards ::part as a proposal to grant
| some mixin behaviors (https://developer.mozilla.org/en-
| US/docs/Web/CSS/::part) but I've never messed with it, and it's
| applicable only to shadow DOM. But mixins haven't been a huge
| issue for me in even enterprise-scale styling that I've done.
|
| Opinion me, everyone has their own opinions on this, use
| whatever CSS style works for you. This is not me saying that
| BEM is the best for everyone, just giving a perspective that as
| someone who tends to stick to vanilla CSS and who generally
| kind of hates working with technologies like Tailwind or CSS-
| in-JS, BEM-style vanilla CSS made CSS pretty pleasant for me to
| work with; I have a lot more appreciation for the language now
| than I used to.
|
| So if you're annoyed by CSS but also get annoyed by pre-
| processors or think that Tailwind is just inline CSS under a
| different name[0], you still don't _need_ to be bound to the
| cascade -- potentially look into BEM. No technology or
| compilation or dependencies, it 's literally just a naming
| convention and style guide.
|
| ----
|
| [0]: yes, I have used it extensively, please don't comment that
| if I used it more something would magically click, I already
| understand the points in its favor that you're going to comment
| and I've already heard the style/framework suggestions you're
| going to offer. It's fine if you like Tailwind, it's great if
| it helps you write CSS, you don't need to convince me.
| seanwilson wrote:
| > But I do generally think that CSS is taught in a way that
| encourages some bad practice around cascade/inheritance. I
| will point out though that (at least in my experience), BEM
| solved the majority of these problems for me even without a
| preprocessor.
|
| I think cascading is just a bad default, and I think
| methodologies like BEM agrees with this by teaching you ways
| to write CSS in ways that stops cascading from getting in the
| way.
|
| Cascading styles are fine for styling how basic document
| content is shown (e.g. h2, p, a, li etc. tags) but outside of
| this, you generally don't want the styles of parent elements
| leaking into the styles of child elements.
| Cascading/inheritance styles is a useful tool to have, but
| not as the default.
|
| I'm not saying Tailwind is perfect, but it's closer to
| "prefer composition over inheritance", where you can sprinkle
| in some cascading/inheritance where it makes sense.
| spankalee wrote:
| But BEM doesn't interact with the cascade. If you have two
| BEM selectors (or a BEM selector and non-BEM selector) that
| match an element and set the same property, the cascade
| algorithm still applies to determine what to set the
| property to.
| danShumway wrote:
| Sure, but the idea with BEM is that you generally don't
| have situations where the result of that algorithm is
| confusing or unexpected. Or at least that's been my
| experience, even on large codebases. I generally don't
| run into situations where styles overload each other in
| weird ways when I'm using BEM (others' experiences might
| vary).
|
| You could throw the same criticism at Tailwind --
| Tailwind can still expose you to cascade issues, not all
| Tailwind classes are single-level selectors under the
| hood and not all Tailwind classes only target one
| property. At the end of the day this is all compiling
| down to raw CSS, so in neither situation have you
| actually eliminated the cascade. But with both BEM and
| Tailwind you are much less likely to see those
| situations, and when they do arise they are less likely
| to introduce long-term maintenance problems and are more
| likely to be easy to address/encapsulate. If you run into
| cascade bugs with Tailwind, it's probably something you
| fix in like one file, instead of needing to search
| through five.
|
| BEM doesn't technically interact with _anything_ , it's
| just a style of writing CSS. There's literally no
| technology behind it, it is just a naming convention. But
| in practice, using a naming convention mitigates or
| eliminates a large number of cascade issues.
| danShumway wrote:
| > I think cascading is just a bad default
|
| I'm again semi-inclined to agree, I just don't think I'd
| say it as forcefully; more that cascading styles tends to
| have a lot of downsides that people aren't familiar with
| and aren't taught.
|
| My point isn't to badmouth Tailwind here; but debates about
| this sometimes boil down to "CSS purists" vs "Tailwind
| advocates" and my point is more -- nah, you don't have to
| like Tailwind to avoid the cascade. You can be a CSS purist
| and still avoid basic element selectors, your choice does
| not have to be either "do semantic styling targeting only
| semantic elements" or "jump on Tailwind and stick a bunch
| of styles inline."
|
| I'm more sticking up for -- look, if you're someone who
| uses Tailwind, great, I don't have to tell you anything.
| You are already using a framework that (regardless of any
| other flaws it may or may not have) discourages you from
| using the cascade. But if you're someone who's in the
| position where you dislike CSS-in-JS or don't like using
| Tailwind, also great! I'm in that position too, I don't
| like Tailwind. But I still avoid cascade and basic element
| selectors and there are ways to basically eliminate most
| cascading styles from your codebase and eliminate most
| cascade-caused bugs even if you aren't going to use a pre-
| processor at all, and it's good to at least consider
| removing those cascading styles.
|
| My only critique of Tailwind I would bring here is that
| sometimes I get the feeling that Tailwind advocates think
| that Tailwind invented this idea of component-based CSS,
| and it really didn't. But that's neither here nor there,
| and if someone is using Tailwind and it works for them,
| great. Life is way too short for me to argue with someone
| using a technology that they enjoy. Honestly, same with the
| cascade -- I think it can lead to long-term maintenance
| problems, but if you like it, fine.
|
| However, if you're using CSS and _hate it_ , and you also
| don't want to use Tailwind, then give BEM a try.
| vram22 wrote:
| >It looks like composition over inheritance has caught on as
| the better default in other languages
|
| "Prefer composition over inheritance" was mentioned in an early
| page of the GoF book (the Design Patterns book) - in 1994.
|
| https://en.m.wikipedia.org/wiki/Design_Patterns
| no_wizard wrote:
| >It looks like composition over inheritance has caught on as
| the better default in other languages, but in the CSS world
| people still cling to the cascade as a best practice for some
| reason
|
| I find this falls into generally two camps, those who want to
| fight the browser and those who don't.
|
| Those that tend to hate the cascade tend to fight the browser a
| lot, whether they realize it or not. Generally speaking, they
| want things to work a certain way (IE everything in isolation)
| and prefer to think of styles isolated bits.
|
| The second camp tends to not fight the browser and embrace the
| browsers methodology. They embrace the cascade because it's
| easier than fighting it, but it requires a more sophisticated
| approach and seeing styles in a wholistic manner, not isolated
| purely into components (though may be organized in a way that
| co-located them with relevant components)
|
| Both work, ultimately, and modern tooling and approaches allow
| both to exist, but I will say, the second group often has a
| better grasp on keeping project maintainability over time in my
| experience.
| coldtea wrote:
| > _Those that tend to hate the cascade tend to fight the
| browser a lot, whether they realize it or not. Generally
| speaking, they want things to work a certain way (IE
| everything in isolation) and prefer to think of styles
| isolated bits._
|
| So, wouldn't that be the browser fighting them, then?
|
| They want something specific, and the broswer forces a
| paradigm upon them that they don't want.
|
| They are the humans and the browser (well, the styling
| language of the browser) is the tool that should accomondate
| them, not the other way around.
| no_wizard wrote:
| This is a sane argument. I have no objection to it. Both
| things are true, in a sense.
|
| It really depends on how you view the browser, IE should
| browsers be more adaptive to certain paradigms or should it
| set a reasonable paradigm and enforce it? I don't know that
| there is a 100% right answer in this case, though they have
| moved to create better hooks for some forms of isolation
| (e.g. layers, scoping) but they fundamentally haven't
| walked away from the cascade aspect.
|
| It's converging the two paradigms, for sure, but as I said,
| I don't think either is wholly incorrect or correct.
|
| Now if you wanted my opinion on the whole thing, I think
| the cascade is a fundamental element to be leveraged not
| avoided, but that's me.
| tonyarkles wrote:
| If someone picks up a hammer and struggles to pound screws
| in with them, it's not the hammer that's defective.
|
| I don't think CSS is the perfect tool for all browser-based
| styling but it's the tool that's there and it'll probably
| work a lot better if you use it the way it's intended to be
| used. If you want a screwdriver instead of a hammer... you
| have options (don't target a browser, propose an
| alternative to CSS, use something that compiles down to
| CSS).
| coldtea wrote:
| > _If someone picks up a hammer and struggles to pound
| screws in with them, it's not the hammer that's
| defective._
|
| If someone wants to hammer nails and they're given a
| blender, then the blender might not be defective, but it
| surely is not the right tool for the job, and it's
| imposed upon them.
|
| Few people ever loved CSS. The majority always either
| hated it or learned to tolerate it. Most who do CSS today
| use a few different paradigms on top to make them
| tolerable like BEM, or use different transpilers to get a
| better language, or directly control styling from code,
| with CSS-in-JS libraries or like React does it.
|
| > _I don't think CSS is the perfect tool for all browser-
| based styling but it's the tool that's there_
|
| Sure, I never denied its existance. Just its design.
| cogman10 wrote:
| > the second group often has a better grasp on keeping
| project maintainability over time in my experience.
|
| That's probably in the eye of the beholder and a necessity.
| If you are all in on the cascades then you have to limit the
| depth of your page structure, otherwise it becomes impossible
| to predict how things will ultimately render or what the
| impact of a change at layer 3 will have on the rest of the
| page.
|
| Technically speaking, isolation is perfectly possible with
| webcomponents.
| no_wizard wrote:
| Yes, there are more tools for isolation now, but you can't
| wholly opt out of the cascade, even if it's only local
| relevant, and I think that's a good thing IMO
| quaunaut wrote:
| I'd ask what you mean by "fighting the browser"- as
| generally, the number one way to ruin the performance of your
| CSS is to introduce depth to it. In general, keeping
| everything isolated regularly leads to better rendering
| performance.
| no_wizard wrote:
| Avoiding the cascade at all costs, for example. It can
| introduce a lot of unintended consequences.
|
| Another anti pattern I have seen is the over use of media
| queries to force the browser to do certain things rather
| than embracing relative sizing constraints via intrinsic
| design and letting the flexbox and grid algorithms do most
| of the heavy lifting.
|
| Here though I want to point out isolation is relative, as
| is the cascade. I think it's important to leverage the
| cascade wherever you can but that doesn't mean you are
| leveraging it from top to bottom per say, but it does mean
| thinking more wholistic about the context of styling
| layer8 wrote:
| It's not only inheritance, it's also lack of encapsulation and
| of separation of concerns, which leads to attack vectors like:
| https://news.ycombinator.com/item?id=39928558
| ramijames wrote:
| I really agree with this.
|
| There was a company that I worked at 10ish years ago with a
| SaaS built around Drupal. They had one monolithic css file that
| was 25000 lines. It was a nightmare. When I took over the
| front-end rebuild, went through it line by tedious line and
| broke it out into discrete components.
|
| I think about that from time to time when building logic or
| other functionality. I'd much rather have a self-contained bit
| of code than a big thing that is so intertwined I'm terrified
| to touch anything.
| cjpearson wrote:
| CSS does actually call it inheritance[0], but it's commonly
| mixed up with the cascade. Inheritance applies to certain
| properties, so that when they are not specified on an element,
| an element inherits the value of the parent.
|
| The cascade[1] determines how rules from multiple sources are
| merged.
|
| [0]: https://developer.mozilla.org/en-
| US/docs/Web/CSS/Inheritance [1]:
| https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade
| spankalee wrote:
| The cascade is not much like inheritance at all. If anything
| it's more like composition because the styles can come from
| multiple sources, eg user styles vs author styles, multiple
| layers.
|
| The cascade is so much not like inheritance that I wonder if
| you meant either the concept of selectors in general, or
| inherited properties?
| seanwilson wrote:
| Yeah, the other poster pointed out
| https://developer.mozilla.org/en-US/docs/Web/CSS/Inheritance
| and https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade.
|
| I meant the concept that when you e.g. apply the style
| "color: blue;" to an element, all child elements get the same
| style unless you override it. I'm not saying it's identical
| to inheritance, but it's similar in that changes and
| additions at the top-level ripple down to lower levels, which
| I find causes most of the problems when writing maintainable
| code.
| danShumway wrote:
| > I meant the concept that when you e.g. apply the style
| "color: blue;" to an element, all child elements get the
| same style unless you override it.
|
| This is the first thing you've said so far that I would
| push back against. Neither BEM nor Tailwind removes this
| behavior that I'm aware of. I thought when talking about
| the cascade you meant generic styles on elements like "p",
| "ul", etc... getting applied across separate components, or
| specificity of child selectors, or something similar.
|
| If you really dislike styles being applied to children in
| the DOM that don't override those styles, I don't think
| there is a way around that other than web-based components
| and shadow DOM with isolated styles. Or I guess use a bunch
| of style resets beforehand I guess? Neither Tailwind nor
| BEM gets rid of child inheritance of applied styles; you
| can use @layer I guess, but that doesn't get rid of that
| behavior either, it just allows you a bit more control over
| style order.
|
| If you're using Tailwind and you write:
| <div class="text-red-400"> <p>Some text</p>
| </div>
|
| that text will be red. If you're using BEM and you write:
| <div class="Container"> <p>Some text</p>
| </div> .Container { color: red; }
|
| same deal.
| seanwilson wrote:
| > I thought with inheritance you meant generic styles on
| elements like "p", "ul", etc... getting applied across
| separate components
|
| Yes, so I mean if you add "color: blue" to "p", it's now
| going to start interacting with any element that's a
| child of "p" (which will probably be on all pages on your
| website so hard to predict and check what will happen).
|
| BEM and Tailwind don't get rid of the behaviour of the
| color being applied to child elements, but it at least
| forces you to isolates these kinds of style changes to
| the component level (vs sitewide) which is what improves
| maintainability.
| danShumway wrote:
| Okay, we are on the same page then -- sorry. Yep, I
| generally agree with this.
| cjpearson wrote:
| I can see how it causes problems, but I don't really see
| any alternatives for certain properties. Would you want to
| set the color property on every element that contains text?
| Or do something with the universal selector?
| dboreham wrote:
| It's not bad. People like to feel smug by saying OO is bad and
| hence inheritance, meanwhile using some construct in their FP
| that is really the same thing.
| chowells wrote:
| Can you explain how any construct in FP conflates subtyping and
| code sharing the way OO inheritance does?
| tossandthrow wrote:
| you seem to be misunderstanding different terms. You can do OO
| without inhertitence.
|
| inheritance is the worst as you grip with you types.
|
| just use strategy patterns instead.
| FrustratedMonky wrote:
| Oh, darling, strap in because the Hacker News catwalk is serving
| us a spicy mix of opinions on inheritance in programming!
|
| First up, we've got the crowd who treats CSS like it's the ugly
| stepchild of inheritance, preaching the gospel of "composition
| over inheritance" as if it's the latest fashion trend.
|
| Then there's the old guard, clutching their pearls and insisting
| that inheritance isn't the problem--it's just misunderstood, like
| a moody teenager.
|
| Cue the functional programming aficionados, sashaying in with
| their "functions over classes" mantra, ready to throw shade at
| OOP's entire existence.
|
| And let's not forget the star of the show, the claim that
| inheritance is the VIP at the GUI and game development party,
| although some party poopers argue that the cool kids moved on to
| ECS systems ages ago.
|
| Meanwhile, the language innovators are flaunting their Kotlin and
| Swift ensembles, dripping in modern features that promise to make
| inheritance feel so last season.
|
| In the midst of this fashion war, there's a heated debate on
| whether we should be dressing our newbie programmers in OOP gowns
| or functional frocks from day one. And, honey, let's not even get
| started on the industry's trend chasers, who once hailed OOP as
| the next big thing, only to ghost it faster than you can say
| "blockchain."
|
| In the end, it's clear that in the world of programming,
| inheritance is either a timeless classic or a faux pas waiting to
| happen, depending on who you ask.
| keiferkif wrote:
| It's funny how easy it is to spot a ChatGPT reply
| FrustratedMonky wrote:
| Do you think it is because it is too verbose? Long winded? A
| human wouldn't bother to write that much?
| constantcrying wrote:
| I think it is pretty clear that the reason inheritance is so
| prevalent is because it is a superficially good sounding idea,
| which again and again is being put in front of people.
|
| I am sure that many, many people have had the experience of a
| professor tell them a nice sounding story about purely
| hierarchical data and went on thinking that this is the way data
| should be modeled.
|
| Thankfully I believe that nowadays many people have understood
| what works and what doesn't work in OOP and we can actually try
| to get away from that model of data.
| bartvk wrote:
| I don't know about other colleges, but when I teach my students
| inheritance, I tell them right away that it's a pretty bad
| idea. And that they will learn better ways in the next
| semester.
| constantcrying wrote:
| I can't speak for what has happened since I left university,
| but when I was a student there you got to here about cars,
| who are also vehicles, dogs, who are also animals and similar
| stuff. Which has "limited" applicability of how inheritance
| works in real software systems.
| CyberDildonics wrote:
| This is exactly what I've seen and I think a lot came from java
| and C++ before it. Ironically C++ is a fantastic language now
| to use now while avoiding inheritance.
|
| In the late 90s, 2000s and maybe even now, people see
| inheritance and think that it makes sense. They make the
| vehicle, the car, the sedan and then get stuck, when all they
| really needed was an x and y point and a velocity.
| ben7799 wrote:
| I love the way people take their narrow range of experience and
| over generalize it to everything. Bonus points for having a blog
| or maybe a patreon to tell everyone how it really is.
|
| We have a million flavors of the month but in the end it doesn't
| really matter. Pick a flavor and there will be teams that are
| successful and teams that are not. No silver bullet will make the
| non-successful teams magically turn into successful ones.
|
| "I/We failed with <insert language feature>" never generalizes to
| "<insert language feature>" is bad.
|
| Then the community constantly sits there and acts like a bunch of
| geniuses because some programming pattern has been discovered..
| except it's 50 years old.
| vram22 wrote:
| Ha ha, yes, happens all the time.
|
| In this case, published in a book 30 years ago, at least:
|
| https://news.ycombinator.com/item?id=40005520
| reactordev wrote:
| Ahhh good 'ol XP/Gang of 4. Never ceases to be referenced.
| (for good reason)
| ben7799 wrote:
| Pretty sure it was in Effective Java and Design Patterns in
| Java 20+ years ago as well since Java is the language that
| gets picked on so frequently for this stuff.
| fuzztester wrote:
| He he. Java and patterns, Java and frameworks (mumble
| Spring mumble IoC mumble dependency injection), Java and
| factories and builders, mumble <buzzword> ...
| ruszki wrote:
| > "I/We failed with <insert language feature>" never
| generalizes to "<insert language feature>" is bad.
|
| I thought the same for decades, then I met Groovy and Grails.
| Of course, it's not bad in an absolute sense (IMHO that doesn't
| make any sense), but when some problem can be found only at
| runtime, when any proper compiler would catch that compile
| time, it's hard to argue that it's a good direction. Especially
| when TypeScript made it quite obvious what's possible only with
| simple type checking.
| maleldil wrote:
| > TypeScript made it quite obvious what's possible only with
| simple type checking.
|
| Typescript's type system is anything but simple.
| d_burfoot wrote:
| It seems like there's a huge industry of people critiquing Java
| and OOP but misdiagnosing the problems as technical rather than
| sociological.
|
| The immense pain and suffering that is related to Java and its
| ecosystem is because of the terrible organizational conditions
| that tend to co-occur with usage of Java. Big slow companies,
| long boring meetings, arguments about design patterns,
| "architects" who haven't written code in ten years, etc etc. Java
| correlates with, but does not _cause_ , those conditions.
|
| Another way of saying it: if Java was good enough for Nutch to
| use to write Minecraft, it's good enough for the vast majority of
| business purposes.
| ldjkfkdsjnv wrote:
| Java complexity is also an artifact of Java being used in large
| complex systems. People are really complaining about how hard
| programming is. There are many types of large scale systems
| where I would only code it using the JVM, everything else is a
| nightmare. Big tech companies largely feel the same
| ben7799 wrote:
| So many of these articles are written by people with no CS
| background who then had a couple stints short stints
| programming and then overnight pivoted to being expert
| consultants. They don't really have experience building or
| maintaining large complex systems. I am really curious who
| hires them. Most of them will have significantly less
| experience than the senior members of a successful team, and
| often less relevant education.
| throwaway5959 wrote:
| No one is more confident in their abilities than a boot
| camp grad.
| tootie wrote:
| There is an all-time great quote from Bjarne Stroustroup when
| asked about how many people hated C++
|
| > There are two types of programming languages: The ones
| people complain about and the ones nobody uses.
| josephg wrote:
| > There are many types of large scale systems where I would
| only code it using the JVM, everything else is a nightmare.
|
| I've seen more than one lifetime's worth of nightmare code
| written in Java. I agree with the GP commenter - I don't
| think the problem is Java (the language) so much as the
| culture surrounding it. The Java programmers I've worked with
| (all smart people) seem addicted to solving all problems my
| writing more Java. More interfaces (most of which just have a
| single implementor). More classes. More files. More
| abstractions. You pay a massive tax for that any time you
| edit your software - since inevitably you're going to end up
| unpicking hundreds of lines of code that could have been two
| if statements.
|
| I've never seen this abstraction vomit disease be quite this
| bad in software written in other languages. You can find a
| bit of it in C#, C++, go and Python. But not as bad as Java.
| In typescript and rust, most of the code I work with focuses
| a lot more on directly solving the actual problem at hand.
|
| I don't think the problem is the language itself. Java is a
| fine language. But for some reason, it's become a magnet for
| a certain kind of developer that I frankly never want to work
| with. Developers who would never use 5 lines of code when 100
| would do. Developers who abstract first and understand the
| problem they're solving later. It's disgusting.
| tootie wrote:
| I have often observed that Java is great because it solves
| organizational problems. Strong and static types, interfaces
| and most of all, javadoc, were miraculous for conveying intent
| to integrators and maintainers. Even seemingly insignificant
| things like a naming convention for packages was extremely
| useful. A lot of the features trumpeted in Java when it was
| new, like abstract classes or checked exceptions, were adopted
| religiously at first but fell out of favor a long time ago.
| Interfaces and interface inheritance is extremely useful.
| HumblyTossed wrote:
| Yeah, there's nothing wrong with Java the language. Java the
| community is what sucks. You had a few bloggers get popular and
| then a bunch of devs trying to make names for themselves would
| ape what they said, compound a few years and you have the mess
| people like to complain about.
|
| Happens with a lot of language communities. C#, good grief, not
| much better than Java. Go(lang) community's favorite two words
| are "idiomatic Go". You can't read any post without seeing
| those two words. Go has like what, seven keywords. Let it go,
| man.
| gorjusborg wrote:
| > there's a huge industry of people critiquing Java and OOP but
| misdiagnosing the problems as technical rather than
| sociological.
|
| I totally agree. I have worked on applications that would have
| collapsed under their own weight if it wasn't for Java. The
| strong type system, stable runtime and standard library APIs,
| as well as great tooling allows code to live far longer than it
| would in some other language ecosystems.
|
| However, don't make the mistake of dismissing all of the
| criticism. The truth is often nuanced, and OOP/Java does have
| some serious downsides and footguns. There is room for both
| criticism and praise.
|
| Ultimately, great systems aren't created by throwing a language
| and problems at a collection of random people. Great systems
| happen when good decisions are consistently made over time, and
| those are context dependent.
| not2b wrote:
| I only use inheritance for what the author calls ontological
| inheritance. For example, I find it useful to represent
| expressions and statements. I prefer composition if I'm trying to
| reuse data structures.
| feoren wrote:
| But ontological inheritance is _the worst kind_. Because your
| ontology is wrong. Similar things are kinda different.
| Different things are kinda similar. Your ontology is based on
| the linguistic remnants of taxonomic garbage from hundreds of
| years ago. There 's no shelf.
| arcticbull wrote:
| Boring answer: Same reason we do a lot of stupid things, haha.
|
| "If C is so unsafe and C++ is so unwieldy why does anyone use
| them?"
|
| Because that's what they learned, or because that's how the
| codebase is structured when you get there. When all you have is a
| bike you're going to ride that shit everywhere.
|
| You rarely if ever get the chance to start from scratch at work
| (where most code is written) and your job is to follow the pre-
| set pattern most of the time unless it becomes completely
| untenable. Sure, inheritance is worse in pretty much every way,
| but if everyone you're hiring does it that way, your codebase is
| built that way, etc, you're going to make the best of it because
| that's your job.
| BatFastard wrote:
| Main reason I use c++ is because it has the most mature
| libraries.
|
| I gave Rust a try, and lots of nice things about it. But many
| of its libraries are not very mature.
| josephg wrote:
| What libraries do you miss from C++ that you can't find in
| rust? ML is the big piece for me - there's no mature
| equivalent to CUDA or PyTorch.
| BatFastard wrote:
| Any kind of usable GUI library. Plus I am doing 3d
| graphics, and rust is not there yet.
| bluGill wrote:
| All the company specific libraries we have debugged of the
| past 30 years.
| layer8 wrote:
| It's only implementation inheritance that is questionable, and
| the reason is that it leads to mutual dependencies on
| implementation details between superclass and subclass. Reasoning
| about correctness becomes difficult, and the superclass is very
| constrained in what implementation details it can change without
| potentially breaking some subclass.
|
| Bertrand Meyer's open-closed principle can be read that way: The
| superclass is closed to modifications. But that goes against the
| principle of decoupling interface from implementation: The
| superclass interface is now stuck with the existing superclass
| implementation.
| nforgerit wrote:
| When I learned programming as a teenager, I was using one of the
| C++ classics which, of course, taught OOP and Inheritance as a
| magic silver bullet.
|
| When I then finished the book and its examples, I wanted to do my
| own exercises and went through about 1-2 very painful years
| trying to model my things using Inheritance and totally blamed
| myself for being too stupid to do software development.
|
| Only then I started searching and finding essays and blog posts
| of people criticizing OOP and esp. Inheritance for exactly the
| things I was struggling with. This felt like a relief!
|
| My question: Why is Inheritance still taught as a silver bullet?
| I'm seeing university courses using Inheritance both for Domain
| and infrastructural code reuse at the same time. Esp. in Germany
| I see a lot of stupid 90s alike "programming" courses and, no
| shit, according code bases. It's as if they were living in a
| gigantic bubble.
| jonnycomputer wrote:
| My situation is different than a lot of others--I work mostly
| with code-bases I 100% control--but I just don't use inheritance
| all that often, and if I do, it's never deeper than one or two
| levels. Now, I do most of my programming in Python and in
| JavaScript, and I do like using python's ABCs to specify
| interfaces for interfaces that I expect to be implemented, but
| that's mostly the end of it.
| ffsm8 wrote:
| I remember the original reason for composition over inheritance
| to be very specific to Java: you can only inherit one class,
| but compose your class from multiple interfaces, each of which
| can have default implemented methods.
|
| In python you've got polymorphism, mixins etc, so the reason
| for this suggestion doesn't really apply.
|
| Django heavily uses inheritance for the controllers for example
| - and it works very well.
| turnsout wrote:
| I stopped using inheritance when Swift protocols and TypeScript
| interfaces came along. Although it sometimes means you need to do
| nasty things with generics, it beats the fragility of
| inheritance. With ObjC, I eventually came to hate updating
| superclasses, because I knew it would lead to unexpected behavior
| in subclasses.
| tootie wrote:
| Inheritance on interfaces is fantastic. Inheritance on
| implementations is fraught with peril. Java developers figured
| this out like 10 years ago.
| josephg wrote:
| I had the same experience. I'm a few years into using rust now
| - which doesn't implement inheritance at all and I don't miss
| it. In typescript I barely use classes at all. I prefer an
| interface paired with a constructor function.
|
| I think it's almost always better to first think of data as a
| struct, with associated code that acts on that struct. As the
| old saying goes, show me your data structures and not your
| code, and you don't need to show me your code.
| zozbot234 wrote:
| Rust implements interface inheritance out of the box via
| traits, but you can also replicate something very much like
| implementation inheritance via phantom types/the typestate
| pattern. It just finds very little use in general, because
| it's readily apparent just how clunky that whole arrangement
| is.
| pianoben wrote:
| Despite misgivings about the language as a whole, I think that
| Go does interfaces extremely well, and made the right choice to
| discard "implementation inheritance".
|
| That said, in my time with obj-c I never really encountered the
| problem of fragile base-classes - protocols and categories gave
| us just about everything we needed. (the foundation classes use
| implementation inheritance extensively, but we don't typically
| modify those too often :) )
| slaymaker1907 wrote:
| For a really neat implementation of OOP and inheritance, I would
| recommend checking out Racket's class system (not to be confused
| with generics). It mostly solves the multiple inheritance problem
| by mixins and also has a great interface system.
| shawn_w wrote:
| I find Racket's OO stuff to be awkward and cumbersome to use,
| and rarely a good option. One of these days I'm going to port a
| CLOS-inspired Scheme OO system like Guile's GOOPS to Racket...
| caseysoftware wrote:
| The key is "prefer composition to inheritance" and dates back to
| Gang of Four.
|
| The word "prefer" is critical to understand. It just means
| "usually choose A over B" _not_ "B is never the right answer."
|
| Unfortunately, since we - as an industry - like hard and fast
| rules, we move towards that second explanation and act like
| inheritance never makes sense. Like any tool, there's a time and
| place where it is the best tool, other times where it's a
| reasonable tool, and other times it's a terrible tool.
|
| Therefore "everyone uses it" because either a) it's a reasonable
| approach or b) the developer doesn't know of or can't use a
| better one.
|
| We should work on fixing b) instead of denying a) exists.
| cogman10 wrote:
| I agree with this sentiment but I also must say that the time's
| I've needed inheritance have been few and far between. I have
| seen really good examples where it works really well (UX is
| pretty common, but I've also seen really clean cases like data
| structures with complex interfaces and a simple abstract
| class).
|
| Where I think inheritance works best is when the state in base
| classes is limited and the interface is quiet clear. Ideally
| where you are meant to override is also well defined.
|
| Where it's the worst is when someone tries to use inheritance
| because they notice coincidentally duplicate code. The worst
| hell for this is in application configuration. I've seen 5
| layer deep inheritance trees to handle really basic things like
| "what port should this app bind to". It saved no code and
| introduced a bunch of complication around the transitive
| dependency baggage it brought on board.
|
| IMO, configuration should always be done via composition. It's
| more than fine to have a bunch of smaller composable config
| pieces just so long as you can easily jettison the broken
| parts.
| quaunaut wrote:
| > Where I think inheritance works best is when the state in
| base classes is limited and the interface is quiet clear.
| Ideally where you are meant to override is also well defined.
|
| What benefit is inheritance providing here? What you
| described sounds mostly like a struct, at which point the
| only value the interface provides is possibly some computed
| fields.
| cogman10 wrote:
| When you scratch deep enough at programming, everything is
| structs and interfaces defining how you interact with them
| and how they interact with the world.
|
| The best example of this (IMO) is how `AbstractMap` works
| in Java. [1]
|
| In order to make a new object which implements the `Map`
| interface, you just have to inherit from the `AbstractMap`
| base class and implement 1 method, `entrySet`. This allows
| for you to have a fully compliant `Map` Object with very
| little involved work which can be progressively enhanced to
| provide the capabilities you want from implementing said
| map.
|
| This comes in handy with stuff we've done when you can take
| advantage of the structure of an object to get a more
| optimal map. For example, a `Map<LocalDate, T>` can be
| represented in a 3 node structure, with the first node
| representing the year, the second the month, and the final
| the day. That can give you a particularly compact and
| fairly fast representation.
|
| The value add here is you can start by implementing almost
| nothing and work your way up.
|
| [1] https://docs.oracle.com/javase/8/docs/api/java/util/Abs
| tract...
| quaunaut wrote:
| I understand, and what you shared is a perfect example of
| what I said- but I fundamentally disagree with the notion
| that it's the same between the two.
|
| I think that in effect, as you associate more behavior
| with a particular struct(as opposed to what you're
| attempting to do with said struct), the greater
| expectation it presents that the struct is what you code
| around. More and more gets added to state over time, and
| more expectations about behavior get added that don't
| need to exist.
|
| Sure, you could say "Well, then just be strict about what
| behavior is expected in the interface"- but that effort
| wouldn't be necessary if we didn't make the struct the
| center of the behavior in the first place.
| taylodl wrote:
| Prefer IS-A relationships where they make sense, i.e. where you
| expect the Liskov Substitution Principle to apply. Otherwise
| use composition. It's really that simple.
| quaunaut wrote:
| I'd be ready to agree if I could be pointed at a time that
| inheritance actually carries a real benefit- a time you _would_
| choose it over composition, if composition is available.
| bsder wrote:
| Inheritance was a _premature optimization_ for computers with
| small memory. It did its job. However, once memory got big,
| people forgot to throw it out.
|
| Composition uses a lot more indirection. That's _bad_ on modern
| CPUs. Pointer chasing throws out performance, so composition is
| _not_ always preferred, either.
| fire_lake wrote:
| Hmmm. I have exactly zero occurrences of implementation
| inheritance in my code base.
| feoren wrote:
| Exactly. "Why does everyone use it?" is a false premise. I
| don't use it.
| quantified wrote:
| Meyer also thought (for a while) that inheritance was more
| powerful than genericity. Later he admitted his "proof" of that
| was weak.
| lamontcg wrote:
| Inheritance is really just a public interface, a private
| interface and automatic delegation of those interfaces to the
| base class.
|
| The problems with inheritance are:
|
| - people shove code in the base class to dedup it without
| thinking about design
|
| - people add public and protected methods without thinking about
| interface design
|
| - the names of the base class and the public and protected
| interfaces are exactly the same thing
|
| The last point is that if you have a FooBase class which is
| public then you can wind up with a bunch of List<FooBase>
| containers that are coupled to the base class and external
| methods that take FooBase parameters along with a bunch of
| concrete instances which are dependent upon the FooBase class
| protected interface and code implementation. This creates the
| brittle base class problem.
|
| If instead you had an IFoo interface and only ever used
| List<IFoo> instead of FooBase anywhere then you could always
| define FooBasev2 which implemented IFoo as well and FooBase and
| FooBasev2 can coexist in your codebase without having to break
| any external consumers (open-closed principle in practice).
|
| If base classes were only allowed to be used in inheritance and
| couldn't be parameters, generics, etc then that would force users
| to create base classes and public interfaces in pairs and would
| decouple their names and by writing the public interface down as
| its own thing developers would be more likely to focus on that
| design.
|
| And really inheritance is just syntax sugar around having a
| component (the base class) which has automatic delegation of of
| the public interface and protected interface to it in the
| inheriting object, with so little typing that it becomes easy to
| not think about what you're doing -- and while reusing the same
| name for three different things and creating tight coupling, and
| you can't dependency inject different baseclasses at runtime. If
| languages had better terse syntax for delegation then composition
| would get a lot easier to use like inheritance is (which is
| something that Go oddly enough gets more-or-less correct, dunno
| why they didn't mandate piles of boilerplate for delegation like
| they did for error checking).
| m463 wrote:
| I found multiple inheritance kind of fun when I was first
| exploring python.
|
| Then, madness...
___________________________________________________________________
(page generated 2024-04-11 23:01 UTC)