I'm currently dealing with a codebase that does this to a ridiculous extent. Like, literally, every change affects the entire project because everything is made of base-classes mixed in weird ways. Every concrete object inherits multiple base classes and no individual behavior. Imagine something like this:
class Book extends ShelfableItem, Pagable, Authored, Readable, BaseBook {}
Even if they were interfaces, it screams of the "model your code after physical objects" approach, where a system has 1 enormous "Book" type which represents all the things you can do with a physical book.
It seems unlikely that the same type should be "Shelfable" and "Readable" / "Pagable," because they describe distinct sets of operations. When a book is on a shelf, you can't page through it. If you "read" a book on a shelf, you only see the title, author, and maybe some pull quotes.
It depends. Of course we don’t see the whole picture because it’s just an example by OP, but I also find weird that the de facto solution to abstract classes is: interfaces. Sometimes, duplication is better than interfaces.
Yes. Did I mention there are interfaces too, with almost the same name, but it’s only used for the base classes. (Yes, there is only one implementation of all interfaces).
Having lots of interfaces for common things is not a bad thing. See how Rust traits work... even basic structs you create will probably implement lots of basic traits (some of which can be done automatically, thankfully) like `Display`, `Default`, several `From` or `Into` impls, `Clone`, `Copy` if your type is "light", `AsRef`, `Send` and many more!
This makes code much more reusable as so many functions are written based on those basic traits alone.
Of course, finding the right basic types is really hard and your company seems to have done that badly, but in principle, having some basic types to model very common "things" is a necessary thing.
The issue isn't the interfaces; it's that there is only one implementation (the base classes) per interface—so why even bother having an interface? Otherwise, I agree with you to a degree.
The main issue with the codebase is that if you want to, say, change the behavior of a Book, you have to go change the behavior of some base class (after working out which one is actually being called). This base class might be used in a Clock, Field, or Filesystem as well—something so conceivably far away that their similar behavior is a coincidence and not really related at all. Then you get to argue with the architect about whether "reading a book" is different than "reading a clock."