I would say, taking the risk of being shot down, that Inheritance is a code smell in itself :)
The issue with inheritance is that it can be used for two orthogonal purposes:
- interface (for polymorphism)
- implementation (for code reuse)
Having a single mechanism to get both is what leads in "inheritance abuse" in the first place, since most people expect inheritance to be about interface, but it might be used to get default implementation even by otherwise careful programmers (it's just so easy to overlook this...)
In fact, modern languages like Haskell or Go, have abandonned inheritance in order to separate both concerns.
Back on track:
A violation of the Liskov Principle is therefore the surest sign, since it means that the "interface" part of the contract is not respected.
However even when the interface is respected, you might have objects inheriting from "fat" base classes just because one of the methods was deemed useful.
Therefore the Liskov Principle in itself is not enough, what you need to know is whether or not the polymorphism is used. If it isn't, then there wasn't much point in inheriting in the first place, that's an abuse.
- enforce the
- check it's actually used
A way around it:
Imposing a clear separation of concerns:
- only inherit from interfaces
- use composition to delegate implementation
means that at least a couple keystrokes are needed for each method, and suddenly people begin to think about whether or not it's such a great idea to reuse this "fat" class for just a small piece of it.