Constructor Inheritance in C# and Ruby

This morning: “Surprise!  Want to conduct a job interview?”  I’ve been here a little over 3 months, but um, sure!  “Great.  He’s in the conference room right now.”  Wow, we move quick.

So, without much time to gather my thoughts for good tech-y interview questions, I printed out the resume, and I winged it.  In the middle of the interview, I flipped over his resume, and scribbled out short-hand C# like this:

class A {
public A() {
Console.WriteLine(“in A”);
}
}
class B : A {
public B() {
Console.WriteLine(“in B”);
}
}
class C : B {
public C() {
Console.WriteLine(“in C”);
}
}
new C();

I asked, “What does this print out?” You know, see if he knows which order constructor inheritance goes in. I wanted to hear, “in A, in B, in C”, but not “in C, in B, in A”.

I forget exactly what the candidate’s answer was, but it stirred up a bit of discussion, because the three of us interviewing him disagreed on the answer: one of us said it would only print “in C,” because you need to stick : base() on the B and C constructors for the inheritance to work; I agreed with the third interviewer, who said it would print “in A, in B, in C”, because constructor inheritance is automatic (with no-arg constructors). We fudged around it, laughed a bit, and the interview moved on. (Update: here’s the answer.)

Back at my desk, I had to try it out. I didn’t want to bother with a whole Visual Studio .sln and all that nonsense, so I tried it in Ruby:

class A
    def initialize
        puts "in A"
    end
end
class B < A
    def initialize
        puts "in B"
    end
end
class C < B
    def initialize
        puts "in C"
    end
end

C.new

And the output is…”in C”! Huh? That can’t be right…I was sure constructors were inherited automatically! Then I realized, of course! I’m working in Ruby, and you have to explicitly call superclass methods, constructors included:

class A
    def initialize
        super # <- call the superclass' constructor
        puts "in A"
    end
end
class B < A
    def initialize
        super # <- call the superclass' constructor
        puts "in B"
    end
end
class C < B
    def initialize
        super # <- call the superclass' constructor
        puts "in C"
    end
end

C.new

Stupid Ruby! Did I find a case where C# actually works nicer than Ruby? But then I realized, this also means Ruby lets you change the order of the constructor inheritance: you can go bottom-up, if you want, or even stranger:

class A
    def initialize
        super
        puts "in A"
    end
end
class B < A
    def initialize
        super
        puts "in B"
    end
end
class C < B
    def initialize
        puts "in C"
        super # <- call up the chain AFTER we're done
    end
end

C.new

That one prints out “in C, in A, in B”. The nice thing? No rule to memorize, and more control. The down-side? More to type. But given how compact Ruby already is, I think the added control is worth it here. What do you think?


