In my last post, I was talking about the tendency of OO programmers to develop overly complex systems. Aristotle pointed out that I was talking about ravioli code. I've never heard the term before, but it seems perfectly appropriate, particularly given some code I'm trying to understand right now. Before I get there, let's have a look at one of the comments in the "ravioli code" discussion:
Although it often does, I don't think RavioliCode always means that something needs fixing. In OO programming, there seems to be a trade-off between "easy to change" code and "easy to understand at first glance" code. You can try to maximize ease-of-change, then do your best to make the code understandable; or you can try to maximize understandability. I do the former. --StanSilver
Ease of change instead of understandability? Pardon me for quibbling, but what we're looking for is the elusive "maintainability": that combination of ease of change and understandability that hits the sweet spot we all argue over. A blanket favoring of "ease of change" is a disaster. If something is really easy to change but hard to understand, is it not, by definition, easy to misunderstand? Thus, those easy changes might be dead wrong, a problem I've found in some code I'm working on right now. I don't think this is what StanSilver is arguing for, but if we're go for ease of change and then understandability, we all know what happens to developers who say "yeah, I'll get around to that".
$ ack 'XXX|TODO' lib/ t/ aggtests/ docs/ --all | wc -l
So, turning to the code I'm working on right now, I've decided to try and understand it. Here's a (loose) inheritance heirarchy.
B A B
| | |
C D E
\ | /
(use.perl is messing with that format a bit)
Given that I need to instantiate F, notice anything problematic there? And yes, B does override stuff in A, so you had better hope that:
And do C, D and E have any identically named methods? We're not calling "next::method" much internally, but even if we did, this level of complexity helps to illustrate why some languages simply outlaw multiple inheritance: it's too dangerous to abuse.
By the way, it's worse than that diagram implies. The hierarchy ignores that various subroutines are exported into some classes (subs, not methods) and we have three traits loaded into F. After working like mad to understand the various classes involved, I ran some tests with an instance of "F". I used the debugger to dump out the method and it identifies 144 methods on the F object. These methods are from nine packages. What it doesn't do is show you how the traits often "wrap" the methods to alter their behavior. It also doesn't show which of those methods are documented as being overridden. It also doesn't show which overriding methods should not have their parent called. It also doesn't show which overriding methods should have their parents called. It also doesn't show which methods just crept in there by accident just because the hierarchy is so complicated.
Now if you'll excuse me, I need to go find some aspirin.