So, day off before travel before onsite, playing ONI, having fun, but I want to muse about a highly geeky matter anyway…
Do you ever override in a sub-class a method that’s already implemented in a super-class? I want to recommend to you a policy of avoiding that like the plague, whenever you can.
I will go further: I avoid, maybe not as much as the plague, but surely as much as the measles, even deriving any sub-class off of any super that has any implementations at all. (Anything not a "pure virtual base class" in c++, "interface" in java. In languages that have no such concept, any super-class that has an implementation other than "throw new ForgotToOverride".)
I came to this philosophy very slowly over an embarrassingly large number of years. I say "embarrassingly" because, nearly as soon as I came to it, it made itself clear almost immediately how much better/stronger/faster/kinder my resulting designs are. I came up through the OO revolution, you see, and i’m a confident and capable OO geek, surrounded by this thing, I’ll call it "implementation inheritance". It was the air I breathed.
When I started TDD, I had an enormous amount to learn just in first the basics and then the intermediates. It took some time. As it progressed, I learned about a kind of meta-pattern i’d been engaging in for years & years. It’s a hard one to see, this meta-pattern, and that’s what took me so damned long. It’s trading the major value of getting what i’ve elsewhere called "rigorous" results for a variety of lesser values that essentially amounted to local convenience.
Or, maybe, that’s not quite right.
Let’s try another take: I was trading to get a kind of local appearance of convenience, and in so doing giving up an actual global improvement in my ability to ship more value faster.
And the worst part of it was, I wasn’t even getting that much local feeling of convenience. Just a little. And I was giving up a lot of global value-shipping speed. And this wasn’t just one feature of my coding technique, I was doing it all over the place in myriad ways. TDD slowly helped me find those ways and choke them down, one by one. I will never not think of it fondly for doing that. One of the things I was doing, tho, was being what I thought was a facile and even gifted — I get perky at times — object-oriented programmer. I used implementation inheritance heavily.
The benefit of implementation inheritance is dreadfully obvious: it’s the fastest possible way in a statically typed language to re-use code you or someone else already wrote. And if you’re good at it, I was, you could carry it around with you all over the place, like an overloaded saddle-bag draped around your neck, and never even notice it, except occasionally if you needed to feel good about yourself.
TDD — as always I mean by this "the modern software development synthesis" broke me of a lot of "good programming practice" habits that were not in fact good programming practice, but it took me a really long time to get past this one. I was already pretty much a TDD master before I finally set it aside.
Why set implementation inheritance aside?
- You almost never need it. That is, anything you can do with it and a modern optimizing compiler you can do just as well without it. The sole exception i’ve found is in some obscure performance situations that, trust me, neither you nor I are likely to find outselves in.
- It complicates in a particular way: you see, implementation inheritance impedes quite sharply what I regard as the central skill or discipline of being able to ship more value faster, the management of mental scope. Every implementation inheritance — every single one — creates a hole in the little conceptual box of “the code right in front of me”. And it does it implicitly, almost invisibly. It is a guaranteed violation of “locality of reference”, the ability to see what you need right when you need to see it.
How did TDD help me finally see this?
TDD helped me to see that implementation inheritance was undesirable because it made me think, more, about stuff that wasn’t there. Well. Mama dint raise no thinkers. Thinking more about stuff that’s not in front of me is exactly the opposite of managing mental scope. When I have to TDD implementation inherited classes, tho, that’s exactly what I have to do. And the thing is? Half of that thinking has almost no relevance to the current value i’m trying to ship faster.
Maybe you’ve never seen hardcore implementation inheritance. I see it all the time, particularly in older larger codebases dominated by a so-called "senior architect" who memorized the GOF book in ’95 and hasn’t shipped value in the last 20 years.
But if you’re wondering, here: this chart — part 1 of 3 — is illustrative. Almost none of those boxes is an interface. At the bottom of that dependency tree there are leaf nodes that have upwards of seven or ten implementing super-classes. Every one of them with dozens of methods, every one a partial override of its super.
We don’t ship more value faster by juggling more balls, but by juggling fewer. Implementation inheritance always adds balls to what you have to juggle in your head while you’re working.
So let it go. If you’re having trouble seeing a new design without it, come find me, or any of dozens of serious TDD’ers out there. We like solving design problems. We do it the way people work crosswords in the sunday times. Ask us, you’ll see.