Seven deadly sins of programming – Sin #2


One of the guys on my wing during my freshman year of college was a trust-fund kid. His great-grandfather had done well in the market, and had left some money for each the great-grandkids.


Michael wasn’t paying for his schooling, nor was he paying for his cars, his clothes, his nice stereo, or his ski vacations. But that was okay with us, because he *was* paying for pizza, and he *was* paying for beer. Or his trust fund was.


Everything was fine until spring came around, and Michael got some bad news. His trust fund had been set up to carry him through 4 years of school, but because he spent money so fast, he had burned through all of it in less than two years. He was forced to get a job at the local grocery store to finish the year out, but couldn’t afford tuition and had to leave school and find a job to support himself.


========


It typically shows up in chapter two or three of the programming book. There’s a section titled something like, “What is an object?”, which speaks in flowing terms about the wonderful work of object-oriented development, and uses one of the following examples:



  • Geometric shapes

  • Animals

  • Musical instruments

  • Cephalopods (rare)

In this section, we find out that a square is an example of a polygon, a cheetah is a cat and also a mammal, and so on.


All of this to introduce is to the concept of “is-a”.


We then see an example where we can ask any polygon how much area it covers, tell any mammal to walk, and tell any cat to ignore us while we’re talking to it, all through the wonders of inheritance and virtual methods.


Good taste and a firm grasp of the relative usefullness of this concept would dictate spending 10 or 20 pages explaining these concepts, but most texts significantly exceed that, and most programming assignments spend a fair bit of time on that as well. Kindof like how linked lists rule your life for a month or so.


It’s therefore not surprising that many younger developers think that inheritance is a feature that you should, like, “use” when you write software.


There are a few problems with this.


The first is that “is-a” is, in my experience, a pretty rare relationship between objects. More common is the “looks the same on one dimension but has different behavior across another dimension”.


Good luck implementing this:


public class Monotreme: Mammal
{
    public override GiveBirth()
    {
        // add appropriate implementation here
    }
}


That’s the sort of thing that tends to be non-obvious when you first create an object, but annoying obvious when you’ve bought into the whole inheritance mindset.


That’s not to say that inheritance isn’t useful. It’s just to say that you should understand that a MemoryStream isn’t really a Stream, at least in the true “is-a” sense, and be prepared to deal with it.


The second problem is more philosophical and aesthetic. I recently wrote code like this:


class ClimbList: List<Climb>
{

}


Which seems like a perfectly reasonable thing to do, at least on the surface.


But the surface – or at least the surface area – is a problem. List<T> is a pretty extensive class, and when I defined ClimbList, I was saying that ClimbList does the proper thing when any of the List<T> methods or properties are called.


I’m pretty sure that’s not true. Or at least, I’m not at all sure that it *is* true (I have no tests to support such a belief), but users of ClimbList don’t have any way of knowing that the only methods I’m currently using are Add() and the indexer. Intellisense brings up all of them when I go to call one of the methods I wrote.


Which brings us in a long and roundabout way to our penultimate sin:

Sin #2 – Overuse of Inheritance

So, what should you use if you don’t use inheritance (and I am advocating that you approach it carefully and thoughtfully – it’s a similar decision to adding a virtual method)?


Composition. Make the object a field inside of your object, and then write the forwarders that you need (it’s usually not more than one or two).


And, if you really need inheritance in your design, add it carefully and thoughtfully.

Comments (17)

  1. jonskeet says:

    I occasionally become unpopular when espousing this point of view – it’s always nice to have other people to say it better than I do 🙂

    Here’s a short list of things which in my view are valuable but should be very carefully considered before use:

    Regular expressions

    Inheritance

    Binary serialization

    Operator overloading

    Extern aliases

    Anyone got any more to add? (I’m sure I will have by the morning)

  2. David says:

    I disagree with the statement that "a MemoryStream isn’t really a Stream, at least in the true "is-a" sense." That is true if you are used to thinking of a stream as a network or file stream. But that is not the definition of a stream. Per the MSDN documentation, a stream "provides a generic view of a sequence of bytes," which is exactly what a memory stream does.

    The monotreme example is dramatic, but to me that is the exception not the rule; it only occurs in cases where your base classes have not or cannot be strictly defined, which in a well-designed object model should not generally be the case.

    That is not to say that inheritance cannot be overused; it certainly can. Especially in C++, when you could inherit from a base class privately (I still don’t understand the logic behind that concept; either ChildClass "is-a" BaseClass, or it isn’t). .NET’s inheritance model, which forces a child class to inherit all of its base class’s functionality, also forces you to think harder about whether you truly have an "is-a" relationship, as in your ClimbList example. That’s why I don’t like interfaces like IBindingList, which force you to implement properties which indicate whether other functionality in the interface is supported. That is not an "is-a" relationship. If the various pieces of functionality are not all required as part of the interface, then there should be separate interfaces to implement them.

  3. ericgu says:

    David,

    My point on MemoryStream is that the classes derived from Stream aren’t true examples of "is-a".

    If it was, the Stream class wouldn’t have CanRead, CanSeek, CanWrite properties.

    Eric

  4. Chris Nahr says:

    Good post. Those artificial textbook examples always make it sound as if inheritance was universally useful, totally obvious, and impossible to get wrong, when in fact it’s none of these. I wished textbooks altogether refrained from using nonsense examples like animals or geometric shapes…

    However, I feel compelled to ask: just what does the anecdote above the line have to do with the essay on inheritance below the line?

  5. Roman says:

    I agree with you. And i have 2 tips for when implementing some inheritances.

    – Stop impementing when you have an inheritance depth greater than 2. It is a very hard work managing such a big inheritance tree.

    – be careful with Properties inside base classes. An Example: A car has 4 wheels. You think this is a good baseclass? Think about a trike (3wheels) or a truck (6 or more wheels).

  6. nksingh says:

    Chris:

    I think the kid’s inheritance was detrimental to his well-being.

    Main Article:

    I’ve been doing a lot of COM lately and I really like the system which advocates interface inheritance, polymorphism, and composition, while being neutral on implementation inheritance.  

    I’ve learned in my short Software Development experience that there is no good way to design a great object model on the first cut and that refactoring and iterative improvement is the only way to do well.

  7. ericgu says:

    Chris,

    Re-read the story while keeping the name of the sin in mind…

    Eric

  8. Chris Nahr says:

    Ahhh… that was far too clever for me. 🙂

  9. Ashwin Kambli says:

    One rule/principle to always keep in mind is the Liskov Substitution Principle (http://en.wikipedia.org/wiki/Liskov_substitution_principle).

    Simply put: Before inheriting your class from a base class ask the question "Can instances of my inherited class replace instances of the base class and not break the system?". If the answer is an overwhelming "Yes" then go ahead with it.

  10. So, the time has come for the worst sin.

    Just to recap – and so there is one post that lists them all…