Agreed; I’ve only encountered a RDBMS change once in my years of development, and even that went rather smoothly. It generally is a bad example because it’s not only the inheritance that might cause troubles, it’s the dialect of SQL just as well.
Nevertheless, I’ve found that composition generally causes less headaches than inheritance, but it’s no silver bullet either. Actually, the two techniques tend to work together to get the most flexible design possible. You’ve probably heard of the rule of thumb that you should only ever use inheritance if there’s an “is-a” relationship.
I’ve extended that rule for myself: the type has to be the same (so Guest is a User), and the motivation for inheritance should be changing the existing behaviour rather than reusing code. If there’s a few lines of code I want to reuse, I try to split it out into another object and inject that object into the client.
Another rule of thumb is that I try never to extend more than once, so I’m not setting up inheritance trees. As PCSpectra already mentioned, experience tells me that a large inheritance tree can be a disaster to debug. Apart from the debugging, there are other problems introduced by inheritance; for starters it’s harder to test as you can’t simply inject a mock or stub to replace the parent object.
The inability to test is a direct result of the tight coupling between parent and child. Child-classes can make a whole lot of assumptions about the state the parent is in and indeed, most child classes I write do so. That’s not a bad thing per se, but if it’s like to change it would be, as it’s harder to change the behaviour of a child class than it is once you’ve gone the mixing and matching route.
This is not carved in stone, obviously. It takes experience and a healthy dose of logic to find the best ratio. I think that it is a good point of view to favour composition over inheritance, but I wouldn’t say inheritance is useless, of even bad.
Yes, of course. If the chance of change is low, you shouldn’t be worrying a whole lot about it. Nevertheless, the cost of abstracting repetitive code into a different object and inject that object into the object that uses that behaviour is not that much higher than extending the object to reduce repetitive code. It doesn’t get “nightmarishly complex” as you put it, if you try to keep sane and remember that at the end of the day, accomplishing the goal is more important than the means to accomplish it.
You can shoot yourself in the foot with either way; the only thing I can tell you is that the hole in my foot was bigger when I had a large inheritance tree.