Live data from Hacker News

Why general inheritance is flawed and how to finally fix it

minborgsjavapot.blogspot.com

11–20 of 104 posts

Re: Why general inheritance is flawed and how to finally fix it

#11
post #7

I think that composition is absolutely better than inheritance except for one thing: boilerplate. The issue is that boilerplate is kind of important. You don't want to litter your code with "f150.ford.car.vehicle.object.move(50, 50)". You can and should re-implement "move" so that you only have to call "f150.move(50, 50)", but that still requires boilerplate, just in the "F150" class. Often you have class containing…

For the same reason, I'm not so absolutist about DRY. Having the most elegant codebase also often means the codebase that's hardest to work on, and it's often better to clean things up afterwards once you know how things will be structured.

This question determines if you need to be DRY or not:

"If [some fact] in the code base needs to change, how many places would we have to change it in?"

If the answer is > 1, you have a very good DRY case. Otherwise, when [some fact] changes, it will probably not be changed in one of the places, and the system will be broken.

This often coincides with having an "elegant codebase", but that's not the most important part.

Re: Why general inheritance is flawed and how to finally fix it

#12

I (happily) write a lot of OOP code, "inheritance is bad, use composition" is such a trite and unhelpful dogma that gets in the way of any actual discussion about where inheritance is useful. IMO, the case where inheritance makes the most sense is when you have a set of objects polymorphically answering some question, usually with a simple answer. class Subset class Whole which is used as such: subset = Subset::Whole…

OP title said "general inheritance is bad", not " Generally, inheritance is bad".

And the text support that. The "general inheritance" the author describe is not the one you've just used.

And i'm hijacking your post, sorry, but i really agree with the author with the "incidental inheritance" point. This is the worst. I lost a month to a bug caused by this kind of inheritance (Jenkins package that tried to be cute and interfered with a cloudbees class). I won't take a java gig ever again. Not worth the brain damage.

Re: Why general inheritance is flawed and how to finally fix it

#13

I think that composition is absolutely better than inheritance except for one thing: boilerplate. The issue is that boilerplate is kind of important. You don't want to litter your code with "f150.ford.car.vehicle.object.move(50, 50)". You can and should re-implement "move" so that you only have to call "f150.move(50, 50)", but that still requires boilerplate, just in the "F150" class. Often you have class containing…

This doesn't inhherently have anything to do with inheritance. Delegation is the compositional solution to this problem and some languages do have built in sugar for that. It usually looks something like:

    class F150(@delegate private val underlying: Car) { ... }
    class F150(private val underlying: Car) : Car by Underlying { ... }
    // etc

Re: Why general inheritance is flawed and how to finally fix it

#14
post #3

Re: using composition In the past, it was feared that this would lead to reduced performance but this is simply not the case. Great to see the strong evidence here /s

It might be more productive if you were to outright disagree with the statement instead of simply noting a claim that isn't strongly substantiated. If you have experiences that demonstrate to you that this claim is weak I'm sure everyone else would be interested in them (I know I would be!).

To add onto that, I tried several different hand rolled alternatives to a c++ vtable and the builtin functionality outperformed them all which was surprising. It’s entirely possible I didn’t know what I was doing but I think my point is that vtables are a language feature that compilers know how to reason about and apply optimizations to. Other concepts not so much.

Re: Why general inheritance is flawed and how to finally fix it

#15

Earlier quoted context omitted.

It might be more productive if you were to outright disagree with the statement instead of simply noting a claim that isn't strongly substantiated. If you have experiences that demonstrate to you that this claim is weak I'm sure everyone else would be interested in them (I know I would be!).

To add onto that, I tried several different hand rolled alternatives to a c++ vtable and the builtin functionality outperformed them all which was surprising. It’s entirely possible I didn’t know what I was doing but I think my point is that vtables are a language feature that compilers know how to reason about and apply optimizations to. Other concepts not so much.

Something to keep in mind when microbenchmarking virtual calls is that compilers will happily devirtualize them whenever possible.

Re: Why general inheritance is flawed and how to finally fix it

#16
post #3

Re: using composition In the past, it was feared that this would lead to reduced performance but this is simply not the case. Great to see the strong evidence here /s

Language Design: Let's start with the admission that there's a lot of cargo culting in language design. Evidence is hard to come by and the best evidence is other languages that succeed with different choices. I remember the flame wars about how multiple inheritance would cause a language to fall apart. When will languages adopt the idea that encapsulation is not sacrosant, as the theoretical issue about the backdooring like _method in python or package level public in Go^ is, someone might abuse it? Howabout make testing a first class concept and lump it in there or just use the shortcuts that have been working.

