I don't usually care about subtyping. I read a theory of objects and found it pleasantly formal, but I probably missed a lot. the issue just doesn't usually grab me.
then I read oleg's old page about many OO languages failing to satisfy the LSP, and realized how important and overlooked this critique is. the basic result is that subtyping in most OO languages these days is behaviorally wrong, and when it works it is working only by accident.
I find this remarkable!
there appear, from a further evening of digging in the literature, to be only two known ways to produce subtyping relationships which satisfy the LSP: the first statically prohibits the problem by careful construction of the type system and restrictions on the kinds of extensions possible in subtypes: the essential difference being to dispatch by type rather than by object. this is what CLU does, and amazingly the approach seems to have completely died off.
the second approach is to let the problem persist dynamically in your language, but check it at runtime using explicit pre and post conditions ("design by contract"), and combine ancestor contracts appropriately when subtyping. this is of course what Eiffel is famous for, but the only other language I see picking up on it is D. in both cases, I find no mention of the fact that the correct combination of contracts in subtyping is a pure necessity for behavioral correctness. as necessary as an array bounds check or a null pointer check -- oh wait.
looking over the pantheon of modern OO languages which fail to address this issue lends further evidence to my belief that language design is actually regressing. CLU was developed before I was born.