Live data from Hacker News

Liskov Substitution: The real meaning of inheritance

cekrem.github.io

11–20 of 92 posts

Re: Liskov Substitution: The real meaning of inheritance

#11
post #3

And functional programmers would argue that contravariance is the real meaning of Liskov’s substitution principle: https://apocalisp.wordpress.com/2010/10/06/liskov-substituti...

>So LSP just says “predicates are contravariant”

Maybe just leave out the "just" for a pleasant journey?

Since the interesting part of Barbara's uh, guideline, as "almost" pointed out by your link, is "almost" the opposite of "almost" trivial..

Don't mind me, I'm imbecilic :)

(See your link's comments, at least those imbeciles "almost" get it)

Re: Liskov Substitution: The real meaning of inheritance

#12
post #4

Do yourself a favor and wear yourself off all this SOLID, Uncle Bob, Object Oriented, Clean Code crap. Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives. Example: imagine you have a school system where there are student users and there are…

Just because you see OO languages starting to favor composition over inheritance does not mean inheritance has no place, and indeed, interfaces as a form of composition have existed in many bog-standard OO languages for decades.

Your example dosn't compute, at least in most languages, because derived objects would not have the same shape as one another, only the shape of the base class. I.e. functions expecting a User object would of course accept either an Employee or a Student (both subclasses of a User), but functions expecting a Student object or an Employee object would not accept the other object type just because they share a base class. Indeed, that's the whole point. And as another poster mentioned, you are introducing a burden by now having no way to determine whether a User is an Employee or a Student without having to pass additional information.

Listen, I'll be the first to admit that the oo paradigm went overboard with inheritance and object classification to the n-th degree by inventing ridiculous object hierarchies etc, but inheritance (even multiple inheritance) has a place- not just when reasoning about and organizing code, but for programmer ergonomics. And with the trend for composition to disallow data members (like traits in Rust), it can seriously limit the expressiveness of code.

Sometimes inheritance is better, and if used properly, there's nothing wrong with that. The alternative is that you wind up implementing the same 5-10 interfaces repeatedly for every different object you create.

It should never be all or nothing. Inheritance has its place. Composition has its place.

And if you squint just right they're two sides of the same coin. "Is A" vs "Can Do" or "Has".

Re: Liskov Substitution: The real meaning of inheritance

#13
post #4

Do yourself a favor and wear yourself off all this SOLID, Uncle Bob, Object Oriented, Clean Code crap. Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives. Example: imagine you have a school system where there are student users and there are…

Never using inheritance is pushing the dogma to the other extreme imo. The whole prefer composition over inheritance is meant to help people avoid the typical OO over use of inheritance. It doesn't mean Is-A relation doesn't/shouldn't exist. It just means when there is data, prefer using composition to give access to that data.

There will be times when you want to represent Is-A relationship - especially when you want to guarantee a specific interface/set of functions your object will have - irrespective of the under the hood details.

  Notifier n = GetNotifier(backend_name);
Here what you care about is notifier providing a set of functions (sendNotification, updateNotification, removeNotification) - irrespective of what the implementation details are - whether you're using desktop notifications or SMS notifications.

Re: Liskov Substitution: The real meaning of inheritance

#14
post #4

Do yourself a favor and wear yourself off all this SOLID, Uncle Bob, Object Oriented, Clean Code crap. Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives. Example: imagine you have a school system where there are student users and there are…

Just because you see OO languages starting to favor composition over inheritance does not mean inheritance has no place, and indeed, interfaces as a form of composition have existed in many bog-standard OO languages for decades. Your example dosn't compute, at least in most languages, because derived objects would not have the same shape as one another, only the shape of the base class. I.e. functions expecting a Use…

Inheritance has no place in production codebases—unless there is strict discipline, enforced by tooling, ensuring calls only go in one direction. This Liskov stuff has zero bearing.

Re: Liskov Substitution: The real meaning of inheritance

#15
I thought there were more but these are the only two interesting prior threads I could find. Others?

A better explanation of the Liskov Substitution Principle - https://news.ycombinator.com/item?id=38182278 - Nov 2023 (1 comment)

The Liskov Substitution Principle (2019) - https://news.ycombinator.com/item?id=23245125 - May 2020 (93 comments)

Re: Liskov Substitution: The real meaning of inheritance

#16
post #4

Do yourself a favor and wear yourself off all this SOLID, Uncle Bob, Object Oriented, Clean Code crap. Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives. Example: imagine you have a school system where there are student users and there are…

> Don't ever use inheritance. Instead of things inheriting from other things, flip the relationship and make things HAVE other things. This is called composition and it has all the positives of inheritance but none of the negatives.

Bah. There are completely legitimate uses of inheritance where it's a really great fit. I think you'll find that being dogmatic about avoiding a programming pattern will eventually get you twisted up in other ways.

Inheritance can be used in a couple of ways that achieve a very-specific kind of code reuse. While I went through the early 2000's Java hype cycle with interfaces and factories and builders and double-dispatch and visitors everywhere, I went through a period where I hated most of that crap and swore to never use the visitor pattern again.

But hey, within the past two years I found an unbeatable use case where the visitor pattern absolutely rocks (it's there: https://github.com/titzer/wizard-engine/blob/master/src/util...). If you can come up with another way by which you can deal with 550 different kinds of animals (the Wasm instructions) and inherit the logic for 545 and just override the logic for 5 of them, then be my guest. (And yes, you can use ADTs and pattern-matching, which I do, liberally--but the specifics of how immediates and their types are encoded and decoded just simply cannot be replicated with as little code as the visitor pattern).

So don't completely swear off inheritance. It's like saying you'll never use a butter knife because you only do pocket knives. After all, butter knives are dull and not good for anything but butter.

Re: Liskov Substitution: The real meaning of inheritance

#17

Earlier quoted context omitted.

Just because you see OO languages starting to favor composition over inheritance does not mean inheritance has no place, and indeed, interfaces as a form of composition have existed in many bog-standard OO languages for decades. Your example dosn't compute, at least in most languages, because derived objects would not have the same shape as one another, only the shape of the base class. I.e. functions expecting a Use…

Inheritance has no place in production codebases—unless there is strict discipline, enforced by tooling, ensuring calls only go in one direction. This Liskov stuff has zero bearing.

Respectfully, I think you're throwing the baby out with the bathwater.

I read your story, and I can certainly empathize.

But just because someone has made a tangled web using inheritance doesn't mean inheritance itself is to blame. Show me someone doing something stupid with inheritance and I can demonstrate the same stupidity with composition.

I mean, base classes should not be operating on derived class objects outside of the base class interface, like ever. That's just poorly architected code no matter which way you slice it. But just like Dijkstra railing against goto, there is a time and a place for (measured, intelligent & appropriate) use.

Even the Linux kernel uses subclassing extensively. Sometimes Struct B really is a Struct A, just with some extra bits. You shouldn't have to duplicate code or nest structures to attain those ergonomics.

Anything can lead to a rube-goldberg mess if not handled with some common sense.

Re: Liskov Substitution: The real meaning of inheritance

#18
From the article:

Again, kudos to Uncle Bob for reminding me about the importance of good software architecture in his classic Clean Architecture! That book is my primary inspiration for this series. Without clean architecture, we’ll all be building firmware (my paraphrased summary).

What does clean architecture have to do with building firmware or not? Plenty of programmers make a living building firmware. Just because they don't need/can't/want to apply clean architecture in their code, doesn't mean they are inferior to those who do.

Furthermore, after a snippet which I suppose it is in Kotlin, there is this:

While mathematically a square is a rectangle, in terms of behavior substitutability, it isn’t. The Square class violates LSP because it changes the behavior that clients of Rectangle expect. Instead of inheritance, we can use composition and interfaces

The Liskov principle is about one of the three types of polymorphism (so far): subtyping polymorphism. Which is about inheritance. Composition is _not_ subtyping. And interfaces (be it Java's or Kotlin's) are another type of polymorphism: ad-hoc. Even Wikipedia[1] has the correct definition:

Ad hoc polymorphism: defines a common interface for an arbitrary set of individually specified types.

Therefore, the examples of interfaces aren't compliant with LSP as well.

I understand the good intentions behind the article, but it left much to be desired. A proper research to at least fix the glaring errors should have been made beforehand.

[1]: https://en.wikipedia.org/wiki/Polymorphism_%28computer_scien...

Re: Liskov Substitution: The real meaning of inheritance

#19
If I remember correctly, Liskov didn't talk about inheritance but subtyping in a more general way. Java, C++ and other, especially statically typed, compiled languages often use inheritance to model subtyping but Liskov/Wing weren't making any statements about inheritance specifically.

Re: Liskov Substitution: The real meaning of inheritance

#20
SOLID and clean code are not some universal bible that is followed everywhere, I spend a considerable amount of effort reasoning juniors and mid levels out of some of the bad habits they get from following these principles blindly.

For example the only reason DI became so popular is that you could not mock static in Java at the time. In FB codebase DI was also used in PHP until they found a way to mock static, after which the DI framework was deprecated and codemods started coming in removing DI. There is literally nothing wrong in using a factory method or constructing what you need on demand. These days static can also be mocked in Java and if you really think about it you see Spring Boot adds a lot of accidental complexity (but sure its convenient and well tested so its ok to use), concepts like beans and beanfactories are not essential for solving any business problem

Which brings me to S in SOLID, which I think is probably top 2 worst principles in software engineering (the no 1 spot goes to DRY). Somehow it came from some early 2000-s TDD crowd and the test pyramid, it makes sense if you embrace TDD, mocking, test pyramid and unit tests as a good thing. In reality that style of software is really hard to understand, every problem is split into 1000 small pieces invoking each other usually in some undefined ways, no flow can be understood without understanding and building a mental model of the entire 1000 object spaghetti. The tests themselves mostly just end up setting a bunch of mocks and then pretty much coupling the impl and the test on method call level, any change to the impl will cause the tests to break for only the reason that the new method call was not mocked. After going through all this ceremony the tests are not even guaranteeing the thing will work during runtime since the db, kafka or http was mocked out and all the filters, listeners, db validations were skipped. In these days so called integration tests with docker compose are a lot better (use actual db or kafka, wiremock the http level), that way your have a reasonble chance to catch things like did this mysql jdbc driver upgrade broke anything

I have to mention DRY also, the amount of sins caused in name of DRY by juniors is crazy, similar looking lines get moved into a common function/method/util all the time and coupling is introduced between 2 previously independant parts of the system. As the code involves and morphs into something different the original function starts getting more args to behave differently in one case and differently in another case, if it had been left as separate files each could evolve separately. I dont really know how to explain this better than coupling should not be introduced to save few lines of typing or boilerplate, in fact any abstraction or indirection should only be introduced when its really needed, the default mode should be copy/paste and no coupling (the person adding a cross cutting PR will likely not be a jr and has enough experience to know how and when to use grep).

Anyhow I have enough experience to know people are usually too convinced that all this solid, clean code stuff is peak software so I wont expect to change anyones thinking with 1 HN post, it usually takes me 2 years or so to train a person out of this and back to just putting the damn json in db without ceremony. Also need to make sure LLM-s have some good data that is based on experience and not dogmas to learn from :)

As for L, no strong beef with L it’s OK

Post reply on HN