(Update: I eventually did fire up Visual Studio, and the code above printed “in A, in B, in C”, without me typing out : base(). C# inherits constructors automatically, and the superclass constructors run before subclass constructors.)

Why “Less Code” Matters

…being able to do task X with 50 lines of code is preferable to needing 500 lines of code to do task X. Less code takes longer to write, but the real benefits are around maintenance: less code means less of a chance of bugs, less to keep in your head, less for someone else (or yourself 6 months later) to read through and learn, less to test, and less to modify when you change the rest of the system.

- Alan Keefer, Syntax Matters

I’d like to expand on that. I don’t think it’s clear how important “less code” is, or how harmful more code is. So let’s take an example written in a Blub-y language, and see how well we can refactor it.

(I know this post is kind of long, but it’s mostly Blub code, and it should scan quickly.)

Let’s make a sandwich.

routine makeSandwich
    look for the peanut butter in the cabinet
    if it's not there, look for it in the other cabinet
    put the peanut butter on the counter

    look for the jelly in the fridge
    if it's not there, look for it in the cabinet
    if it's not there, look for it in the other cabinet
    put the jelly on the counter

    find a napkin
    put the napkin on the counter

    find the bread in the bread drawer
    untie the bread bag
    take two pieces of bread from the bag
    close the bread bag
    put the bread back in the bread drawer
    put the two pieces of bread on the napkin

    find a butter knife
    put the butter knife on the napkin

    open the peanut butter jar
    stick the butter knife into the peanut butter jar
    with the butter knife, scoop out some peanut butter
    spread the peanut butter on one piece of bread
    close the peanut butter jar
    put the peanut butter back in the cabinet

    wipe the butter knife on the other piece of bread

    open the jelly jar
    stick the butter knife into the jelly jar
    with the butter knife, scoop out some jelly
    spread the jelly on one the other piece of bread
    close the jelly jar
    put the jelly back in the fridge

    put the knife in the sink

So much work! No wonder I seldom cook. Can we improve that at all? Well, the “looking for in 2 cabinets” seems to be a pattern, so let’s Extract Method:

routine lookForInTwoCabinets (lookFor)
    look for the lookFor in the cabinet
    if it's not there, look in the other cabinet
    return it

routine makeSandwich
    lookForInTwoCabinets (peanut butter)
    put the peanut butter on the counter

    look for the jelly in the fridge
    if it's not there, lookForInTwoCabinets(jelly)
    put the jelly on the counter
    ...

Can we move the “put it on the counter” inside lookForInTwoCabinets? I don’t know…it would work for the peanut butter, but what if we find the jelly in the fridge? In that case, we wouldn’t call lookForInTwoCabinets(jelly), so we might never put the jelly on the counter. Besides, the name doesn’t really imply anything about what we do after we find the thing. We should probably leave it outside. Yeah, it’s not so DRY, but let’s move on.

That big block where we look for bread, we can’t really compress it at all…but we can extract it, just to wrap the whole sequence of steps up with a name.

...
routine getBread
    find the bread in the bread drawer
    untie the bread bag
    take two pieces of bread from the bag
    close the bread bag
    put the bread back in the bread drawer
    put the two pieces of bread on the napkin

routine makeSandwich
    ...
    find a napkin
    put the napkin on the counter

    getBread

    find a butter knife
    put the butter knife on the napkin
    ...

Ok, we’re making progress. What about spreading the peanut butter & jelly on the bread? Can we extract another method?

routine spread (topping, breadSlice)
    open the topping jar
    stick the butter knife into the topping jar
    with the butter knife, scoop out some topping
    spread the topping on breadSlice
    close the topping jar
    put the topping back in the cabinet

routine makeSandwich
    ...
    find a butter knife
    put the butter knife on the napkin

    spread (peanut butter, one piece of bread)

    wipe the butter knife on the other piece of bread

    spread (jelly, the other piece of bread)

    put the knife in the sink

Great! Except we just introduced a bug: after closing the topping jar, spread always puts the topping back in the cabinet, and the jelly goes in the fridge (moldy jelly is a Bad Thing). Introduce Parameter:

routine spread (topping, breadSlice, returnToppingTo)
    open the topping jar
    stick the butter knife into the topping jar
    with the butter knife, scoop out some topping
    spread the topping on breadSlice
    close the topping jar
    put the topping back in returnToppingTo

routine makeSandwich
    ...
    find a butter knife
    put the butter knife on the napkin

    spread (peanut butter, one piece of bread, the cabinet)

    wipe the butter knife on the other piece of bread

    spread (jelly, the other piece of bread, the fridge)

    put the knife in the sink

Ok, I think we’re done. (Does it make sense to send a “return topping to” parameter to a method that’s just spreading? Not now, we’re almost ready to commit…) Let’s step back and admire our craft:

routine lookForInTwoCabinets (lookFor)
    look for the lookFor in the cabinet
    if it's not there, look in the other cabinet
    return it

routine getBread
    find the bread in the bread drawer
    untie the bread bag
    take two pieces of bread from the bag
    close the bread bag
    put the bread back in the bread drawer
    put the two pieces of bread on the napkin

routine spread (topping, breadSlice, returnToppingTo)
    open the topping jar
    stick the butter knife into the topping jar
    with the butter knife, scoop out some topping
    spread the topping on breadSlice
    close the topping jar
    put the topping back in returnToppingTo

routine makeSandwich
    lookForInTwoCabinets (peanut butter)
    put the peanut butter on the counter

    look for the jelly in the fridge
    if it's not there, lookForInTwoCabinets(jelly)
    put the jelly on the counter

    find a napkin
    put the napkin on the counter

    getBread

    find a butter knife
    put the butter knife on the napkin

    spread (peanut butter, one piece of bread, the cabinet)

    wipe the butter knife on the other piece of bread

    spread (jelly, the other piece of bread, the fridge)

    put the knife in the sink

31 lines down to…32 lines. Ok, well, even if it’s longer, is it better? makeSandwich is shorter, that’s good. But it doesn’t feel like we’ve really made the job any easier — we moved stuff around, but it’s still all there. There’s no semantic compression. It’s still 3 + 3 + 3 + 3 + 3 + 3, instead of 3 * 6.

What did we think about? We had to ask ourselves whether to move “put it on the counter” into lookForInTwoCabinets. The value of getBread is questionable. We had the bug with spread putting the jelly in the cabinet, and we had to wonder about its “return topping to” parameter. Every time we consider refactoring, we risk introducing a crappy abstraction that confuses, when it should clarify. Every decision point, we have to think about it, and we might get it wrong. But that’s why they pay us the big bucks, right? Software development is hard, after all!

No. We’re looking at accidental complexity, not essential complexity. Here’s the same code, in a higher-level language, that removes some of the accidental complexity:

put peanut butter on a piece of bread
put jelly on another piece of bread
stick the peanut butter to the jelly

Essential complexity is when you start thinking, why jelly? Why not cinnamon and raisins with the peanut butter? Or currants? What kind of bread? Let’s use multigrain. Would peanut butter with jelly and banana be overkill? What to drink? Essential complexity looks at the problem, not the solution. Accidental complexity is when you say “I really want to do THIS but dammit, my language just won’t let me.” Or, “Gosh, we have so much code to move around, I can barely see what it does.” Or when you just can’t figure out where to put that parameter, or method, or class.

So what does this have to do with “less code”?

This is why we say YAGNI. If you add that method on a hunch that it’ll be helpful, you have more stuff to move around, more accidental complexity, more decisions to make about your housekeeping, all for a speculative benefit. It’s like playing lotto – you pay up front, and if you’re really lucky, you’ll win. But if you lose, you’ve wasted resources, and now you have something you need to throw away.

Each of the possible ways to code and refactor that sandwich code is pretty valid…any of them could be in our source control repository. A new hire is going to have to read through whichever one we coded, and try to mentally get from there, to the 3-liner at the end, before he can really be effective. Why don’t we just start him there?

Let’s take that 3 + 3 + 3 + 3 + 3 + 3 example again. What if we don’t use multiplication? We could still refactor it. The first two threes are kind of together, let’s group them: 6 + 3 + 3 + 3 + 3. And the last one looks kind of bulky, so let’s decompose it: 6 + 3 + 3 + 3 + 1 + 1 + 1. Could we move some of the numericality from the middle 3 to an earlier one? 6 + 3 + 4 + 2 + 1 + 1 + 1. Oh, and let’s sort, so it’s easier to find the numbers you want: 1 + 1 + 1 + 2 + 3 + 4 + 6. There! Is it immediately obvious to you that this is the same as 3 * 6? Of course not. Ralph Johnson calls refactoring “wiping dirt off a window,” and you just put more dirt on.