What is inheritance? Inheritance (for any given language) is a language supported type of class composition (https://youtu.be/eEBOvqMfPoI?t=2874), as a closure. A class is a function and once you understand this, it opens up possibilities in how you design and test. This has nothing to do with performance, which is a nonsequitor. Is Rust less performant than Java because of how it does composition? No. Perhaps there's something in the JVM that makes mixins difficult to optimize for, but that would require some evidence (there's no general branch prediction in the JVM, last I checked) and is, ultimately, at the feet of the JVM implementation. Have a look at Go and Rust.

Naturally, because a specific kind of inheritance is a language feature, it gets overused and a language, (like Java) for backward compatibility's sake, overuses it in designing new features. Looking at other languages like Javascript, Lua, Erlang, PHP, Ruby, Rust, etc. saner heads have prevailed and even Java has resorted to using "Aspects" ...which are runtime traits for additional types of composition^^.

Regarding the rest of the article... His arguments for using final include: 1. Someone may forget to use super() and that's bad because what I want to happen trumps what they want to happen. 2. People can't subclass my class across package boundaries, because I don't know why handwaved JPMS (then covered in Should Inheritance Across Package Boundaries Ever be Used?). His reasoning is not compelling, in the least. I can say, without hesitation, that 'final' is harmful. Adding final to a class is such a violation of the concept of reusable software, I'm surprised the FSF doesn't boycott languages. In C++ there are performance benefits. In Java it's just to put up a roadblock. This was never a good idea and makes testing impossible in some cases (where final classes are injected into final classes). This is purely because of Java's design as a language, not because of some demonstrably helpful concept, implemented poorly^^.

^If you are a language designer, always allow backdooring of accessors for testing, at the very least. This conviction that they must always apply to protect developers from each other is misplaced and has hurt the reliability of software, badly.

^^Spring has a form tacked-on composition, which is both ugly from a conceptual standpoint and problematic from a testing standpoint. Java always seems 20+ years behind.

Re: Why general inheritance is flawed and how to finally fix it

#17
post #7

I think that composition is absolutely better than inheritance except for one thing: boilerplate. The issue is that boilerplate is kind of important. You don't want to litter your code with "f150.ford.car.vehicle.object.move(50, 50)". You can and should re-implement "move" so that you only have to call "f150.move(50, 50)", but that still requires boilerplate, just in the "F150" class. Often you have class containing…

For the same reason, I'm not so absolutist about DRY. Having the most elegant codebase also often means the codebase that's hardest to work on, and it's often better to clean things up afterwards once you know how things will be structured.

Recently (last month basically), i rewrote my code (i'm leaving soon and i want my coworkers/successors to have success improving on what i've done).

I followed every principle of good code, except one, DRY. I tried to make generic parts to connectors, because they do have similarities. But this is a work of at least a year, and the price to make it generic was increasingly more complex configuration files (Just the pagination alone added 3 variables for two different APIs, and the number of app i am supposed to interact with should grow to ~40). I decided after a few days of reflexion that the idea was not that dumb in principle, but unworkable in my case, and decided that one connector for one API, even with a lot of repetition.

Re: Why general inheritance is flawed and how to finally fix it

#18
post #15

Earlier quoted context omitted.

To add onto that, I tried several different hand rolled alternatives to a c++ vtable and the builtin functionality outperformed them all which was surprising. It’s entirely possible I didn’t know what I was doing but I think my point is that vtables are a language feature that compilers know how to reason about and apply optimizations to. Other concepts not so much.

Something to keep in mind when microbenchmarking virtual calls is that compilers will happily devirtualize them whenever possible.

Yeah I’m aware but I made sure the same devirtualization would apply in production too so the compiler doing any devirtualization was good. It was a bit surprising that vtables outperformed std::variant (and I had tried implementing my own hand rolled equivalent of that too).

Re: Why general inheritance is flawed and how to finally fix it

#19

I (happily) write a lot of OOP code, "inheritance is bad, use composition" is such a trite and unhelpful dogma that gets in the way of any actual discussion about where inheritance is useful. IMO, the case where inheritance makes the most sense is when you have a set of objects polymorphically answering some question, usually with a simple answer. class Subset class Whole which is used as such: subset = Subset::Whole…

In most languages Subset would be called a trait or interface, rather than general inheritance. You've picked an example with no fields or overriden methods, so it's impossible for it to demonstrate the shortcomings of inheritance.

What is the practical difference between inheritance, and a trait or interface with a default implementation? It seems like both risk the addAll() bug.

Re: Why general inheritance is flawed and how to finally fix it

#20
post #7

Earlier quoted context omitted.

For the same reason, I'm not so absolutist about DRY. Having the most elegant codebase also often means the codebase that's hardest to work on, and it's often better to clean things up afterwards once you know how things will be structured.

This question determines if you need to be DRY or not: "If [some fact] in the code base needs to change, how many places would we have to change it in?" If the answer is > 1, you have a very good DRY case. Otherwise, when [some fact] changes, it will probably not be changed in one of the places, and the system will be broken. This often coincides with having an "elegant codebase", but that's not the most important pa…

The original formulation of DRY was "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system," which in import winds up pretty close to what you have here.
Post reply on HN